Published on

MySQL InnoDB Buffer Pool 부족? 히트율로 튜닝

Authors

서버가 느려졌을 때 흔히 innodb_buffer_pool_size부터 키우려는 유혹이 큽니다. 하지만 Buffer Pool 튜닝은 “메모리를 더 주면 빨라진다”가 아니라, 현재 워크로드가 디스크 I/O로 밀리고 있는지를 먼저 확인하고, 히트율(hit rate)과 실제 읽기 패턴을 근거로 조정해야 합니다.

이 글에서는 InnoDB Buffer Pool이 부족한 상황을 히트율과 핵심 상태 변수로 판별하고, 사이즈/인스턴스/관련 파라미터를 단계적으로 튜닝하는 방법을 정리합니다.

참고로 DB가 느려진 증상이 복제 지연으로도 나타날 수 있어요. 복제 환경이라면 MySQL 8 복제 지연 - GTID·병렬복제 튜닝도 함께 점검하면 원인 분리가 빨라집니다.

Buffer Pool이 하는 일과 “부족”의 의미

InnoDB Buffer Pool은 크게 두 가지를 캐시합니다.

  • 데이터 페이지(클러스터/세컨더리 인덱스 페이지)
  • 변경 버퍼, 어댑티브 해시 인덱스, 락/데이터 딕셔너리 등 내부 구조 일부

Buffer Pool이 “부족”하다는 말은 보통 다음 중 하나입니다.

  1. 읽을 때마다 디스크에서 페이지를 가져오느라 대기가 늘었다(랜덤 I/O 증가)
  2. 워킹셋(자주 접근하는 데이터/인덱스)이 메모리에 못 올라가 캐시가 계속 쫓겨난다(eviction)
  3. 스캔/리포트성 쿼리가 캐시를 오염시켜, OLTP 쿼리까지 함께 느려진다

이 중 1번과 2번은 히트율과 읽기 지표로 비교적 명확히 보이고, 3번은 히트율만으로는 놓치기 쉬워서 “스캔 패턴” 지표도 같이 봐야 합니다.

히트율을 계산하는 가장 안전한 방법

히트율은 “Buffer Pool에서 읽었는지(논리 읽기)” vs “디스크에서 읽었는지(물리 읽기)”의 비율입니다.

MySQL 8 기준으로 가장 자주 쓰는 계산은 다음입니다.

  • 논리 읽기: Innodb_buffer_pool_read_requests
  • 물리 읽기: Innodb_buffer_pool_reads

히트율 공식은 아래처럼 계산합니다.

hit_rate = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)

1) 현재 누적값으로 히트율 보기

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';

-- 또는 한 번에
SHOW GLOBAL STATUS
WHERE Variable_name IN (
  'Innodb_buffer_pool_read_requests',
  'Innodb_buffer_pool_reads'
);

이 값은 서버 기동 이후 누적이므로, “지금 느린 구간”을 보려면 구간 델타를 봐야 합니다.

2) 1분 구간 델타로 히트율 보기(추천)

-- 1) t1
SELECT
  NOW() AS ts,
  @@GLOBAL.innodb_buffer_pool_size AS bp_size,
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
   WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests') AS read_req,
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status
   WHERE VARIABLE_NAME='Innodb_buffer_pool_reads') AS reads;

-- 2) 60초 후 t2 동일 쿼리 실행 후
-- hit_rate = 1 - ((reads2 - reads1) / (read_req2 - read_req1))

운영에서는 이 계산을 Prometheus/Grafana나 스크립트로 자동화합니다. 중요한 포인트는 히트율을 “구간”으로 본다는 것입니다.

히트율 해석: 몇 %면 부족한가?

정답은 워크로드마다 다르지만, 실무에서 자주 쓰는 가이드라인은 다음과 같습니다.

  • 99.9% 이상: 보통 읽기 캐시는 충분. 느리면 다른 원인(락, CPU, 쿼리 플랜, fsync, 로그/체크포인트 등)을 의심
  • 99% 전후: 트래픽/데이터 크기에 따라 체감 성능이 갈릴 수 있음. 디스크가 NVMe인지, 랜덤 I/O가 병목인지 확인 필요
  • 98% 이하: OLTP에서라면 대체로 “부족” 가능성이 큼. 특히 reads 증가 속도가 빠르면 즉시 점검

