InnoDB의 메모리 캐시 시스템
InnoDB Buffer Pool (예시 : 8GB 설정)
┌────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Data Pages (실제 테이블 데이터) │70% │
│ │ - users 테이블의 행들 │ │
│ │ - orders 테이블의 행들 │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Index Pages (B+Tree 인덱스) │ 20%│
│ │ - Primary Key 인덱스 │ │
│ │ - Secondary 인덱스들 │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Change Buffer (Insert/Update 버퍼) │ 5%│
│ └────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Adaptive Hash Index (자동 생성 해시) │ 3%│
│ └────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Lock Information (행 잠금 정보) │ 2%│
│ └────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────┘
Page 내부 구조
FIL Header ( 38 bytes)
Page Header (56bytes)
Infimum Record (논리적 최솟값)
User Records (실제 데이터)
Supremum Record(논리적 최대값)
Free Space(빈공간)
새 레코드 삽입 가능영역
Page Dricetory(슬롯)
FIL Trailer (8bytes)
Buffer Pool의 페이지 타입별 분포
SELECT
page_type,
COUNT(*) as page_count,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage
FROM
information_schema.INNODB_BUFFER_PAGE
GROUP BY
page_type
ORDER BY
page_count DESC;
+-------------------+------------+------------+
| page_type | page_count | percentage |
+-------------------+------------+------------+
| UNKNOWN | 6977 | 85.18 |
| UNDO_LOG | 482 | 5.88 |
| SYSTEM | 387 | 4.72 |
| INDEX | 274 | 3.35 | <- B+Tree 인덱스
| FILE_SPACE_HEADER | 17 | 0.21 | <- 일반 데이터
| INODE | 15 | 0.18 |
| LOB_DATA | 15 | 0.18 |
| LOB_FIRST | 14 | 0.17 |
| IBUF_BITMAP | 3 | 0.04 |
| RSEG_ARRAY | 2 | 0.02 |
| SDI_INDEX | 2 | 0.02 |
| TRX_SYSTEM | 1 | 0.01 |
| IBUF_INDEX | 1 | 0.01 |
| LOB_INDEX | 1 | 0.01 |
+-------------------+------------+------------+
14 rows in set (0.058 sec)
Buffer Pool Hit Ratio (캐시 효율)
mysql> SELECT
-> ROUND(
-> (1 - (r.Innodb_buffer_pool_reads / req.Innodb_buffer_pool_read_requests)) * 100,
-> 2
-> ) AS hit_ratio_percent
-> FROM
-> (SELECT VARIABLE_VALUE AS Innodb_buffer_pool_reads
-> FROM performance_schema.global_status
-> WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') r,
-> (SELECT VARIABLE_VALUE AS Innodb_buffer_pool_read_requests
-> FROM performance_schema.global_status
-> WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') req;
+-------------------+
| hit_ratio_percent |
+-------------------+
| 98.64 |
+-------------------+
1 row in set (0.00 sec)
목표 Hit Ratio: 99% 이상
- 99.5% → 매우 우수
- 95-99% → 양호
- 90% 미만 → Buffer Pool 크기 증설 필요
Buffer Pool 튜닝
-- 현재 Buffer Pool 크기 확인
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- 일반적 권장사항:
-- 전용 DB 서버: 물리 메모리의 70-80%
-- 공유 서버: 물리 메모리의 50-60%
-- 예: 16GB 서버 → 12GB Buffer Pool
-- my.cnf에 설정:
/*
[mysqld]
innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 8 ← 병렬 처리 (8GB 이상 시)
innodb_buffer_pool_chunk_size = 128M
*/
-- 동적으로 크기 변경
SET GLOBAL innodb_buffer_pool_size = 12884901888; -- 12GB
WAL(Write - Ahead Loggin)
데이터를 디스크에 쓰기전 로그를 먼저 작성
Redo Log의 구조
Redo Log는 고정 크기의 순환 버퍼 구조
┌─────────────────────────────────────────┐
│ Redo Log Ring │
│ │
│ Write Pos ─┐ │
│ ▼ │
│ ┌───────────────────────────────────┐ │
│ │ [Record 1] [Record 2] [Record 3] │ │
│ │ │ │
│ │ ... [Record N] ... │ │
│ │ ▲ │ │
│ │ └── Checkpoint Pos
│ └───────────────────────────────────┘ │
│ │
│ Write Pos가 Checkpoint Pos를 따라잡으면? |
│ → Checkpoint 강제 실행 (Flush) │
└─────────────────────────────────────────┘
Redo Log Record 형식
┌─────────────────────────────────────────┐
│ [Type] [Space ID] [Page Number] │ ← 어떤 페이지인지
│ [Offset] [Length] [Data] │ ← 어떤 변경인지
└─────────────────────────────────────────┘
UPDATE users SET balance = 1000 WHERE id = 42
Redo Log Record
Undo Log의 역할
MVCC 동작원리
같은 데이터의 여러 버전을 동시에 유지
초기 상태:
┌────────────────────────────┐
│ users 테이블 │
│ id=1, balance=1000 │
│ (Transaction ID: 100) │
└────────────────────────────┘
트랜잭션 A (ID: 101) 시작:
UPDATE users SET balance = 1500 WHERE id = 1;
데이터 변화:
┌────────────────────────────┐
│ users 테이블 (최신 버전) │
│ id=1, balance=1500 │ ← 트랜잭션 101이 수정
│ (Transaction ID: 101) │
│ [Roll Pointer] ───────┐ │
└────────────────────────│────┘
│
▼
┌────────────────────────────┐
│ Undo Log (이전 버전) │
│ id=1, balance=1000 │ ← 원래 값
│ (Transaction ID: 100) │
└────────────────────────────┘
트랜잭션 B (ID: 102) 시작:
SELECT balance FROM users WHERE id = 1;
트랜잭션 B가 보는 것
트랜잭션 A가 COMMIT 한 경우
Undo Log 레코드 체인
여러 트랜잭션이 동일 행을 수정하면 버전 체인 형성
최신 ──────────────────> 과거
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ V4 │───▶│ V3 │───▶│ V2 │───▶│ V1 │
│(1500)│ │(1200)│ │(1000)│ │(800) │
│TxID │ │TxID │ │TxID │ │TxID │
│ 104 │ │ 103 │ │ 102 │ │ 101 │
└──────┘ └──────┘ └──────┘ └──────┘
▲
│
최신 버전 (Buffer Pool & 디스크)
→ 각 트랜잭션은 자신의 Read View에 따라 적절한 버전 선택
MVCC 실습
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance INT,
version INT DEFAULT 0
) ENGINE=InnoDB;
INSERT INTO accounts VALUES (1, 1000, 0);
-- ===== 세션 1 (터미널 1) =====
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
-- 1. 초기 값 확인
SELECT * FROM accounts WHERE id = 1;
+----+---------+---------+
| id | balance | version |
+----+---------+---------+
| 1 | 1000 | 0 |
+----+---------+---------+
1 row in set (0.000 sec)
-- 2. 10초 대기 (세션 2가 수정하도록)
SELECT SLEEP(10);
+-----------+
| SLEEP(10) |
+-----------+
| 0 |
+-----------+
1 row in set (10.003 sec)
-- 3. 다시 조회 (세션 2가 수정했지만 격리됨)
SELECT * FROM accounts WHERE id = 1;
+----+---------+---------+
| id | balance | version |
+----+---------+---------+
| 1 | 1000 | 0 |
+----+---------+---------+
1 row in set (0.000 sec)
-- 4. 트랜잭션 커밋
COMMIT;
-- 5. 커밋 후 조회
SELECT * FROM accounts WHERE id = 1;
+----+---------+---------+
| id | balance | version |
+----+---------+---------+
| 1 | 1500 | 1 |
+----+---------+---------+
1 row in set (0.000 sec)
-- ===== 세션 2 (터미널 2) =====
START TRANSACTION;
-- 세션 1이 대기 중일 때 수정
UPDATE accounts SET balance = 1500, version = version + 1
WHERE id = 1;
-- 커밋
COMMIT;
Undo Log 모니터링
SELECT
TABLESPACE_NAME,
FILE_NAME,
ROUND(TOTAL_EXTENTS * EXTENT_SIZE / 1024 / 1024, 2) as size_mb,
ROUND(FREE_EXTENTS * EXTENT_SIZE / 1024 / 1024, 2) as free_mb
FROM
information_schema.FILES
WHERE
TABLESPACE_NAME LIKE 'innodb_undo%';
+-----------------+------------+---------+---------+
| TABLESPACE_NAME | FILE_NAME | size_mb | free_mb |
+-----------------+------------+---------+---------+
| innodb_undo_001 | ./undo_001 | 16.00 | 2.00 |
| innodb_undo_002 | ./undo_002 | 16.00 | 2.00 |
+-----------------+------------+---------+---------+
2 rows in set (0.007 sec)
Undo Log History List Length
SHOW ENGINE INNODB STATUS\G
------------
TRANSACTIONS
------------
Trx id counter 118953
Purge done for trx's n:o < 118953 undo n:o < 0 state: running but idle
**History list length 0**
UPDATE users SET balance = 1500 WHERE id = 1; → Parse Tree 생성Optimizer 실행 계획 수립
Executor → InnoDB에 명령 전달
PRIMARY KEY (id = 1) 사용하여 검색 Hash Table 체크 (빠른 검색) Key: (Space ID, Page Number)목적
Undo Tablespace에 기록
┌─────────────────────────────────────────┐
│ Undo Record │
├─────────────────────────────────────────┤
│ Type: UPDATE_EXISTING │
│ Transaction ID: 12345 │
│ Table ID: 23 │
│ Index: PRIMARY │
│ Key: id=1 │
│ │
│ Old Values (복구용): │
│ balance = 1000 │
│ │
│ Previous Version Pointer: NULL │
└─────────────────────────────────────────┘
→ Undo Log의 주소를 Roll Pointer
┌─────────────────────────────────────────────────┐
│ Buffer Pool의 Page 157 수정 │
├─────────────────────────────────────────────────┤
│ │
│ 수정 전: │
│ ┌─────────────────────────────────────────┐ │
│ │ id=1, balance=1000 │ │
│ │ TxID=12340 │ │
│ │ Roll Pointer=NULL │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 수정 후: │
│ ┌─────────────────────────────────────────┐ │
│ │ id=1, balance=1500 ← 값 변경! │ │
│ │ TxID=12345 ← 현재 트랜잭션 ID │ │
│ │ Roll Pointer=0x7F3A2B1C │ │
│ │ └─ Undo Log 주소 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 이 페이지는 이제 "Dirty Page" │
│ (메모리와 디스크가 불일치) │
│ │
│ Modified DB Pages 카운터 증가 │
│ │
└─────────────────────────────────────────────────┘
Redo Log Buffer → Redo Log File 플러시 (디스크에 물리적 기록)
Log Buffer -(fsync 호출) > ib_logfile0 (디스크)
트랜잭션 상태 변경
ACTIVE → COMMITED
Lock 해제
id = 1 행의 잠금 해제