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에서 가장 중요한 메모리 영역입니다. 테이블 데이터와 인덱스 페이지를 메모리에 캐시합니다.

기본 동작

PLAINTEXT
읽기 요청 → Buffer Pool에 페이지 있음? → 있으면 메모리에서 반환 (캐시 히트)
                                      → 없으면 디스크에서 읽어와서 Buffer Pool에 적재

LRU(Least Recently Used) 알고리즘

Buffer Pool은 단순한 LRU가 아닌 ** 변형된 LRU** 리스트를 사용합니다.

PLAINTEXT
┌──────────────────────────────────────┐
│  Young 영역 (5/8)  │  Old 영역 (3/8)  │
│  (자주 접근하는      │  (새로 읽힌       │
│   핫 페이지)         │   페이지)         │
└──────────────────────────────────────┘
  • 새로 읽힌 페이지는 Old 영역의 head 에 삽입됩니다
  • Old 영역에서 다시 접근되면 Young 영역 으로 승격됩니다
  • 풀 테이블 스캔 시 한 번만 읽는 페이지가 핫 페이지를 밀어내는 것을 방지합니다

주요 설정

SQL
-- 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) 계산:

PLAINTEXT
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 가 발생합니다.

PLAINTEXT
INSERT 실행 → 세컨더리 인덱스 페이지가 Buffer Pool에 없음
           → Change Buffer에 변경 사항 기록 (디스크 I/O 없음)
           → 나중에 해당 페이지가 읽힐 때 머지(Merge)

적용 조건

  • 세컨더리 인덱스 에만 적용됩니다 (Primary Key는 해당 없음)
  • 유니크 인덱스에는 적용되지 않습니다 (유니크 체크를 위해 페이지를 읽어야 하므로)
  • INSERT, UPDATE, DELETE 모두 버퍼링 가능
SQL
-- Change Buffer 사용량 확인
SHOW STATUS LIKE 'Innodb_ibuf%';

-- Change Buffer 사용 범위 설정
-- innodb_change_buffering = all (기본값: inserts, deletes, purges 모두)

Adaptive Hash Index (AHI)

InnoDB는 기본적으로 B+Tree 인덱스를 사용하지만, 자주 접근하는 페이지에 대해 자동으로 ** 해시 인덱스 **를 생성합니다.

PLAINTEXT
B+Tree 검색: O(log n) — 루트 → 중간 노드 → 리프 노드
해시 검색:   O(1)     — 해시 함수 → 바로 페이지 접근
  • InnoDB가 자동으로 판단하여 생성합니다 (DBA가 직접 생성할 수 없음)
  • 특정 패턴의 쿼리가 반복적으로 같은 페이지에 접근하면 활성화됩니다
  • 워크로드에 따라 오히려 성능을 저하시킬 수 있습니다
SQL
-- AHI 상태 확인
SHOW ENGINE INNODB STATUS\G
-- "Hash table size" 섹션에서 확인

-- AHI 비활성화 (필요 시)
SET GLOBAL innodb_adaptive_hash_index = OFF;

Log Buffer

Log Buffer는 Redo Log 에 기록할 내용을 임시로 보관하는 메모리 영역입니다.

PLAINTEXT
트랜잭션 변경 → Log Buffer에 기록 → 주기적으로 Redo Log 파일에 플러시
SQL
-- 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초마다 동기화높음중간
01초마다 디스크 동기화중간가장 빠름

테이블스페이스

InnoDB의 데이터는 테이블스페이스 단위로 디스크에 저장됩니다.

시스템 테이블스페이스

PLAINTEXT
ibdata1 — 시스템 테이블스페이스 파일
├── Data Dictionary (메타데이터)
├── Doublewrite Buffer
├── Change Buffer
└── Undo Logs (5.6 이전 기본)

File-Per-Table 테이블스페이스

MySQL 5.6.6부터 기본값으로, 테이블마다 별도의 .ibd 파일을 생성합니다.

PLAINTEXT
my_database/
├── users.ibd       ← users 테이블 데이터 + 인덱스
├── orders.ibd      ← orders 테이블 데이터 + 인덱스
└── products.ibd    ← products 테이블 데이터 + 인덱스

장점:

  • 테이블 단위로 백업/복원이 용이합니다
  • TRUNCATE TABLE이 즉시 디스크 공간을 반환합니다
  • 테이블별 용량 확인이 쉽습니다

General 테이블스페이스

SQL
-- 여러 테이블을 하나의 테이블스페이스에 저장
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) 가 발생할 수 있습니다.

PLAINTEXT
데이터 페이지 → Doublewrite Buffer (순차 쓰기) → 실제 테이블스페이스 (랜덤 쓰기)
  • 크래시 복구 시 Doublewrite Buffer에서 완전한 페이지를 찾아 복구합니다
  • 쓰기 I/O가 2배가 되지만, 순차 쓰기이므로 실제 오버헤드는 5~10% 수준입니다
  • MySQL 8.0.20부터 Doublewrite Buffer가 별도 파일로 분리되었습니다

InnoDB 전체 아키텍처 요약

PLAINTEXT
┌─────────────── 메모리 영역 ───────────────┐
│  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                 │
└───────────────────────────────────────────┘

모니터링 명령어

SQL
-- 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 BufferRedo Log의 동기 쓰기를 줄이는 메모리 버퍼
Doublewrite Buffer16KB 페이지의 부분 쓰기 손상을 방지하는 안전장치
핵심 설정innodb_buffer_pool_size가 성능에 가장 큰 영향
댓글 로딩 중...