주의할 점은 히트율이 높아도 느릴 수 있다는 겁니다.

  • 풀스캔/대량 리포트 쿼리가 메모리에서 돌아가면 히트율은 높게 나오지만 CPU/버퍼 오염으로 OLTP가 느려질 수 있음
  • 쓰기 병목(redo log flush, 체크포인트) 중심이면 히트율이 높아도 TPS가 안 나옴

그래서 히트율과 함께 아래 지표들을 같이 봐야 합니다.

“정말 Buffer Pool이 원인인지” 확인하는 핵심 지표

1) 물리 읽기 속도와 대기

  • Innodb_buffer_pool_reads의 증가 속도
  • OS 레벨 디스크 지표(읽기 IOPS, await)

물리 읽기가 늘고 디스크 대기(await)가 올라가면 캐시 미스가 성능을 갉아먹고 있을 확률이 큽니다.

2) 페이지 플러시/체크포인트로 인한 간접 영향

쓰기 압력이 높으면 읽기 성능도 같이 무너집니다.

SHOW GLOBAL STATUS
WHERE Variable_name IN (
  'Innodb_data_fsyncs',
  'Innodb_data_written',
  'Innodb_os_log_fsyncs',
  'Innodb_log_waits'
);
  • Innodb_log_waits가 증가하면 redo log 공간/flush 타이밍 문제로 쓰기 대기가 발생했을 가능성이 큽니다.

3) Buffer Pool 오염(스캔) 징후

MySQL 8에서는 performance_schema로 InnoDB 버퍼 관련 메트릭을 더 풍부하게 볼 수 있습니다. 환경마다 활성화 상태가 다르지만, 가능하다면 sys 스키마 뷰도 활용하세요.

-- sys 스키마가 있다면(기본 포함)
SELECT * FROM sys.innodb_buffer_stats_by_schema ORDER BY pages DESC LIMIT 20;

특정 스키마/테이블이 페이지를 과점하고 있다면, 리포트성 쿼리나 배치가 캐시를 오염시키는지 의심할 수 있습니다.

튜닝 1단계: Buffer Pool 사이즈 결정(안전한 기준)

가장 흔한 권장치는 “DB 전용 서버라면 RAM의 60~75%”입니다. 하지만 컨테이너/공용 서버에서는 그대로 적용하면 장애가 납니다.

1) 먼저 메모리 상한을 계산

다음을 고려해 상한을 잡습니다.

  • OS/에이전트/백업/모니터링이 사용할 메모리
  • 연결 수(max_connections)와 세션당 메모리(정렬/조인 버퍼)
  • InnoDB 외 메모리(테이블 캐시, 바이너리 로그 캐시 등)

즉, Buffer Pool을 크게 잡다가 OOM이 나면 성능 이전에 가용성부터 깨집니다.

2) 변경은 “한 번에 크게”가 아니라 단계적으로

운영에서는 보통 10~20% 단위로 올리고, 다음을 관찰합니다.

  • 구간 히트율 변화
  • 물리 읽기 감소
  • p95/p99 쿼리 지연 감소
  • 디스크 await 감소

MySQL 8에서는 innodb_buffer_pool_size를 온라인으로 변경할 수 있지만(상황에 따라 리사이징 시간이 걸림), 안전하게는 트래픽이 낮은 시간에 진행하고 모니터링을 붙여두는 게 좋습니다.

-- 예시: 32GiB로 조정(바이트 단위)
SET GLOBAL innodb_buffer_pool_size = 34359738368;

튜닝 2단계: innodb_buffer_pool_instances 조정

Buffer Pool 인스턴스는 내부 락 경합을 줄이기 위한 분할입니다.

  • 너무 적으면 핫한 워크로드에서 경합이 생길 수 있고
  • 너무 많으면 인스턴스당 크기가 작아져 관리 오버헤드/효율이 떨어질 수 있습니다.

