핵심 한 줄

정합성은 지킬 수 있다 — 이중차감 0, 멱등 100%. 그런데 그 옳은 설계가 한 가맹점에 부하가 몰리는 순간 취소의 99.9%를 거부한다. 코드 리뷰도, 단위테스트도 전부 초록인데. 이 시리즈는 그걸 실측으로 드러내고, 한 줄까지 진단하고, 원자 UPDATE로 고치고, 다시 실측으로 증명한다 — 99.9% 거부에서 0%까지.

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

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


1. 문제 — “테스트는 다 초록인데”

취소 시스템의 정합성은 코드로 지킬 수 있다. request_hash UNIQUE로 따닥을 막고, cancelRequestId 원장으로 이중 한도차감을 막는다. 단위테스트가 증명한다 — 같은 요청이 두 번 와도 딱 한 번 처리된다.

여기까지는 코드 리뷰와 단위테스트로 검증되는 세계다. 정확성은 눈에 보인다.

그런데 정확성 옆에는 눈에 안 보이는 축이 하나 더 있다. 가용성이다. “이 설계가 정확한가”는 코드를 읽으면 안다. 하지만 “이 설계가 부하에서도 취소를 받아주는가”는 코드를 읽어도 모른다. 그건 코드에 안 써 있다.

로컬 한 머신에선 절대 안 보인다

앱·DB·부하생성기가 한 노트북에 섞여 있으면, 서로의 CPU를 뺏느라 진짜 병목이 무엇인지 뭉개진다. 동시성 결함은 분리된 인프라 + 진짜 동시 부하라는 조건이 갖춰져야 비로소 얼굴을 내민다.

이 시리즈의 발단이 그거였다. 코드 리뷰를 통과하고, 단위테스트가 전부 초록인 취소 로직을 AWS에 분리해 올리고 부하를 걸었더니 — 10명이 동시에 눌렀을 뿐인데 7.63%가 실패했다. 그리고 그 부하를 한 가맹점에 몰자, 99.9%가 튕겼다. 코드엔 아무 문제가 안 써 있었다.

2. 논제 — 부하로 드러내고, 부하로 증명한다

카오스 벤치가 장애를 주입했다면, 이 시리즈는 자연 발생한 결함을 발굴한다. 그리고 거기서 멈추지 않는다. 한 줄 논제는 이것이다.

부하로 드러낸 것은, 부하로 고쳐졌음을 증명한다.

닫힌 루프다:

  1. 판을 깐다 — 서버·DB·부하생성기를 분리한 AWS 실측 환경. 병목이 로직인지 lock인지 네트워크인지 구별 가능하게.
  2. 드러낸다 — VU를 올리고 데이터 분포를 바꾸며, 코드 리뷰가 못 본 결함을 수치로 끌어낸다.
  3. 한 줄까지 진단한다 — “느려요”가 아니라 “ValidateAndReserveService L37, SET NX 락이 대기 없이 거부한다”까지.
  4. 고친다 — 설계로. 그리고 재측정한다. 단위테스트는 correctness(초과차감 0)를 증명하고, 실측은 증상 소멸(99.9%→0%)을 증명한다. **둘 다 있어야 “고쳤다”**고 말한다.

각 편은 이 패턴을 반복한다:

  1. 무엇을 쟀나 — 어떤 축(처리량/경합), 어떤 부하
  2. 부하가 드러낸 것 — 코드로는 안 보이던 수치
  3. 코드/설계로 이은 것 — 그 수치의 원인을 소스 한 줄까지, 그리고 수정

3. 지도

편무엇을부하가 드러낸 / 증명한 것
01실측 환경서버·DB를 안 나누면 “느린 게 로직인지 lock인지” 못 가른다
02인스턴스 사이징DB를 크게 잡으면 버퍼풀이 I/O 병목을 덮어 무릎이 안 보인다
03배포 모델빌드/실행 분리 — CI가 굽고, 서버는 필요할 때만 pull
04라이브 버그한글 SG description·SSM 문서명·개행 — 파이프라인의 실제 결함
05처리량의 정체190 rps = 10 VU ÷ 지연(Little’s law). 용량이 아니다. 풀은 30초에 마른다
06baseline저부하인데 실패 — 코드 리뷰가 못 본 결함의 첫 신호
07축B 경합분산의 집계가 숨긴 진실. 한 가맹점 = 99.9% 거부
08오진데드락이 아니었다. 기다리지 않는 분산락(fail-fast)이 경합을 거부로 바꾼다
09해결락을 걷고 DB 원자 조건부 UPDATE로. 재측정: 99.9% → 0%
10관측로그를 CLI로 캐던 걸 Grafana 한 화면으로
11조기최적화캐시를 껐는데 무사 — merchant-limit를 느리게 하자 15배 붕괴

이 시리즈의 클라이맥스는 8→9편이다. “데드락인 줄 알았다” 에서 진짜 원인(대기 없는 분산락)을 만나고, “거절을 대기로 바꿨다” 에서 경합의 대가를 가용성에서 지연으로 옮긴다 — 요청이 튕기는 대신 잠깐 줄 서서 전부 성공한다. 그리고 그게 진짜 고쳐졌는지는, 다시 부하를 걸어 확인한다.

코드 리뷰가 “옳다”고 말한 설계의 대가를, 부하가 청구서로 보여준다. 그 청구서를 읽는 법 — 그게 이 시리즈다.