Published on

MySQL 8 대용량 트래픽 - 버퍼풀·Redo 로그 튜닝

Authors

대용량 트래픽에서 MySQL 8 성능 병목은 대체로 두 축에서 터집니다. 하나는 읽기 성능의 심장인 InnoDB 버퍼풀(캐시 미스가 늘면 디스크 I/O 폭발), 다른 하나는 쓰기 내구성과 처리량을 좌우하는 Redo 로그(체크포인트/플러시 압박으로 스톨 발생)입니다.

이 글은 “버퍼풀을 키우면 빨라진다” 수준이 아니라, 어떤 지표가 어떤 병목을 의미하는지, 그리고 어떤 순서로 값을 바꿔야 안전한지를 MySQL 8 기준으로 정리합니다.

운영 환경에서 설정 변경은 반드시 점진적으로 적용하고, 변경 전후로 동일한 부하 패턴에서 지표를 비교하세요.

1) 먼저 병목을 분류하기: 읽기 캐시 문제 vs 쓰기 로그 문제

튜닝은 “증상”을 “원인”으로 분류하는 것부터 시작합니다.

읽기 병목(버퍼풀 부족)에서 자주 보이는 증상

  • QPS는 높은데 p95 응답시간이 점점 늘고, 디스크 읽기 IOPS가 같이 상승
  • 동일 쿼리를 반복하는데도 히트율이 낮거나, 워킹셋이 메모리에 못 올라감
  • SHOW ENGINE INNODB STATUS 에서 버퍼풀 관련 대기/읽기 증가

쓰기 병목(Redo 로그/체크포인트 압박)에서 자주 보이는 증상

  • 쓰기 TPS가 특정 지점에서 급격히 꺾이면서 지연이 튀는 스파이크
  • fsync 또는 로그 플러시 대기가 늘어남
  • 체크포인트가 따라가지 못해 “로그가 꽉 차서” 스톨에 가까운 현상

아래 명령으로 기본 지표를 수집해 두면 튜닝 전후 비교가 쉽습니다.

-- 전역 상태 스냅샷
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
SHOW GLOBAL STATUS LIKE 'Innodb_data%';
SHOW GLOBAL STATUS LIKE 'Innodb_log%';
SHOW GLOBAL STATUS LIKE 'Innodb_os_log%';

-- InnoDB 엔진 상태(텍스트지만 힌트가 많습니다)
SHOW ENGINE INNODB STATUS\G

-- 설정 확인
SHOW VARIABLES WHERE Variable_name IN (
  'innodb_buffer_pool_size',
  'innodb_buffer_pool_instances',
  'innodb_log_file_size',
  'innodb_log_files_in_group',
  'innodb_redo_log_capacity',
  'innodb_flush_log_at_trx_commit',
  'sync_binlog',
  'innodb_io_capacity',
  'innodb_io_capacity_max',
  'innodb_flush_neighbors'
);

2) InnoDB 버퍼풀: “크기”보다 중요한 건 “안정적인 캐시 동작”

버퍼풀은 데이터/인덱스 페이지를 캐시하는 영역입니다. 대용량 트래픽에서 버퍼풀이 작으면 디스크 랜덤 읽기가 늘고, 그 순간부터 지연이 비선형으로 증가합니다.

2-1) 버퍼풀 크기 산정: 워킹셋을 메모리에 올리는 게 목표

일반적인 전용 DB 서버라면 다음이 출발점입니다.

  • DB 전용(다른 서비스 거의 없음): RAM의 약 60% ~ 75%
  • OS 페이지 캐시, 커넥션/스레드, 정렬/조인 버퍼, 복제 버퍼 등을 고려해 여유를 남김

예시:

[mysqld]
innodb_buffer_pool_size = 48G

주의할 점:

  • 버퍼풀을 과도하게 키우면 OS 캐시가 줄어들고, 메모리 압박 시 스왑/메모리 회수로 오히려 지연이 커질 수 있습니다.
  • 컨테이너 환경이라면 cgroup 메모리 한도를 기준으로 계산하세요.

2-2) 버퍼풀 인스턴스: 락 경합을 줄이되, 너무 잘게 쪼개지 말기

버퍼풀이 큰데도 동시성이 높은 환경에서 경합이 보이면 인스턴스를 늘리는 게 도움이 됩니다.

  • 경험칙: 인스턴스당 최소 수 GB 이상이 되도록 구성
[mysqld]
innodb_buffer_pool_size = 64G
innodb_buffer_pool_instances = 8

너무 많은 인스턴스는 관리 오버헤드가 생길 수 있어, 코어 수와 워크로드를 보며 조정합니다.

2-3) 히트율만 보지 말고 “페이지 읽기 패턴”을 같이 보세요

