Docker Compose 만으로 1년 — Kubernetes 로 가지 않은 이유
3인 팀이 결제·정산 시스템을 1년간 Docker Compose만으로 운영한 이유를 수치로 분석. 쿼리 최적화로 응답 8초→0.4초, k8s 전환 ROI가 맞지 않은 실제 근거를 정리했다.
결제·정산 시스템에서 인프라 결정이 다른 이유
일반적인 웹 서비스와 결제·정산 시스템의 인프라 결정은 출발점이 다르다. 대부분의 서비스에서 다운타임이란 "사용자가 잠깐 불편한 것"이지만, 결제 시스템에서 배포 중 30초 공백은 그 시간에 들어온 PG사 웹훅이 유실되거나 중복 처리될 수 있다는 의미다. PG사 중 상당수는 웹훅을 단발로 전송하고 재시도 정책이 없거나 매우 짧다. 컨테이너가 재시작 중이었다면 그 트랜잭션은 영구 미처리로 남고, 이는 정산 오차→가맹점 민원→수동 대사 작업으로 직결된다.
그래서 우리가 인프라를 결정할 때 가장 먼저 따지는 질문은 "얼마나 많은 트래픽을 처리하는가"가 아니라 "장애나 배포 시 어떤 일이 벌어지는가, 그걸 얼마나 빠르게 파악하고 대응할 수 있는가"다. Kubernetes의 자동 복구와 롤링 업데이트는 분명 강력하다. 하지만 그 강력함을 올바르게 쓰려면 그만큼의 이해도와 운영 여력이 뒷받침돼야 한다. 작은 팀이 불완전하게 이해한 채 운영하는 k8s 클러스터는, 깊이 이해하고 운영하는 Docker Compose보다 결제 현장에서 훨씬 위험하다.
운영 복잡도의 숨겨진 비용 — 수치로 계산하면
Kubernetes 전환을 진지하게 검토했을 때, 우리가 계산한 건 클러스터 비용만이 아니었다. EKS 기준 컨트롤 플레인 요금은 약 월 $144에 노드 EC2가 추가된다. 이건 그나마 보이는 비용이다. 진짜 문제는 학습과 운영의 기회비용이었다.
k8s를 "쓸 줄 아는" 수준이 아니라 결제 시스템을 안전하게 운영할 수준까지 올리는 데 필요한 것들을 나열하면 — 배포 전략(Rolling/Blue-Green/Canary) 설계, RBAC 및 네트워크 폴리시, Helm 차트 관리, etcd 백업, 노드 업그레이드 사이클, 그리고 실제 장애 시 kubectl 만으로 원인을 추적하는 능력 — 업계 공통 경험치로 최소 34개월의 집중 학습이 필요하다. 3인 팀에서 엔지니어 1명이 4개월간 인프라에 집중한다는 건 제품 개발 인력이 33% 감소한다는 뜻이다. 우리 팀 기준으로는 피처 개발 약 810개를 포기하는 것과 맞바꾸는 셈이었다. 결제·정산 자동화 도메인은 비즈니스 로직이 빠르게 변해야 하는 곳이다. 이 손실은 계산이 맞지 않았다.
진짜 병목은 수평 확장으로 해결되지 않았다
Kubernetes의 핵심 가치 제안은 수평 확장이다. 트래픽이 몰리면 파드를 더 띄운다. 그런데 1년간 우리가 경험한 성능 병목을 돌아보면, 컨테이너를 더 늘리는 것으로 해결되는 경우가 없었다.
유형별로 정리하면 세 가지였다. 첫째, N+1 쿼리 문제. 월말 정산 집계 시 가맹점별로 루프 안에서 개별 쿼리가 발생해 DB 부하가 급증했다. 쿼리를 배치 로드 방식으로 재설계하자 응답 시간이 8초에서 0.4초로 줄었다. 컨테이너를 10개 더 띄워봤자 이 문제는 그대로였다. 둘째, 외부 PG API 동기 호출. 결제 검증마다 외부 API를 동기로 호출하던 구조를 큐 기반 비동기로 전환하자 처리량이 3배 올랐다. 셋째, 캐시 미적용. 가맹점 설정값을 매 요청마다 DB에서 읽던 걸 Redis 캐시로 전환해 전체 DB 쿼리의 약 40%를 제거했다. 이 세 가지 개선은 모두 코드 레벨과 아키텍처 레벨의 문제였다. 수평 확장에 최적화된 인프라를 갖추기 이전에, 먼저 단일 인스턴스가 효율적으로 동작하는지를 검증해야 한다. 우리는 아직 그 단계에 있다.
Docker Compose로 실제로 해결한 것들 — 구체적으로
"Compose로 안 되는 게 있지 않냐"는 질문을 받을 때마다 솔직하게 답한다. 불편했던 적은 있다. 그러나 "안 됐던" 적은 없었다.
배포 중 다운타임이 대표적인 사례였다. 초기에는 docker compose up -d 실행 시 컨테이너 재시작 동안 수십 초 공백이 발생했다. 앞서 말한 이유로 이건 결제 시스템에서 허용할 수 없는 상황이었다. 해결책은 Nginx upstream을 Blue에서 Green으로 전환하는 셸 스크립트를 직접 작성하는 것이었다. 신규 컨테이너 기동 → 헬스체크 통과 확인 → Nginx reload → 구 컨테이너 drain 후 종료, 이 흐름을 약 80줄짜리 스크립트로 구현했다. Kubernetes의 Rolling Update가 이걸 더 선언적으로 처리해주는 건 맞다. 하지만 80줄짜리 스크립트와 etcd + control plane + kubelet 전체 스택을 유지하는 비용을 같은 선상에 놓을 수 없다. 로그 분산 문제도 마찬가지였다. Compose 파일에 Fluent Bit 컨테이너를 추가하고 각 서비스 로그를 CloudWatch로 중앙 수집하는 설정을 구성하는 데 하루가 걸렸다. Kubernetes DaemonSet 기반 로그 수집이 더 체계적이지만, 우리가 필요한 기능은 이것으로 충분히 커버된다.
전환 기준 — 이 세 가지가 충족되면 간다
"언제 전환하냐"는 질문에는 명확하고 측정 가능한 기준이 있어야 한다. "팀이 더 커지면"이나 "언젠가는"은 기준이 아니다. 우리가 팀 내에 문서화해둔 전환 트리거는 세 가지다.
첫째, 인프라 전담 엔지니어를 별도로 둘 수 있을 만큼 팀이 성장했을 때. 구체적으로는 개발 인원 6명 이상, 혹은 DevOps 역할 전담자가 채용됐을 때다. 둘째, 수평 확장이 실제로 필요한 트래픽 수준에 도달했을 때. 우리 기준으로는 단일 인스턴스 최적화가 한계에 도달하고 초당 500건 이상의 결제 요청이 지속적으로 발생하는 시점이다. 셋째, Compose로 해결이 안 되는 구체적인 기술적 한계가 확인됐을 때. "불편하다"는 느낌이 아니라 "이 기능은 이 도구로는 불가능하다"는 명확한 판단이 내려졌을 때다. 이 세 기준이 문서화되어 있으면, 기술 선택이 감정이나 트렌드가 아닌 데이터로 결정된다. 반대로 이 기준 없이 "남들이 다 쓰니까"로 전환하는 건, 비용은 선불이고 혜택은 불확실한 베팅이다. 결제·정산 시스템에서 그 베팅의 리스크는 가맹점과 사용자가 직접 진다.
지금 당장 써먹을 수 있는 행동 지침
인프라 결정에서 중요한 건 "무엇을 쓰냐"가 아니라 "왜 쓰냐"와 "그 결정을 지탱하는 기준이 있냐"다. 우리 팀이 1년간 Compose를 유지하며 배운 것을 행동 가능한 형태로 정리한다.
① 현재 병목을 먼저 정확히 측정하라. "트래픽이 많아질 것 같다"는 예측이 아니라, 지금 느린 것이 무엇인지를 APM 또는 슬로우 쿼리 로그로 찍어내라. 수평 확장이 해결책인지, 쿼리 최적화가 해결책인지는 측정 이후에만 알 수 있다.
② 전환 트리거를 팀 내에 명문화하라. 인원 수, 트래픽 임계값, 기술적 한계 — 이 세 가지를 구체적인 숫자와 함께 문서에 박아두면 다음 번 "k8s 쓸 때 안 됐냐" 질문에 감정 소모 없이 답할 수 있다.
③ Compose를 쓴다면 배포 스크립트부터 강화하라. 무중단 배포, 헬스체크, 롤백 — 이 세 가지는 Kubernetes 없이도 구현 가능하다. 특히 결제 시스템이라면 배포 중 웹훅 수신이 유실되지 않도록 구 컨테이너의 drain 로직을 반드시 챙겨라. SIGTERM 수신 후 처리 중인 요청을 완료하고 종료하는 graceful shutdown 설정도 필수다.
④ "더 좋은 도구"와 "지금 맞는 도구"를 구분하라. 기술 블로그와 컨퍼런스는 항상 최신 도구의 장점만 이야기한다. 그 장점이 지금 우리 팀의 실제 문제를 해결하는지는 별개의 질문이다. 도구는 문제를 위해 존재한다. 도구를 위해 문제를 만들어서는 안 된다.
* 위 링크는 인프런 affiliate 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
* 위 추천 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.