강의 요약

[팀프로젝트] MSA 기반 티켓팅 프로그램 - 개발 키워드 정리 (포트폴리오/면접용)

김둘리 2026. 5. 21. 16:00

프로젝트는 수행하는 것보다 "설명할 수 있는 상태로 만드는 것"이 더 중요하다. 결제 서비스를 개발하면서 사용한 기술들을 정리하고 왜 그 기술을 선택했는지, 어떤 문제를 어떻게 해결했는지 구조화했다.

1. 핵심 기술 키워드

토스페이먼츠 결제 연동

무엇을 했는가

  • 서버 승인 방식으로 클라이언트 직접 승인을 차단하고 결제 금액 대조 검증으로 위변조를 탐지했다.
  • bookingId UNIQUE 제약과 DataIntegrityViolationException catch로 중복 결제를 원천 차단했다.

왜 서버 승인 방식인가

  • 클라이언트 승인 방식은 사용자가 금액을 조작하거나 직접 토스 승인 API를 호출할 수 있는 보안 취약점이 있다.
  • 서버에서 승인하면 DB에 저장된 금액과 토스 응답 금액을 대조 검증할 수 있어 위변조를 즉시 탐지할 수 있다.

면접 답변 형태

토스페이먼츠 결제 연동에서 서버 승인 방식을 채택했습니다.
클라이언트 승인 방식은 사용자가 금액을 조작할 수 있는 보안 취약점이 있기 때문입니다.
결제 승인 시 DB에 저장된 금액과 토스 응답 금액을 대조 검증하여
위변조 시도를 즉시 차단했고, bookingId UNIQUE 제약으로
중복 결제도 DB 레벨에서 원천 차단했습니다.

사가 패턴 (코레오그래피)

무엇을 했는가

  • 예매 → 결제 → 확정 흐름에서 중간 실패 시 보상 트랜잭션을 자동으로 실행했다.
  • 오케스트레이션 방식이 아닌 코레오그래피 방식을 선택했다.

왜 코레오그래피인가

항목오케스트레이션코레오그래피 (선택)

결합도 높음 (중앙 의존) 낮음
SPOF 있음 없음
확장성 낮음 높음

MSA에서 서비스 간 결합도를 낮추는 것이 최우선이었다. 중앙 Orchestrator 없이 각 서비스가 자율적으로 동작하므로 특정 서비스 장애가 전체에 영향을 주지 않는다.

면접 답변 형태

MSA 환경에서 분산 트랜잭션 정합성을 보장하기 위해 사가 패턴을 적용했습니다.
코레오그래피와 오케스트레이션을 비교했을 때
오케스트레이션은 중앙 Orchestrator에 의존성이 집중되어 SPOF 위험이 있었고
서비스 간 결합도가 높아지는 단점이 있었습니다.
코레오그래피 방식으로 각 서비스가 이벤트를 발행/구독하며 자율적으로 처리하도록 했고
흐름 추적의 어려움은 Zipkin 분산 트레이싱으로 보완했습니다.

아웃박스 패턴

무엇을 했는가

  • 트랜잭션 커밋과 Kafka 이벤트 발행의 원자성을 보장했다.
  • 이벤트 유실 0%를 달성했다.

왜 필요한가

기존 방식의 문제
@Transactional
결제 상태 저장 → 트랜잭션 커밋 → Kafka 발행

Kafka 발행 실패 시
→ DB는 SUCCESS 상태
→ Booking은 예매 확정 못 받음
→ 데이터 불일치 💀

실제로 RuntimeException 발생 시 트랜잭션이 롤백되면서 이벤트 발행이 취소되는 버그를 경험했고 아웃박스 패턴으로 근본적으로 해결했다.

면접 답변 형태

결제 승인 후 Kafka 이벤트를 직접 발행하면 Kafka 장애 시
DB와 이벤트 상태가 불일치하는 문제가 있었습니다.
아웃박스 패턴을 적용하여 결제 상태 저장과 p_outbox 저장을
같은 트랜잭션에서 처리하고 스케줄러가 10초마다 PENDING 이벤트를
재발행하도록 했습니다. 이를 통해 이벤트 유실 0%를 달성했습니다.

결제 상태 설계 (FINAL_FAILED)

무엇을 했는가

  • FAILED와 FINAL_FAILED를 분리하여 선점 시간 내 재시도 기회를 보장했다.
  • payment.failed 이벤트는 FINAL_FAILED 시에만 발행했다.

왜 분리했는가

FAILED 즉시 payment.failed 발행 시 문제
→ Booking이 예매 취소
→ 사용자가 다른 카드로 재시도하려 해도 이미 취소됨 💀

해결
FAILED (선점 시간 내) → 이벤트 발행 X, 재시도 가능
FINAL_FAILED (만료 후) → payment.failed 발행, 예매 취소

면접 답변 형태

결제 실패 즉시 payment.failed 이벤트를 발행하면
사용자가 다른 카드로 재시도할 기회가 사라집니다.
티켓팅 서비스 특성상 좌석 선점 시간(10분) 내에는
재시도를 허용해야 한다고 판단하여 FAILED와 FINAL_FAILED를 분리했습니다.
선점 시간이 만료된 경우에만 이벤트를 발행하여
보상 트랜잭션이 실행되도록 설계했습니다.

 


