핵심 한 줄
Load 시리즈는 한 대에서 결제 취소 시스템을 쥐어짰다. 병목이 CPU도 홉도 아닌 **취소 1건당 커밋 수(fsync)**임을 규명했고, 이력 커밋을 줄여 147→220rps까지 올렸다. 한 대의 천장은 DB 커밋이 정한다 — 거기까지가 Load의 결론이다. 이제 두 질문이 남았다: 여러 대로 늘리면 안전한가, 그리고 정말 빨라지나.
Scale 시리즈 — k3s 수평 스케일아웃
1. Load가 어디까지 갔나
Load 22편을 끝내고 돌아보면, 수직 실측은 두 단계로 진행됐다.
1막(01~11편): AWS에 서버·DB·부하생성기를 분리해 판을 깔고, 취소 시스템에 부하를 걸었다. 코드 리뷰가 “옳다”고 통과시킨 분산락이 한 가맹점에 부하가 몰리는 순간 취소의 99.9%를 거부했다. 데드락인 줄 알았다 — 대기 없이 거부하는 분산락이었다. DB 원자 UPDATE로 교체하자 99.9% 거부가 0%로 내려갔다.
2막(12~15편): 병목 위치가 틀렸다는 걸 관측으로 재규명했다. “동기 홉(risk 호출)이 185 천장의 원인”이라 결론냈는데 — USE 인구조사를 돌리자 두 번 뒤집혔다. risk 홉은 21ms로 무죄였고, 풀 크기도 이미 2코어 DB 공식 상한이었다. 진짜 정체는 취소 1건당 커밋 6개(fsync). 이력 3커밋을 1로 묶자 147→220rps로 올랐다. open-model SLO로 무릎 ~190rps도 쟀다.
요컨대 Load의 결론은 이것이다.
한 대의 천장은 DB fsync가 정한다. 그리고 그 천장을 관측 없이 추측하면 틀린다.
2. 남은 질문 둘
Load는 단일 토폴로지(payment 1대, DB 1대)를 전제했다. 그 전제를 걷으면 질문이 두 개 생긴다.
첫째, 안전한가. 파드가 N개로 늘면, 한 대일 때는 숨어 있던 동시성 결함이 새로 얼굴을 내민다. 여러 파드가 같은 취소 요청을 동시에 처리하면 이중취소가 생기나? 같은 Kafka 이벤트가 두 번 발행되나? 스케줄러가 여러 파드에서 동시에 돌면 중복 재처리가 터지나? Load에서 다진 정합성 보장이 오케스트레이션 레이어에서도 살아남는지 — 이걸 먼저 따진다.
둘째, 정말 빨라지나. Load가 규명한 천장은 “DB커밋 수”였다. 파드를 N개로 늘려도 DB는 그대로다. replica를 늘리면 처리량이 오를까, 아니면 모두 같은 DB 앞에서 막힐까? 직관은 “당연히 빨라진다”고 말한다. 2막에서 그 직관이 뒤집힌다 — 지금은 스포일러만.
3. 왜 k3s인가
관리형 쿠버네티스(EKS, GKE)를 쓰면 오케스트레이션 내부가 블랙박스가 된다. 스케줄링이 어떻게 결정되는지, 롤링 업데이트 중에 어떤 순서로 파드가 교체되는지, 노드가 죽으면 무슨 일이 일어나는지 — API로는 안 보인다.
k3s는 경량 쿠버네티스다. 단일 바이너리, etcd 대신 SQLite(또는 내장 데이터스토어), 컨트롤 플레인까지 노드 위에서 직접 돌아간다. 설치가 curl 한 줄이고, 내부가 열려 있다. 여기서 “자립 리그”를 깐다는 건 — 관리형 서비스 없이, 내가 만든 인프라 위에서 오케스트레이션을 직접 실측한다는 뜻이다.
DB는 클러스터 밖에 고정한다. Load에서 규명한 병목(DB fsync)을 통제변수로 묶어두기 위해서다. 파드를 늘렸을 때 무엇이 변하고 무엇이 그대로인지 — DB를 클러스터 밖에 두면 그 경계가 선명하게 보인다.
실측 환경
Load 시리즈와 동일하게 AWS EC2에 올린다. k3s 컨트롤 플레인 1대 + 워커 노드 N대. DB는 클러스터 외부 단일 인스턴스 고정. 부하생성기는 별도 분리.
4. 이 시리즈의 지도
| 편 | 무엇을 | 핵심 질문 |
|---|---|---|
| 01 | k3s 클러스터 구성 | AWS에 k3s를 까는 데 어디서 막히나 |
| 02 | 정합성 검증 | replica N개에서 이중취소·중복발행 실제로 발생하나 |
| 03 | 자가치유(HA) | 노드 하나가 죽으면 취소 요청이 어떻게 되나 |
| 04 | 롤링 업데이트 함정 | 롤링 중 in-flight 취소가 어떻게 처리되나 |
| 05 | 처리량 반전 | replica 3개가 처리량을 올렸다 — 정말? |
| 06 | 병목 재규명 | 처리량이 안 오른 진짜 이유 |
| 07 | 오퍼레이터 관점 | 쿠버네티스가 숨긴 운영 비용 |
| 08 | 설계 근거 | 각 YAML 줄이 왜 그렇게 써 있나 |
1막은 안전하게 늘리는 것을 다진다. 빠르게 늘리는 게 아니다 — 취소 시스템에서 이중차감 한 번이 “느려서 못 쓰는 것”보다 나쁘다. 2막은 그렇게 안전하게 늘린 다음에야 처리량 질문을 꺼낸다.
그리고 2막 첫 결과가 예상을 깬다. “replica가 천장을 올렸다”는 첫 숫자가 통째로 뒤집힌다. 원인 진단에 네 단계가 걸린다.
Load를 안 읽었다면
이 시리즈는 Load 00편부터 시작하는 게 자연스럽다. k3s를 쓰는 이유가 “k3s가 좋아서”가 아니라 “Load에서 규명한 천장을 수평 축에서 다시 만나기 위해서”이기 때문이다.
한 줄
Load에서 배운 건 측정이 추측을 이긴다는 것이었다. 그걸 들고 수평으로 간다. 다음 편(01편)에서 리그를 까는 순간부터 첫 번째 벽을 만난다.