실무 가이드:

  • Buffer Pool이 수 GiB 이하이면 인스턴스를 굳이 늘릴 필요가 적습니다.
  • 수십 GiB 이상이면 innodb_buffer_pool_instances를 4~8 정도로 두고 관찰하는 경우가 많습니다.
SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

주의: 인스턴스 값은 보통 재시작이 필요합니다(환경/버전에 따라 다를 수 있으니 매뉴얼 확인).

튜닝 3단계: “히트율이 낮은데도 못 키우는” 상황의 대안

메모리를 더 줄 수 없을 때는 워킹셋을 줄이거나, 불필요한 읽기를 줄이는 방향이 효과적입니다.

1) 인덱스로 읽기량 자체를 줄이기

  • 불필요한 풀스캔 제거
  • 복합 인덱스 정렬(선행 컬럼 선택)
  • 커버링 인덱스로 랜덤 액세스 횟수 감소
EXPLAIN ANALYZE
SELECT user_id, created_at
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;

EXPLAIN ANALYZE에서 실제 읽은 행 수가 과도하면, Buffer Pool을 키우기 전에 플랜부터 잡는 게 더 큽니다.

2) 배치/리포트 쿼리로 인한 캐시 오염 분리

가능하다면 다음 전략을 고려합니다.

  • 리포트/배치는 리드 레플리카로 분리
  • 시간대 분리
  • 쿼리 튜닝(필요 컬럼만, 범위 제한)

복제 환경에서 리드 레플리카를 적극 활용한다면, 앞서 언급한 복제 지연 글도 같이 보면 운영 설계에 도움이 됩니다.

3) 테이블/인덱스 크기 자체를 줄이기

  • 불필요한 세컨더리 인덱스 제거
  • 아카이빙/파티셔닝으로 핫 데이터만 남기기
  • TEXT/BLOB 컬럼 분리(자주 읽지 않는다면)

이건 히트율을 “올리는” 게 아니라, 캐시해야 할 대상의 총량을 줄여 결과적으로 히트율과 지연을 개선합니다.

운영 체크리스트: 튜닝 전후로 반드시 비교할 것

Buffer Pool 튜닝은 “지표가 좋아졌는지”를 객관적으로 확인해야 합니다.

  1. 구간 히트율: read_requests 대비 reads 비율
  2. 물리 읽기 IOPS/await: OS 디스크 지표
  3. p95/p99 쿼리 지연, TPS
  4. 쓰기 대기: Innodb_log_waits, Innodb_os_log_fsyncs
  5. 메모리 여유: OOM risk(컨테이너라면 cgroup 제한 포함)

가능하면 변경 전후 30분~수시간의 동일 트래픽 구간을 비교하세요.

예시: “히트율 98%”가 왜 큰 문제일 수 있나

히트율 98%는 숫자로는 좋아 보이지만, 트래픽이 큰 경우 물리 읽기가 폭발합니다.

  • 논리 읽기 1초당 500,000번
  • 히트율 98%면 물리 읽기 1초당 10,000번

랜덤 읽기 10,000 IOPS는 디스크/스토리지 계층에 따라 바로 병목이 될 수 있고, 그 대기가 곧 쿼리 지연으로 나타납니다. 반대로 99.9%면 물리 읽기는 500 IOPS 수준으로 떨어져 체감이 확 달라집니다.

마무리: 히트율은 “결론”이 아니라 “출발점”

InnoDB Buffer Pool 튜닝에서 히트율은 매우 유용하지만, 단독으로 결론을 내리면 위험합니다.

  • 히트율이 낮고 물리 읽기/디스크 대기가 함께 증가한다면: Buffer Pool 확장 또는 워킹셋 축소가 우선
  • 히트율이 높은데 느리다면: 락/쿼리 플랜/쓰기 플러시/스캔 오염 등 다른 병목을 의심

결국 목표는 “히트율 몇 %”가 아니라, 물리 I/O를 줄여 지연을 낮추고, 피크에서도 안정적인 p99를 만드는 것입니다.