개요
주문, 결제, 재고가 서비스와 DB로 분리되는 순간 가장 어려운 문제는 실패 복구와 데이터 일관성이었습니다. 이 프로젝트에서는 주문, 결제, 재고, 정산 흐름을 서비스별로 나누고, 한 단계라도 실패하면 이전 상태를 되돌리는 SAGA와 Outbox 보상 패턴을 구현했습니다. 로컬 Docker Compose 환경부터 K8s Helm 배포, GitHub Actions E2E 검증까지 연결해 작동하는 분산 시스템을 끝까지 가져간 경험입니다.
분산 트랜잭션 패턴
- ›SAGA: 재고 예약 → 결제 → 주문 저장, 실패 시 보상 트랜잭션 실행
- ›Outbox: 결제 성공 후 주문 저장 실패 시, Outbox 테이블에 보상 이벤트 저장
- ›Outbox Processor: 5초 간격 폴링, 배치 20건씩 처리, 결제 취소·재고 복구 호출
- ›Resilience4j: Retry 3회(200ms 간격), CircuitBreaker(50% 임계치, 5초 복구)
서비스 구성 (6개 독립 서비스)
- ›API Gateway(8080): JWT 검증, 라우팅, Rate Limit(120 req/min)
- ›User Service(8081): 회원가입, 로그인(JWT HS256), 인증
- ›Product Service(8082): 상품 CRUD, 재고 관리, Redis 캐싱
- ›Order Service(8083): 주문 생성, 장바구니, SAGA 오케스트레이션, Outbox 보상
- ›Payment Service(8084): 결제 승인/취소, RabbitMQ 이벤트 발행
- ›Settlement Service(8085): 일별/월별 매출 집계, 배치 스케줄러
검증 & 인프라
- ›K8s Helm 차트: 19개 템플릿, order-service HPA(1-3 replicas, CPU 70%)
- ›GitHub Actions: CI(빌드+테스트+E2E) + CD(ghcr.io 멀티플랫폼 이미지)
- ›관측성: Prometheus(15초 스크래핑) + Grafana + Zipkin 분산 트레이싱
- ›E2E 테스트: 11개 시나리오(인증, 주문, 장바구니, 실패 케이스, 정산 등) 자동화
아키텍처
전체 아키텍처▼
ERD
전체 ERD▼
시퀀스 다이어그램
문제 원인
- 01주문·결제·재고가 6개 서비스와 5개 DB에 분산되어 있어, 한 단계 실패 시 이전 단계를 되돌려야 했습니다.
- 02결제는 성공했는데 주문 저장만 실패하는 경우, 단순 메시지 발행으로는 유실·중복 가능성이 있었습니다.
- 036개 서비스를 한 번에 기동·배포·검증할 수 있는 환경과 자동화된 테스트가 필요했습니다.
해결 과정
- 01SAGA 패턴으로 재고 예약 → 결제 → 주문 저장 흐름을 정의하고, Resilience4j(Retry 3회, 200ms)로 일시적 실패를 처리하며, 결제 실패 시 재고 자동 복구를 구현했습니다.
- 02Outbox 패턴으로 결제 성공 후 주문 저장 실패 시, 같은 트랜잭션에 Outbox 테이블에 보상 이벤트를 저장하고, 별도 스케줄러(5초 간격, 20건 배치)가 폴링해 결제 취소·재고 복구를 호출합니다.
- 03Docker Compose로 로컬 개발 환경, Helm 차트로 K8s 배포를 구성하고, GitHub Actions에서 E2E 11개 시나리오를 자동 실행합니다.
결과
- 016개 서비스 + Gateway를 Docker Compose로 일괄 기동하고, 인증·주문·장바구니·실패 케이스·정산을 포함한 E2E 11개 시나리오로 전체 흐름을 검증합니다.
- 02SAGA·Outbox로 분산 트랜잭션 일관성을 보장하고, RabbitMQ 이벤트(payment.completed) 기반 비동기 정산 집계를 구현했습니다.
- 03Helm 차트(19개 템플릿)로 K8s 배포, order-service HPA(CPU 70% 기준 1-3 replicas), ghcr.io 멀티플랫폼(amd64/arm64) 이미지 CI/CD를 구성했습니다.
- 04Zipkin으로 서비스 간 요청 추적, Prometheus+Grafana로 실시간 메트릭 모니터링 환경을 구축했습니다.