포스트

아웃박스 패턴 프로젝트 시작하기: 데이터 유실 없는 이벤트 기반 아키텍처 구축!

아웃박스 패턴 프로젝트 시작하기: 데이터 유실 없는 이벤트 기반 아키텍처 구축!

안녕하세요! 마이크로서비스 아키텍처에서 데이터 일관성과 이벤트 발행의 신뢰성을 보장하는 핵심 패턴인 ‘아웃박스 패턴(Outbox Pattern)’에 대해 얼마 전 다뤘었죠. 오늘은 그 아웃박스 패턴을 실제로 적용해보는 프로젝트를 시작하면서, 어떤 점들을 고민하고 어떻게 구현해 나갈지 함께 이야기해볼까 합니다. 이론을 넘어 실제 코드로 패턴을 적용해보고 싶으셨던 분들께 좋은 가이드가 되기를 바랍니다!


왜 ‘아웃박스 패턴’ 프로젝트인가요?

마이크로서비스 환경에서 비즈니스 로직의 데이터베이스 변경과 외부 시스템(메시지 큐 등)으로의 이벤트 발행은 항상 원자성(Atomicity)을 보장해야 합니다. 그렇지 않으면 데이터 불일치나 이벤트 유실이라는 치명적인 문제가 발생할 수 있죠.

저도 이 문제 때문에 골머리를 앓았던 경험이 많습니다. 그래서 이번 기회에 이벤트 발행의 신뢰성을 극대화하면서도, 복잡한 분산 트랜잭션 없이 서비스를 설계하는 방법을 직접 구현해보고자 합니다.


프로젝트 목표: 주문 서비스와 재고 서비스 간의 안전한 이벤트 통신

이번 프로젝트의 목표는 다음과 같습니다.

  • 주문 서비스(Order Service): 주문이 ‘결제 완료’되면, 자신의 데이터베이스에 주문 상태를 업데이트하고 아웃박스 패턴을 사용하여 ‘재고 감소’ 이벤트를 발행합니다.
  • 재고 서비스(Inventory Service): ‘재고 감소’ 이벤트를 수신하면, 재고 데이터를 업데이트합니다.

가장 중요한 부분은, 주문 서비스에서 주문 상태 업데이트와 ‘재고 감소’ 이벤트 발행이 동시에 성공하거나 동시에 실패하도록 원자성을 보장하는 것입니다.


프로젝트 스택

우리가 사용할 기술 스택은 다음과 같이 구성할 예정입니다.

  • 백엔드 프레임워크: Spring Boot (Java)
  • 데이터베이스: PostgreSQL (아웃박스 테이블 생성 및 트랜잭션 관리)
  • 메시지 브로커: Apache Kafka (이벤트 발행 및 구독)
  • 이벤트 릴레이:
    • Phase 1 (초기): Polling Publisher (폴링 방식) 구현
      • 간단한 스케줄러를 사용하여 아웃박스 테이블을 주기적으로 폴링하는 방식으로 시작합니다. 직접 구현해보면서 패턴의 동작 원리를 명확히 이해할 수 있습니다.
    • Phase 2 (확장): Debezium (CDC) 연동 고려
      • 나중에는 PostgreSQL의 트랜잭션 로그를 직접 읽어 이벤트를 발행하는 Change Data Capture (CDC) 도구인 Debezium을 연동하는 것도 고려해볼 예정입니다. 이 방식이 더 효율적이고 확장성이 높습니다.
  • 빌드 도구: Gradle

핵심 구현 시나리오 및 고민 지점

아웃박스 패턴을 구현하면서 주로 다룰 내용과 고민할 지점들은 다음과 같습니다.

  1. 아웃박스 테이블 설계:
    • 어떤 정보를 아웃박스 테이블에 저장해야 할까요? (event_id, aggregate_type, aggregate_id, event_type, payload_json, created_at, status 등)
    • status 필드를 두어 이벤트의 발행 여부를 관리하는 것이 중요합니다.
  2. 단일 로컬 트랜잭션 보장:
    • Spring의 @Transactional 어노테이션을 활용하여 비즈니스 로직과 Outbox 테이블에 이벤트를 저장하는 작업을 하나의 트랜잭션으로 묶는 방법을 구현합니다.
    • 트랜잭션이 커밋될 때만 이벤트가 ‘준비됨’ 상태가 되도록 설계합니다.
  3. 이벤트 릴레이(Polling Publisher) 구현:
    • @Scheduled 어노테이션을 활용한 간단한 스케줄러를 구현하여 주기적으로 Outbox 테이블을 조회합니다.
    • status가 ‘PENDING’인 이벤트를 찾아 Kafka로 발행합니다.
    • Kafka 발행 성공 후, 아웃박스 테이블의 해당 이벤트 status를 ‘SENT’로 업데이트하거나 삭제합니다.
    • 멱등성(Idempotency) 고려: 재시도 시에도 이벤트가 중복 처리되지 않도록 재고 서비스에서 어떻게 처리해야 할지 함께 고민합니다.
  4. 장애 상황 처리:
    • 이벤트 릴레이가 이벤트를 Kafka로 발행하다 실패하면 어떻게 처리할까요? (재시도 로직, 데드 레터 큐(DLQ) 연동 등)
    • Outbox 테이블의 이벤트를 ‘Sent’로 업데이트하기 전에 릴레이 서비스가 죽으면 어떻게 될까요? (릴레이의 복구 전략)

프로젝트 진행 계획

  1. Day 1: 기본 프로젝트 구조 설정 및 도메인 모델링
    • Spring Boot 프로젝트 생성
    • Order 엔티티, OutboxMessage 엔티티 정의
    • PostgreSQL 연동 설정
  2. Day 2: 주문 생성 & 아웃박스 기록 로직 구현
    • OrderService에서 주문 생성 시 OutboxMessage를 함께 저장하는 트랜잭션 로직 구현
  3. Day 3: Polling Publisher (이벤트 릴레이) 구현
    • @Scheduled를 이용한 스케줄러 구현
    • Kafka Producer를 이용한 메시지 발행 로직
  4. Day 4: 재고 서비스 (이벤트 컨슈머) 구현
    • Kafka Consumer를 이용한 이벤트 수신 로직
    • 멱등성 보장을 위한 전략 적용 (예: 메시지 ID 중복 체크)
  5. Day 5: 테스트 및 리팩토링
    • 통합 테스트를 통해 주문부터 재고 감소까지의 흐름 검증
    • 에러 상황 테스트 및 로깅/모니터링 고려

마무리하며

아웃박스 패턴은 처음 접하면 다소 복잡하게 느껴질 수 있지만, 분산 시스템에서 데이터 일관성을 지키는 데 있어 매우 강력하고 실용적인 패턴입니다. 이번 프로젝트를 통해 아웃박스 패턴의 이론적 지식을 넘어, 실제 코드 베이스에서 어떻게 동작하고 어떤 문제들을 해결하는지 직접 경험해 볼 예정입니다.

함께 이 프로젝트를 진행하면서, 안정적이고 확장 가능한 이벤트 기반 시스템을 구축하는 노하우를 얻어가시길 바랍니다. 다음 포스팅에서는 구체적인 코드와 함께 첫 번째 단계인 ‘아웃박스 테이블 설계’와 ‘단일 로컬 트랜잭션 보장’에 대해 자세히 다루겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.