InnoDB 아키텍처 — MySQL 기본 엔진의 내부 구조
MySQL에 데이터를 저장하면 디스크에 바로 쓰일까요, 아니면 어딘가에 먼저 보관되었다가 쓰일까요?
InnoDB는 MySQL 8.0의 기본 스토리지 엔진입니다. 트랜잭션, 행 수준 잠금, 크래시 복구를 모두 지원하며, 그 뒤에는 정교한 내부 아키텍처가 숨어 있습니다.
개념 정의
InnoDB 아키텍처는 크게 메모리 영역 과 디스크 영역 으로 나뉩니다.
- **메모리 영역 **: Buffer Pool, Change Buffer, Adaptive Hash Index, Log Buffer
- ** 디스크 영역 **: 테이블스페이스, Redo Log, Undo Log, Doublewrite Buffer
모든 읽기/쓰기는 가능한 한 메모리에서 처리하고, 디스크 I/O를 최소화하는 것이 InnoDB 설계의 핵심 철학입니다.
Buffer Pool — InnoDB의 심장
Buffer Pool은 InnoDB에서 가장 중요한 메모리 영역입니다. 테이블 데이터와 인덱스 페이지를 메모리에 캐시합니다.
기본 동작
읽기 요청 → Buffer Pool에 페이지 있음? → 있으면 메모리에서 반환 (캐시 히트)
→ 없으면 디스크에서 읽어와서 Buffer Pool에 적재
LRU(Least Recently Used) 알고리즘
Buffer Pool은 단순한 LRU가 아닌 ** 변형된 LRU** 리스트를 사용합니다.
┌──────────────────────────────────────┐
│ Young 영역 (5/8) │ Old 영역 (3/8) │
│ (자주 접근하는 │ (새로 읽힌 │
│ 핫 페이지) │ 페이지) │
└──────────────────────────────────────┘
- 새로 읽힌 페이지는 Old 영역의 head 에 삽입됩니다
- Old 영역에서 다시 접근되면 Young 영역 으로 승격됩니다
- 풀 테이블 스캔 시 한 번만 읽는 페이지가 핫 페이지를 밀어내는 것을 방지합니다
주요 설정
-- Buffer Pool 크기 (전용 서버에서 전체 메모리의 50~80% 권장)
SET GLOBAL innodb_buffer_pool_size = 8589934592; -- 8GB
-- Buffer Pool 인스턴스 수 (멀티코어 환경에서 경합 감소)
-- innodb_buffer_pool_instances = 8 (my.cnf에서 설정)
-- Buffer Pool 적중률 확인
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
Buffer Pool 적중률(Hit Rate) 계산:
Hit Rate = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) × 100
99% 이상이면 양호, 95% 미만이면 Buffer Pool 크기 증가를 고려해야 합니다.
Change Buffer
Change Buffer는 세컨더리 인덱스 의 변경 사항을 버퍼링하는 메모리 영역입니다.
왜 필요한가
세컨더리 인덱스는 Primary Key와 달리 데이터 삽입 순서와 인덱스 순서가 다릅니다. 매 INSERT마다 인덱스 페이지를 디스크에서 읽어와야 한다면 랜덤 I/O 가 발생합니다.
INSERT 실행 → 세컨더리 인덱스 페이지가 Buffer Pool에 없음
→ Change Buffer에 변경 사항 기록 (디스크 I/O 없음)
→ 나중에 해당 페이지가 읽힐 때 머지(Merge)
적용 조건
- 세컨더리 인덱스 에만 적용됩니다 (Primary Key는 해당 없음)
- 유니크 인덱스에는 적용되지 않습니다 (유니크 체크를 위해 페이지를 읽어야 하므로)
- INSERT, UPDATE, DELETE 모두 버퍼링 가능
-- Change Buffer 사용량 확인
SHOW STATUS LIKE 'Innodb_ibuf%';
-- Change Buffer 사용 범위 설정
-- innodb_change_buffering = all (기본값: inserts, deletes, purges 모두)
Adaptive Hash Index (AHI)
InnoDB는 기본적으로 B+Tree 인덱스를 사용하지만, 자주 접근하는 페이지에 대해 자동으로 ** 해시 인덱스 **를 생성합니다.
B+Tree 검색: O(log n) — 루트 → 중간 노드 → 리프 노드
해시 검색: O(1) — 해시 함수 → 바로 페이지 접근
- InnoDB가 자동으로 판단하여 생성합니다 (DBA가 직접 생성할 수 없음)
- 특정 패턴의 쿼리가 반복적으로 같은 페이지에 접근하면 활성화됩니다
- 워크로드에 따라 오히려 성능을 저하시킬 수 있습니다
-- AHI 상태 확인
SHOW ENGINE INNODB STATUS\G
-- "Hash table size" 섹션에서 확인
-- AHI 비활성화 (필요 시)
SET GLOBAL innodb_adaptive_hash_index = OFF;
Log Buffer
Log Buffer는 Redo Log 에 기록할 내용을 임시로 보관하는 메모리 영역입니다.
트랜잭션 변경 → Log Buffer에 기록 → 주기적으로 Redo Log 파일에 플러시
-- Log Buffer 크기 (기본 16MB)
-- innodb_log_buffer_size = 16777216
-- 플러시 주기 설정
-- innodb_flush_log_at_trx_commit = 1 (커밋마다 플러시 — 가장 안전)
-- innodb_flush_log_at_trx_commit = 0 (1초마다 플러시 — 최대 1초 데이터 손실 가능)
-- innodb_flush_log_at_trx_commit = 2 (커밋마다 OS 버퍼에 쓰기, 1초마다 디스크 동기화)
innodb_flush_log_at_trx_commit 값에 따른 차이:
| 값 | 동작 | 안전성 | 성능 |
|---|---|---|---|
| 1 | 커밋마다 디스크 동기화 | 최고 | 가장 느림 |
| 2 | 커밋마다 OS 버퍼, 1초마다 동기화 | 높음 | 중간 |
| 0 | 1초마다 디스크 동기화 | 중간 | 가장 빠름 |
테이블스페이스
InnoDB의 데이터는 테이블스페이스 단위로 디스크에 저장됩니다.
시스템 테이블스페이스
ibdata1 — 시스템 테이블스페이스 파일
├── Data Dictionary (메타데이터)
├── Doublewrite Buffer
├── Change Buffer
└── Undo Logs (5.6 이전 기본)
File-Per-Table 테이블스페이스
MySQL 5.6.6부터 기본값으로, 테이블마다 별도의 .ibd 파일을 생성합니다.
my_database/
├── users.ibd ← users 테이블 데이터 + 인덱스
├── orders.ibd ← orders 테이블 데이터 + 인덱스
└── products.ibd ← products 테이블 데이터 + 인덱스
장점:
- 테이블 단위로 백업/복원이 용이합니다
TRUNCATE TABLE이 즉시 디스크 공간을 반환합니다- 테이블별 용량 확인이 쉽습니다
General 테이블스페이스
-- 여러 테이블을 하나의 테이블스페이스에 저장
CREATE TABLESPACE my_space
ADD DATAFILE 'my_space.ibd'
ENGINE = InnoDB;
CREATE TABLE t1 (...) TABLESPACE my_space;
Doublewrite Buffer
InnoDB 페이지 크기는 기본 16KB 이지만, 대부분의 파일시스템은 4KB 단위로 쓰기합니다. 16KB 쓰기 도중 크래시가 발생하면 부분 쓰기(Partial Page Write) 가 발생할 수 있습니다.
데이터 페이지 → Doublewrite Buffer (순차 쓰기) → 실제 테이블스페이스 (랜덤 쓰기)
- 크래시 복구 시 Doublewrite Buffer에서 완전한 페이지를 찾아 복구합니다
- 쓰기 I/O가 2배가 되지만, 순차 쓰기이므로 실제 오버헤드는 5~10% 수준입니다
- MySQL 8.0.20부터 Doublewrite Buffer가 별도 파일로 분리되었습니다
InnoDB 전체 아키텍처 요약
┌─────────────── 메모리 영역 ───────────────┐
│ Buffer Pool │ Log Buffer │
│ (데이터 캐시) │ (Redo Log 버퍼) │
│ │ │
│ Change Buffer │ Adaptive Hash Index │
│ (인덱스 변경) │ (자동 해시 인덱스) │
├─────────────── 디스크 영역 ───────────────┤
│ System Tablespace (ibdata1) │
│ File-Per-Table Tablespace (.ibd) │
│ Redo Log Files (ib_logfile0, 1) │
│ Undo Tablespace │
│ Doublewrite Buffer Files │
└───────────────────────────────────────────┘
모니터링 명령어
-- InnoDB 전체 상태 확인 (가장 상세)
SHOW ENGINE INNODB STATUS\G
-- Buffer Pool 통계
SELECT * FROM information_schema.INNODB_BUFFER_POOL_STATS;
-- 현재 Buffer Pool에 있는 페이지 확인
SELECT TABLE_NAME, INDEX_NAME, NUMBER_RECORDS, DATA_SIZE
FROM information_schema.INNODB_BUFFER_PAGE
WHERE TABLE_NAME IS NOT NULL
LIMIT 20;
주의할 점
Buffer Pool 크기가 부족하면 모든 것이 느려진다
Buffer Pool이 작으면 디스크 I/O가 빈번해져서 SELECT, INSERT 모두 성능이 급격히 떨어집니다. 히트율이 95% 미만이면 크기 증가를 즉시 검토해야 합니다.
innodb_flush_log_at_trx_commit 변경 시 데이터 손실 위험
성능을 위해 이 값을 0이나 2로 변경하면, 서버 크래시 시 최대 1초 분량의 커밋된 트랜잭션이 유실될 수 있습니다. 금융/결제 시스템에서는 반드시 1을 유지해야 합니다.
Change Buffer가 유니크 인덱스에는 적용되지 않는다
유니크 인덱스는 중복 체크를 위해 반드시 페이지를 읽어야 하므로 Change Buffer를 사용할 수 없습니다. 유니크 인덱스가 많은 테이블에서 INSERT가 느리다면 이것이 원인일 수 있습니다.
정리
| 항목 | 설명 |
|---|---|
| Buffer Pool | 디스크 I/O를 최소화하는 핵심 메모리 영역, 히트율 99% 이상 목표 |
| Change Buffer | 비유니크 세컨더리 인덱스의 랜덤 I/O를 줄이는 버퍼링 |
| Adaptive Hash Index | 반복 접근 패턴에서 B+Tree를 O(1)로 우회하는 자동 해시 |
| Log Buffer | Redo Log의 동기 쓰기를 줄이는 메모리 버퍼 |
| Doublewrite Buffer | 16KB 페이지의 부분 쓰기 손상을 방지하는 안전장치 |
| 핵심 설정 | innodb_buffer_pool_size가 성능에 가장 큰 영향 |