핵심 한 줄

“DB니까 크게 잡자”는 본능이 실측을 망친다. 메모리 큰 인스턴스(r7g)를 DB에 쓰면 버퍼풀이 I/O 병목을 덮어 knee가 안 보인다. 반대로 부하생성기를 작게 잡으면 그게 먼저 터진다. 사이징은 비용 문제가 아니라 무엇이 먼저 병목이 되게 할 것인가의 문제다.

시리즈 — 부하가 드러낸 설계

1막 · 실측으로 잡고 증명하다 00 · 01 · 02 · 03 · 04 · 05 · 06 · 07 · 08 · 09 · 10 · 11 2막 · 관측을 코드로, 코드를 재측정으로 12 · 13 · 14 · 15


1. “DB니까 크게”

처음 인스턴스를 고를 때, DB에는 반사적으로 큰 걸 골랐다. r7g.large — 메모리 최적화, vCPU당 8GB, 16GB짜리. “DB는 메모리가 많아야 빠르지”라는 상식대로.

그런데 이건 버려도 되는 부하테스트용 DB다. 시딩 데이터는 payment 수만 건. working set이 크지 않다. 여기에 16GB를 붙이면 무슨 일이 일어나나?

2. 반전 — 병목을 덮는다

InnoDB는 버퍼풀에 데이터를 캐싱한다. 버퍼풀이 working set보다 크면, 읽기가 디스크로 안 내려간다. 전부 메모리에서 처리된다.

부하 테스트의 목적은 knee(무릎) — 성능이 비선형으로 꺾이는 지점을 찾는 것이다. DB I/O는 그 knee의 유력한 원인 중 하나다. 그런데 버퍼풀이 다 덮어버리면?

큰 DB는 거짓 초록을 준다

“부하를 올려도 DB가 멀쩡하다”는 결과가 나온다. 하지만 그건 DB가 튼튼해서가 아니라 working set이 메모리에 다 올라가서 I/O 병목이 안 나타난 것뿐이다. prod에서 데이터가 커지면 그때 터진다. 실측이 그걸 미리 못 보게 만든 셈이다.

그래서 DB를 일부러 작게 잡았다. r7g.large(16GB) → m7g.large(8GB), 콜드 DB는 c7g.large(4GB)까지. 버퍼풀이 병목을 덮지 않게 해서, I/O가 정직하게 드러나도록.

3. Graviton 패밀리, 문제 모양에 맞게

AWS Graviton(ARM) 패밀리는 vCPU당 RAM으로 갈린다.

패밀리성격vCPU당 RAM
c7gcompute 최적화2 GB
m7g범용4 GB
r7g메모리 최적화8 GB
t4g버스터블(크레딧)1 GB

핵심은 역할별로 다르게 잡는 것이다.

  • 핫패스(payment·risk)는 c7g.xlarge 유지 — 측정의 주인공이다. 얘가 먼저 포화돼야 knee가 의미 있다. 여기까지 작게 잡으면 병목이 엉뚱한 데로 옮겨간다.
  • DB는 m7g/c7g로 하향 — 버퍼풀이 I/O를 덮지 않게.
  • 부하생성기(k6)는 넉넉히 — 1편의 0번 원칙. 생성기가 병목이 되면 앱의 한계를 못 본다.

4. 관측은 반대로 — 키운다

한 대는 오히려 키웠다. obs(Prometheus+Grafana)를 t4g.small(2GB) → t4g.medium(4GB)로. 9개 타깃의 히스토그램을 스크레이프하는데 2GB로는 버겁고, 버스터블 CPU 크레딧이 마르면 스크레이프가 끊긴다. 관측 스택이 죽으면 실측 데이터를 통째로 잃는다 — 측정보다 관측을 먼저 지켜야 한다.

여기에 함정이 하나 더 있었다. obs를 Spot으로 잡았더니 t4g.medium Spot 용량이 없어 인스턴스가 안 떴다(이건 4편에서 다시 나온다). 그래서 obs만 온디맨드로 예외 처리했다 — Spot 용량은 타입×AZ 풀 단위라, 한 타입이 마르면 그것만 온디맨드로 빼는 게 답이다.


정리하면: 과하게 잡으면 병목을 덮고, 부족하게 잡으면 생성기가 병목이 된다. 사이징은 비용 절감이자 동시에 측정 신뢰도다. 이렇게 판을 정직하게 깔고 나서야, 다음 문제 — 이 서버들을 어떻게 띄우고 배포할 것인가 — 로 넘어간다.