Buffer Pool — InnoDB의 메모리 관리와 페이지 교체
데이터베이스가 매번 디스크에서 데이터를 읽는다면 얼마나 느릴까요? 메모리에 데이터를 올려놓으면 얼마나 빨라질까요?
Buffer Pool이란
InnoDB Buffer Pool은 디스크에 저장된 데이터 페이지와 인덱스 페이지를 메모리에 캐싱하는 영역입니다. MySQL 성능의 가장 핵심적인 요소라고 해도 과언이 아닙니다.
디스크 I/O는 메모리 접근보다 수만 배 느립니다. 자주 사용하는 데이터를 메모리에 올려놓으면 디스크 접근 없이 바로 처리할 수 있습니다.
SELECT * FROM users WHERE id = 1;
1) Buffer Pool에 해당 페이지가 있는 경우 (Buffer Pool Hit)
→ 메모리에서 바로 읽기 (~0.1ms)
2) Buffer Pool에 없는 경우 (Buffer Pool Miss)
→ 디스크에서 읽어 Buffer Pool에 올린 후 반환 (~10ms)
Buffer Pool의 내부 구조
Buffer Pool은 단순한 캐시가 아닙니다. 세 가지 리스트로 페이지를 관리합니다.
1. LRU List (Least Recently Used)
Buffer Pool에 있는 모든 페이지를 관리합니다. 자주 사용되는 페이지는 앞쪽에, 오래 사용되지 않은 페이지는 뒤쪽에 위치합니다.
InnoDB의 LRU는 일반적인 LRU와 다릅니다. Midpoint Insertion Strategy를 사용합니다.
LRU 리스트 구조:
[Head] ←── young 서브리스트 (최근 많이 사용된 페이지) ──→ [Midpoint]
[Midpoint] ←── old 서브리스트 (새로 읽은/오래된 페이지) ──→ [Tail]
약 5/8 약 3/8
- Young 서브리스트: 자주 접근되는 "뜨거운" 페이지
- Old 서브리스트: 새로 읽힌 페이지나 한동안 접근되지 않은 페이지
- Midpoint: 두 서브리스트의 경계.
innodb_old_blocks_pct로 비율 조절 (기본 37%, 약 3/8)
왜 Midpoint 전략을 사용하는가
일반 LRU를 사용하면 풀 테이블 스캔 같은 대량 읽기 작업이 Buffer Pool을 오염시킵니다. 한 번만 읽고 다시 쓰지 않을 페이지들이 자주 사용되는 페이지를 밀어냅니다.
Midpoint 전략에서는 새 페이지가 old 서브리스트에 먼저 들어갑니다. 일정 시간(innodb_old_blocks_time, 기본 1000ms) 내에 다시 접근되지 않으면 tail 쪽으로 밀려나 제거됩니다. 다시 접근되면 young 서브리스트로 승격됩니다.
-- Midpoint 설정 확인
SHOW VARIABLES LIKE 'innodb_old_blocks_pct'; -- 기본 37
SHOW VARIABLES LIKE 'innodb_old_blocks_time'; -- 기본 1000 (ms)
2. Free List
아직 사용되지 않은 빈 페이지 목록입니다. 새 페이지를 Buffer Pool에 올릴 때 Free List에서 빈 페이지를 가져옵니다.
Free List가 비어 있으면 LRU 리스트의 tail에서 오래된 페이지를 제거(eviction)하여 공간을 확보합니다.
3. Flush List
Dirty Page(메모리에서 수정되었지만 아직 디스크에 기록되지 않은 페이지)의 목록입니다.
데이터 수정 흐름:
UPDATE 실행 → Buffer Pool의 페이지 수정 → Flush List에 등록
→ (나중에) 백그라운드 스레드가 디스크에 기록 → Flush List에서 제거
디스크 쓰기는 바로 하지 않고, 백그라운드에서 일정 주기로 수행합니다. 이를 Checkpoint라고 합니다.
페이지 교체 과정
Buffer Pool이 가득 찼을 때 새 페이지를 올려야 하는 상황의 흐름입니다.
1. Free List에서 빈 페이지 검색
├─ 있으면 → 빈 페이지 사용
└─ 없으면 → LRU tail에서 제거할 페이지 선택
├─ Clean Page → 바로 제거
└─ Dirty Page → 디스크에 기록(Flush) 후 제거
2. 확보된 공간에 디스크에서 읽은 새 페이지 배치
3. LRU 리스트의 Midpoint에 새 페이지 삽입
Buffer Pool 상태 모니터링
히트율 확인
SHOW STATUS LIKE 'Innodb_buffer_pool%';
핵심 지표는 다음과 같습니다.
-- Buffer Pool 히트율 계산
SELECT
(1 - (
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')
)) * 100 AS hit_rate_pct;
- 히트율 99% 이상: 양호
- 히트율 95% 미만: Buffer Pool 크기 증가를 고려해야 합니다
SHOW ENGINE INNODB STATUS
SHOW ENGINE INNODB STATUS\G
BUFFER POOL AND MEMORY 섹션에서 다음을 확인합니다.
Buffer pool size 65536 -- 전체 페이지 수 (16KB × 65536 = 1GB)
Free buffers 1024 -- 빈 페이지 수
Database pages 63000 -- 데이터가 올라간 페이지 수
Modified db pages 500 -- Dirty Page 수
Buffer Pool 크기 튜닝
innodb_buffer_pool_size
Buffer Pool의 전체 크기를 설정합니다.
-- 현재 설정 확인
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- 동적 변경 (MySQL 5.7.5+)
SET GLOBAL innodb_buffer_pool_size = 4 * 1024 * 1024 * 1024; -- 4GB
권장 크기:
- 전용 DB 서버: 전체 메모리의 50~80%
- 다른 프로세스와 공유: 전체 메모리의 50% 이하
- OS, 스레드 메모리, 기타 버퍼를 위한 여유분 필요
innodb_buffer_pool_instances
Buffer Pool을 여러 인스턴스로 나눌 수 있습니다. 각 인스턴스는 독립적인 LRU, Free, Flush 리스트를 가집니다.
-- Buffer Pool 크기가 1GB 이상이면 여러 인스턴스 권장
SET GLOBAL innodb_buffer_pool_instances = 8;
여러 인스턴스를 사용하면 동시 접근 시 뮤텍스 경합이 분산되어 성능이 향상됩니다. 일반적으로 인스턴스당 1GB 이상이 되도록 설정합니다.
innodb_buffer_pool_chunk_size
MySQL 5.7.5부터 Buffer Pool 크기를 동적으로 변경할 수 있습니다. 크기 변경은 chunk 단위(기본 128MB)로 이루어집니다.
SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size'; -- 기본 134217728 (128MB)
innodb_buffer_pool_size는 chunk_size × instances의 배수로 자동 조정됩니다.
Buffer Pool Prefetch (Read-Ahead)
InnoDB는 곧 필요할 것으로 예상되는 페이지를 미리 Buffer Pool에 올립니다.
Linear Read-Ahead
연속된 페이지를 순차적으로 읽는 패턴을 감지하면 다음 extent(64페이지)를 미리 읽습니다.
-- 임계값 설정 (기본 56)
-- extent의 56페이지 이상을 순차 접근하면 다음 extent를 미리 읽음
SHOW VARIABLES LIKE 'innodb_read_ahead_threshold';
Random Read-Ahead
같은 extent 내에서 여러 페이지가 Buffer Pool에 있으면 나머지도 미리 읽습니다.
SHOW VARIABLES LIKE 'innodb_random_read_ahead'; -- 기본 OFF
Buffer Pool Dump & Load
서버 재시작 시 Buffer Pool이 비어 있으면 워밍업에 시간이 걸립니다. MySQL 5.6+에서는 종료 전에 Buffer Pool 상태를 저장하고 시작 시 복원할 수 있습니다.
-- 자동 저장/복원 활성화
SET GLOBAL innodb_buffer_pool_dump_at_shutdown = ON;
SET GLOBAL innodb_buffer_pool_load_at_startup = ON;
-- 수동 저장
SET GLOBAL innodb_buffer_pool_dump_now = ON;
-- 수동 복원
SET GLOBAL innodb_buffer_pool_load_now = ON;
-- 복원 진행 상태 확인
SHOW STATUS LIKE 'Innodb_buffer_pool_load_status';
실제로 데이터를 다시 읽는 것이므로 대용량 Buffer Pool에서는 복원 시간이 길 수 있습니다.
정리
- Buffer Pool은 InnoDB의 핵심 메모리 영역으로, 디스크 I/O를 줄여 성능을 높입니다
- LRU 리스트는 Midpoint Insertion 전략으로 풀 스캔에 의한 캐시 오염을 방지합니다
- Flush List의 Dirty Page는 백그라운드에서 주기적으로 디스크에 기록됩니다
- Buffer Pool 크기는 전용 서버 기준 전체 메모리의 50~80%가 권장되며, 히트율 99% 이상을 목표로 합니다