핵심 한 줄
정합성은 지킬 수 있다 — 이중차감 0, 멱등 100%. 그런데 그 옳은 설계가 한 가맹점에 부하가 몰리는 순간 취소의 99.9%를 거부한다. 코드 리뷰도, 단위테스트도 전부 초록인데. 이 시리즈는 그걸 실측으로 드러내고, 한 줄까지 진단하고, 원자 UPDATE로 고치고, 다시 실측으로 증명한다 — 99.9% 거부에서 0%까지.
시리즈 — 부하가 드러낸 설계
1. 문제 — “테스트는 다 초록인데”
취소 시스템의 정합성은 코드로 지킬 수 있다. request_hash UNIQUE로 따닥을 막고, cancelRequestId 원장으로 이중 한도차감을 막는다. 단위테스트가 증명한다 — 같은 요청이 두 번 와도 딱 한 번 처리된다.
여기까지는 코드 리뷰와 단위테스트로 검증되는 세계다. 정확성은 눈에 보인다.
그런데 정확성 옆에는 눈에 안 보이는 축이 하나 더 있다. 가용성이다. “이 설계가 정확한가”는 코드를 읽으면 안다. 하지만 “이 설계가 부하에서도 취소를 받아주는가”는 코드를 읽어도 모른다. 그건 코드에 안 써 있다.
로컬 한 머신에선 절대 안 보인다
앱·DB·부하생성기가 한 노트북에 섞여 있으면, 서로의 CPU를 뺏느라 진짜 병목이 무엇인지 뭉개진다. 동시성 결함은 분리된 인프라 + 진짜 동시 부하라는 조건이 갖춰져야 비로소 얼굴을 내민다.
이 시리즈의 발단이 그거였다. 코드 리뷰를 통과하고, 단위테스트가 전부 초록인 취소 로직을 AWS에 분리해 올리고 부하를 걸었더니 — 10명이 동시에 눌렀을 뿐인데 7.63%가 실패했다. 그리고 그 부하를 한 가맹점에 몰자, 99.9%가 튕겼다. 코드엔 아무 문제가 안 써 있었다.
2. 논제 — 부하로 드러내고, 부하로 증명한다
카오스 벤치가 장애를 주입했다면, 이 시리즈는 자연 발생한 결함을 발굴한다. 그리고 거기서 멈추지 않는다. 한 줄 논제는 이것이다.
부하로 드러낸 것은, 부하로 고쳐졌음을 증명한다.
닫힌 루프다:
- 판을 깐다 — 서버·DB·부하생성기를 분리한 AWS 실측 환경. 병목이 로직인지 lock인지 네트워크인지 구별 가능하게.
- 드러낸다 — VU를 올리고 데이터 분포를 바꾸며, 코드 리뷰가 못 본 결함을 수치로 끌어낸다.
- 한 줄까지 진단한다 — “느려요”가 아니라 “
ValidateAndReserveServiceL37,SET NX락이 대기 없이 거부한다”까지. - 고친다 — 설계로. 그리고 재측정한다. 단위테스트는 correctness(초과차감 0)를 증명하고, 실측은 증상 소멸(99.9%→0%)을 증명한다. **둘 다 있어야 “고쳤다”**고 말한다.
각 편은 이 패턴을 반복한다:
- 무엇을 쟀나 — 어떤 축(처리량/경합), 어떤 부하
- 부하가 드러낸 것 — 코드로는 안 보이던 수치
- 코드/설계로 이은 것 — 그 수치의 원인을 소스 한 줄까지, 그리고 수정
3. 지도
| 편 | 무엇을 | 부하가 드러낸 / 증명한 것 |
|---|---|---|
| 01 | 실측 환경 | 서버·DB를 안 나누면 “느린 게 로직인지 lock인지” 못 가른다 |
| 02 | 인스턴스 사이징 | DB를 크게 잡으면 버퍼풀이 I/O 병목을 덮어 무릎이 안 보인다 |
| 03 | 배포 모델 | 빌드/실행 분리 — CI가 굽고, 서버는 필요할 때만 pull |
| 04 | 라이브 버그 | 한글 SG description·SSM 문서명·개행 — 파이프라인의 실제 결함 |
| 05 | 처리량의 정체 | 190 rps = 10 VU ÷ 지연(Little’s law). 용량이 아니다. 풀은 30초에 마른다 |
| 06 | baseline | 저부하인데 실패 — 코드 리뷰가 못 본 결함의 첫 신호 |
| 07 | 축B 경합 | 분산의 집계가 숨긴 진실. 한 가맹점 = 99.9% 거부 |
| 08 | 오진 | 데드락이 아니었다. 기다리지 않는 분산락(fail-fast)이 경합을 거부로 바꾼다 |
| 09 | 해결 | 락을 걷고 DB 원자 조건부 UPDATE로. 재측정: 99.9% → 0% |
| 10 | 관측 | 로그를 CLI로 캐던 걸 Grafana 한 화면으로 |
| 11 | 조기최적화 | 캐시를 껐는데 무사 — merchant-limit를 느리게 하자 15배 붕괴 |
이 시리즈의 클라이맥스는 8→9편이다. “데드락인 줄 알았다” 에서 진짜 원인(대기 없는 분산락)을 만나고, “거절을 대기로 바꿨다” 에서 경합의 대가를 가용성에서 지연으로 옮긴다 — 요청이 튕기는 대신 잠깐 줄 서서 전부 성공한다. 그리고 그게 진짜 고쳐졌는지는, 다시 부하를 걸어 확인한다.
코드 리뷰가 “옳다”고 말한 설계의 대가를, 부하가 청구서로 보여준다. 그 청구서를 읽는 법 — 그게 이 시리즈다.