비관적 락 (동시성 제어)

무엇을 했는가

  • 결제 만료 스케줄러와 사용자의 동시 접근으로 인한 상태 불일치를 방지했다.
  • @Lock(PESSIMISTIC_WRITE)로 조회 시점에 DB 락을 걸었다.

왜 비관적 락인가

항목낙관적 락비관적 락 (선택)

방식 @Version 충돌 감지 후 재시도 조회 시점 DB 락으로 차단
정합성 재시도 필요 충돌 원천 방지
결제 적합성 이중 결제 위험 안전 ✅

결제는 금액이 오가는 도메인으로 정합성이 최우선이다. 낙관적 락의 재시도 로직은 이중 결제 위험이 있어 비관적 락을 선택했다.

면접 답변 형태

결제 만료 스케줄러가 PENDING 결제를 만료 처리하는 동안
사용자가 동시에 결제 승인을 시도하면 동일한 Payment를
두 트랜잭션이 읽어 상태 불일치가 발생할 수 있었습니다.
낙관적 락(@Version)은 충돌 후 재시도가 필요한데
결제 도메인에서는 재시도가 이중 결제 위험이 있어
비관적 락(@Lock PESSIMISTIC_WRITE)을 선택하여
충돌을 원천 방지했습니다.

2. 성능 개선 키워드

N+1 문제 해결

무엇을 했는가

  • JMeter 부하 테스트 중 SQL 로그 분석으로 N+1 문제를 발견했다.
  • fetch join으로 Payment와 PaymentHistory를 한 번에 조회하도록 변경했다.

개선 수치

항목BeforeAfter개선율

응답 시간 6.66ms 3.27ms 51% 감소
쿼리 수 N+1번 1번 91% 감소

면접 답변 형태

부하 테스트 중 Grafana 모니터링과 SQL 로그 분석으로
GET /api/v1/payments/me API에서 N+1 문제를 발견했습니다.
@OneToMany의 기본 LAZY 로딩으로 인해
PaymentHistory 접근 시마다 쿼리가 추가 실행됐습니다.
fetch join으로 한 번에 조회하도록 변경하여
응답 시간을 6.66ms에서 3.27ms로 51% 단축했습니다.

DB 인덱스 최적화

무엇을 했는가

  • 부하 테스트 중 Prometheus에서 인덱스 부재로 인한 Full Scan을 발견했다.
  • user_id, status + requested_at 복합 인덱스를 추가했다.

개선 수치

인덱스BeforeAfter개선율

user_id 15.957ms 0.107ms 99% 감소 (149배)
status + requested_at 17.298ms 0.076ms 99.6% 감소 (227배)

면접 답변 형태

PostgreSQL은 FK에 자동으로 인덱스를 생성하지 않아
user_id 컬럼 조회 시 10만 건 전체를 Seq Scan하는 문제가 있었습니다.
Flyway 마이그레이션으로 인덱스를 추가했고
EXPLAIN ANALYZE로 Seq Scan에서 Index Scan으로 변경된 것을 확인했습니다.
user_id 인덱스는 15.957ms에서 0.107ms로 149배 향상됐습니다.

3. 인프라 키워드

아웃박스/인박스 패턴

  • 아웃박스: 이벤트 유실 방지, 스케줄러 10초마다 재발행
  • 인박스: @IdempotentConsumer로 Kafka 메시지 중복 처리 방지
  • DLQ: 최대 3회 재시도 후 Dead Letter Topic으로 이동

AWS ECS Fargate + CI/CD

  • GitHub Actions CI/CD 파이프라인 구축
  • OIDC 인증으로 AWS Access Key 미사용
  • BuildKit Secret으로 GitHub Token 이미지 레이어 노출 방지
  • SSM Parameter Store로 Toss API Key 안전 관리

Prometheus + Grafana 모니터링

  • HTTP 요청 수, API별 응답 시간, JVM Heap, DB 커넥션 풀, CPU, 에러율 8개 패널
  • 부하 테스트 중 모니터링으로 N+1 문제, 인덱스 부재 발견

4. 전체 정량 성과

결제 조회 응답 시간 6.66ms 3.27ms 51% 감소
쿼리 수 (N+1) N+1번 1번 91% 감소
user_id 인덱스 15.957ms 0.107ms 99% 감소 (149배)
복합 인덱스 17.298ms 0.076ms 99.6% 감소 (227배)
테스트 커버리지 35% 95% +60%p
domain 커버리지 27% 100% +73%p
Docker 이미지 475MB 317MB 33% 감소
부하 테스트 에러율 - 0% -

 

 

단순히 "아웃박스 패턴을 적용했다"가 아니라 "왜 필요했고, 어떤 문제를 해결했고, 어떤 트레이드오프가 있었는지"를 설명할 수 있어야 진짜 기술 역량이다.

면접에서는 코드보다 사고 과정을 보기 때문에 기술 선택의 근거와 문제 해결 과정을 명확하게 정리해두는 것이 중요하다.

'강의 요약' 카테고리의 다른 글

MSA(Microservice Architecture) 요약 - 1  (0) 2026.02.23