Zettelkasten

RDS 다운사이징 전에 buffer pool을 점진적으로 낮춰 핫셋이 작은 pool에 맞는지 실측한다

·수정 4

요약

  • RDS의 innodb_buffer_pool_size는 워크로드가 키운 게 아니라 인스턴스 RAM의 75%로 자동 설정된 상한이다. "buffer pool이 꽉 찼다"는 다운사이징 블로커가 아니다.
  • 다운사이징 안전성의 진짜 기준은 "핫 워킹셋이 더 작은 pool에 들어가는가"이고, 이건 같은 인스턴스에서 pool을 점진적으로 낮추며 ReadIOPS/ReadLatency로 실측한다.
  • 인스턴스 RAM ≥ 안전 최소 pool + 비-pool 오버헤드(perf_schema + 커넥션 + OS) + 마진. 이 식으로 pool 실측값을 인스턴스 클래스로 환산한다.

본문

buffer pool 사용량은 수요가 아니라 설정값이다. InnoDB 버퍼 풀은 innodb_buffer_pool_size미리 잡는 고정 한도다. RAM이 남는다고 자동으로 커지지 않으며(엔진 기본값은 128MB), "빈 메모리를 알아서 끌어다 쓴다"는 건 InnoDB가 아니라 OS(리눅스) 페이지 캐시의 동작이다. RDS는 파라미터 그룹에서 이 값을 {DBInstanceClassMemory*3/4}(RAM의 75%)로 설정해 두므로 64GiB 인스턴스면 약 48GiB가 잡힌다. 버퍼 풀은 데이터가 한도보다 크면 그 한도까지 LRU로 hot 페이지를 비우지 않고 거의 항상 꽉 채운다. 따라서 데이터가 pool보다 훨씬 큰 환경에선 pool이 47GB든 18GB든 똑같이 "꽉 찬" 상태가 되고, "pool이 찼다"는 필요 메모리에 대한 신호가 아니다. CPU 사용률이 낮은데도(예: 피크 15~19%) 메모리 때문에 다운사이징을 못 한다는 인식은 이 오해에서 나온다.

다운사이징의 진짜 제약은 핫 워킹셋 크기다. 전체 데이터가 pool의 수십 배여도(예: 데이터 수백 GB, pool 48GB) 자주 읽히는 핫셋만 pool에 들어가면 디스크 읽기가 거의 없다. 판단 지표:

  • ReadIOPS — 데이터가 거대한데도 평균 수십 수준이면 핫셋이 pool보다 훨씬 작다는 직접 증거. pool을 줄였을 때 이 값이 튀는 지점이 floor.
  • ReadLatency — 평탄하면 I/O 압박 없음.
  • buffer pool 히트율(Innodb_buffer_pool_reads/read_requests) — 누적값이라 "현재 충분함"만 알려주고 "더 줄여도 되는지"는 못 알려준다. 그래서 CloudWatch 실시간 지표(ReadIOPS)가 결정적.
  • 일배치 등 주기적 read 피크 시간대를 반드시 관찰 창에 포함해야 한다. cold 데이터를 끌어오는 그 순간이 합격/불합격을 가른다.

검증은 인스턴스 변경 전에 같은 머신에서 in-place로 한다. innodb_buffer_pool_size는 MySQL 8.0에서 dynamic 변수라 온라인 리사이즈된다. RDS에서도 ApplyType이 dynamic이라 재부팅 없이 즉시 적용/원복 가능하다 (단 전제: 인스턴스가 default가 아닌 커스텀 파라미터 그룹을 쓰고 있어야 함. default 그룹은 수정 불가, 새 그룹 attach는 재부팅 유발).

# 점진적 축소 (off-peak에 한 단계씩, 단계당 최소 하루 — 배치 포함)
aws rds modify-db-parameter-group \
  --db-parameter-group-name <custom-pg> \
  --parameters "ParameterName=innodb_buffer_pool_size,ParameterValue=42949672960,ApplyMethod=immediate"
# 40GiB=42949672960, 32GiB=34359738368, 24GiB=25769803776, 18GiB=19327352832, 16GiB=17179869184

# 원복 (자동 75%로, 역시 dynamic)
#   ParameterValue={DBInstanceClassMemory*3/4}

pool 실측값 → 인스턴스 클래스 환산. in-place 테스트는 64GB 머신에서 하므로 "핫셋이 X GB pool에 맞는가"만 검증하고, "줄인 인스턴스의 RAM이 오버헤드를 감당하는가"는 검증하지 못한다(pool 줄이면 FreeableMemory만 늘어남). 후자는 계산으로 메운다:

필요 인스턴스 RAM ≥ 안전 최소 pool + 비-pool 오버헤드 + 여유 마진

비-pool 오버헤드 = 사용 메모리 - buffer pool = perf_schema(접속 host/account가 많으면 by_host/by_account 요약 테이블이 비대해져 수 GB) + 커넥션당 버퍼 + temptable + OS. 이 오버헤드는 인스턴스를 줄여도 거의 안 줄기 때문에, 작은 인스턴스에서 75% 자동 공식을 그대로 쓰면 pool + 오버헤드 > RAM이 되어 swap/OOM 위험이 생긴다. 그래서 작은 인스턴스에선 75%에 맡기지 말고 pool을 수동으로 캡한다.

표준 RDS 인스턴스 RAM이 16/32/64 GiB로 띄엄띄엄이라(48GB 중간 클래스 없음), 48→40→32 같은 중간 pool 테스트는 "여유 확인용"일 뿐 실제 다운사이징을 증명하지 못한다. 다운사이징을 증명하는 건 목표 인스턴스가 오버헤드 빼고 줄 수 있는 pool 크기(예: 32GB 인스턴스 → 오버헤드 ~11GB 빼면 pool ~18GB)까지 내려서 ReadIOPS가 평탄한지 확인하는 구간이다.

주의 1: pool shrink는 InnoDB가 dirty page를 flush/evict하느라 적용 중 순간 CPU/IO 부하와 짧은 stall이 생길 수 있으니 off-peak에 한다.

주의 2 (노드 선택): 읽기가 read replica로 분산돼 있으면 master에서만 테스트하면 안 된다. master는 읽기가 적어 pool을 줄여도 ReadIOPS가 잘 안 튀지만, 실제 읽기 부하는 replica가 받으므로 읽기를 받는 노드에서도 같은 in-place 축소 테스트를 따로 해야 한다.

관련 노트

참고