히트율은 높아도 지연이 큰 경우가 있습니다. 예를 들어, 특정 쿼리가 큰 범위를 스캔하거나, 버퍼풀에 올릴 수 없는 워킹셋이 계속 들어오면 “캐시가 열심히 교체되는 상태”가 됩니다.

다음 지표를 함께 봅니다.

  • 논리 읽기 대비 물리 읽기 비율
  • 버퍼풀 free 페이지가 거의 없고 교체가 과도한지
  • Innodb_buffer_pool_reads 증가 속도
-- 히트율 근사(샘플링해서 델타로 계산하는 방식 권장)
-- hit_rate = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';

2-4) 버퍼풀 워밍업과 재시작 비용 줄이기

대용량 트래픽 서비스에서 재시작 직후 콜드 캐시는 큰 장애 요인이 됩니다.

MySQL 8은 버퍼풀 덤프/로드 기능을 제공합니다.

[mysqld]
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON

재시작이 잦거나 롤링 배포가 있는 환경이라면 체감 효과가 큽니다.

3) Redo 로그: “로그가 작아서” 생기는 스톨을 막는 게 핵심

Redo 로그는 InnoDB가 변경 사항을 먼저 기록하는 로그입니다. 쓰기 트래픽이 많으면 Redo 로그와 체크포인트가 병목이 됩니다.

3-1) MySQL 8에서 Redo 로그 용량 설정: innodb_redo_log_capacity

MySQL 8.0.30 이후에는 innodb_log_file_size 대신 innodb_redo_log_capacity 사용이 권장되는 흐름입니다(버전에 따라 동작이 다를 수 있으니 현재 버전을 확인하세요).

  • 용량이 너무 작으면 체크포인트가 자주 발생하고, 로그 공간 부족으로 쓰기 스톨이 발생
  • 너무 크면 크래시 리커버리가 길어질 수 있음

예시:

[mysqld]
innodb_redo_log_capacity = 8G

버전/환경에 따라 다음 조합을 쓰는 경우도 있습니다.

[mysqld]
innodb_log_file_size = 2G
innodb_log_files_in_group = 4

이 경우 총 Redo 로그 용량은 innodb_log_file_size 곱하기 innodb_log_files_in_group 입니다.

3-2) innodb_flush_log_at_trx_commitsync_binlog: 내구성 비용을 숫자로 이해하기

대용량 쓰기에서 가장 흔한 선택지는 다음 3가지입니다.

  • innodb_flush_log_at_trx_commit = 1: 트랜잭션 커밋마다 Redo를 디스크에 플러시. 가장 안전하지만 fsync 비용 큼
  • innodb_flush_log_at_trx_commit = 2: 커밋 시 OS 버퍼까지만, 디스크 플러시는 주기적으로. 장애 시 최근 1초 내 데이터 유실 가능
  • innodb_flush_log_at_trx_commit = 0: 주기적 플러시. 유실 가능성 더 큼

바이너리 로그 동기화도 같이 봐야 합니다.

  • sync_binlog = 1: binlog도 매 커밋마다 동기화
  • sync_binlog = 0 또는 큰 값: 성능은 좋아지나 유실 가능성 증가

예시(타협안으로 자주 쓰이는 구성):

[mysqld]
innodb_flush_log_at_trx_commit = 2
sync_binlog = 1

이 조합은 InnoDB 레벨에서는 일부 유실 가능성이 있지만, binlog는 상대적으로 안전하게 가져가려는 선택입니다. 다만 복제/장애복구 전략에 따라 정답이 달라집니다.

3-3) 체크포인트 압박 완화: I/O capacity 설정

체크포인트가 밀리면 백그라운드 플러시가 따라가지 못하고, 결국 포그라운드(사용자 쿼리)가 디스크 쓰기를 떠안으면서 지연이 폭발합니다.

스토리지 성능에 맞춰 다음을 조정합니다.

[mysqld]
innodb_io_capacity = 2000
innodb_io_capacity_max = 6000
  • SSD/NVMe에서는 기본값이 너무 보수적인 경우가 많습니다.
  • 너무 높이면 백그라운드가 과도하게 디스크를 점유해 읽기 지연이 커질 수 있으니, 실제 디바이스 IOPS와 함께 튜닝합니다.

3-4) innodb_flush_neighbors: SSD에서는 보통 끄는 게 유리

인접 페이지까지 함께 플러시하는 최적화는 HDD 시대의 유산입니다. SSD에서는 불필요한 쓰기를 늘릴 수 있습니다.

[mysqld]
innodb_flush_neighbors = 0

4) 실전 튜닝 절차: “큰 값 한 번에”가 아니라 “지표 기반으로 단계적”

운영에서 안전하게 적용하기 위한 순서를 권장합니다.

4-1) 1단계: 관측부터 고정

  • 피크 시간대의 QPS, TPS, p95/p99 latency
  • 디스크 read/write IOPS, await, util (예: iostat)
  • InnoDB 상태 지표 델타(5분 단위)

4-2) 2단계: 버퍼풀로 읽기 병목부터 줄이기

  • 버퍼풀이 명백히 부족하면 innodb_buffer_pool_size 를 점진적으로 증가
  • 재시작이 필요할 수 있으므로, 워밍업 옵션도 함께 고려

4-3) 3단계: Redo 로그 용량과 플러시 정책으로 쓰기 스톨 제거

  • 체크포인트/로그 공간 부족이 의심되면 Redo 로그 용량을 늘림
  • 내구성 요구사항에 따라 innodb_flush_log_at_trx_commitsync_binlog 를 재검토

4-4) 4단계: I/O capacity로 백그라운드 플러시를 스토리지에 맞추기

  • NVMe인데도 플러시가 밀린다면 innodb_io_capacity 가 낮을 가능성이 큼
  • 반대로 읽기 지연이 늘면 너무 공격적일 수 있음

5) 변경 예시: 고트래픽 OLTP 서버의 현실적인 설정 샘플

아래는 “전용 DB 서버, NVMe, OLTP 중심”을 가정한 예시입니다. 그대로 복붙이 아니라, 지표를 보고 조정해야 합니다.

[mysqld]
# Buffer Pool
innodb_buffer_pool_size = 64G
innodb_buffer_pool_instances = 8
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON

# Redo Log
innodb_redo_log_capacity = 8G

# Durability vs throughput trade-off
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

# Flushing
innodb_io_capacity = 3000
innodb_io_capacity_max = 9000
innodb_flush_neighbors = 0

만약 “초당 수만 커밋”에서 fsync 가 병목이라면, 내구성 요구사항을 합의한 뒤 다음처럼 타협할 수 있습니다.

[mysqld]
innodb_flush_log_at_trx_commit = 2
sync_binlog = 1

6) 자주 하는 실수와 체크리스트

실수 1: 버퍼풀만 키우고 쓰기 스톨은 방치

읽기 성능은 좋아져도 쓰기에서 체크포인트가 밀리면 전체 지연이 튑니다. 특히 트래픽이 커질수록 “평소엔 괜찮다가 특정 순간에만 멈추는” 형태로 나타납니다.

실수 2: Redo 로그를 과도하게 키워 복구 시간을 무시

Redo 로그가 크면 리커버리 시간이 늘 수 있습니다. 장애 복구 목표 시간과 함께 결정해야 합니다.

실수 3: 컨테이너 메모리 한도에서 버퍼풀을 과하게 설정

OOM 또는 커널의 강제 종료로 이어질 수 있습니다. 컨테이너 환경에서는 메모리 한도 내에서 안전 마진을 크게 잡으세요.

체크리스트

  • 워킹셋이 버퍼풀에 들어오는가
  • 디스크 읽기 IOPS가 “캐시 미스” 때문인지 “풀스캔” 때문인지
  • 쓰기 지연 스파이크가 Redo 로그/체크포인트 압박과 연관되는지
  • 스토리지 특성(SSD/NVMe/HDD)에 맞게 플러시 설정을 했는지

7) 운영 관점: 장애 징후를 빨리 잡는 방법

튜닝은 한 번으로 끝나지 않습니다. 트래픽/데이터가 커지면 다시 병목이 이동합니다. 따라서 “빨리 감지”가 중요합니다.

  • fsync 대기 증가, 디스크 await 상승을 알람으로 설정
  • Innodb_buffer_pool_reads 증가율 급등 감지
  • p95/p99 지연이 튀는 구간에서 Redo 관련 상태값 델타를 함께 저장

운영 자동화/진단 루틴을 만들 때는 쉘 스크립트의 예외 처리도 중요합니다. 관련해서는 bash set -euo pipefail 함정과 안전한 예외처리 글이 도움이 됩니다.

또한 DB를 쿠버네티스에서 운영하면서 재시작/불안정이 잦다면 원인 분류가 먼저입니다. Kubernetes CrashLoopBackOff 원인 12가지와 진단 도 함께 참고하세요.

마무리

MySQL 8 대용량 트래픽 튜닝에서 InnoDB 버퍼풀은 “읽기 지연의 바닥”을 만들고, Redo 로그는 “쓰기 스톨의 천장”을 올립니다. 핵심은 두 영역을 분리해서 관측하고, 지표 기반으로 단계적으로 조정하는 것입니다.

다음 단계로는 쿼리 패턴(핫 키, 대량 업데이트, 인덱스 설계), 복제 지연, binlog 설정, 스토리지 레이아웃까지 확장해 전체 병목 지도를 그리면 튜닝의 정확도가 크게 올라갑니다.