들어가기 앞서
비동기 메시징 시스템을 사용하면서 가장 많이 고민하게 되는 부분은 바로 트랜잭션과 메시지 손실에 대한 처리입니다. 메시지를 발행하고 소비하는 과정에서 예기치 않은 장애가 발생하거나, 메시지가 중간에 유실될 가능성은 언제나 존재합니다.
예를 들어, 커머스 서비스에서 사용자가 상품을 구입하고 결제가 완료된 후, 결제 완료 메시지가 배송 서비스로 전송되었다고 가정해봅시다. 이때 만약 배송 서비스에 장애가 발생하거나, 메시지를 정상적으로 수신하지 못했다면 어떻게 될까요? 결제는 완료되었지만 배송이 이루어지지 않는, 즉 데이터 불일치 상황이 발생하게 됩니다.
이처럼 메시지 유실이나 처리 실패에 대한 대비가 없다면, 시스템 전반의 무결성이 무너질 수 있습니다. 그래서 저는 비동기 메시징을 활용할 때 가장 중요한 요소는 바로 장애 발생 시의 복구 전략과 메시지 분실에 대한 처리 방안이라고 생각합니다. 이번 글에서는 위와 같은 문제를 해결하기 위한 접근법으로 트랜잭션 아웃박스 패턴, TCC 패턴, 그리고 RabbitMQ의 Publisher Confirms 기능 등을 중심으로 메시지 유실과 장애 대응 전략을 정리해보겠습니다.
RabbitMQ 트랜잭션 처리
1. 왜 비동기 메시징을 사용할까?
하나의 주문 요청이 들어오면 주문 → 결제 → 배송 → 알림 등 여러 처리를 하나의 긴 트랜잭션 안에서 처리해야 합니다. 하지만 이런 구조는 다음과 같은 문제를 만듭니다.

- 응답 지연: 한 서비스가 느려지면 전체 응답이 지연된다.
- 서비스 장애 전파: 하나의 서비스가 죽으면 다른 서비스까지 영향 받는다.
- 확장성 한계: 부분 확장이 불가능하다.
이러한 문제를 해결하기 위해 사용하는 것이 메시지 큐(MQ) 기반의 비동기 아키텍처입니다.
2. 왜 트랜잭션 처리 전략이 필요한가?
메시지 큐 기반의 비동기 아키텍처는 서비스 간의 느슨한 결합을 가능하게 하여, 응답 지연 최소화, 장애 격리, 수평 확장성 확보 등의 장점을 제공합니다. 하지만 비동기 처리라는 특성상 메시지 손실이나 중복, 처리 실패에 대한 리스크가 존재합니다.
예를 들어, 다음과 같은 상황이 발생할 수 있습니다:
- 주문 서비스에서 결제 성공 후 메시지를 발행했지만, 메시지 브로커에 전송되지 않음
- 메시지는 브로커에 도달했지만, 컨슈머 서비스가 일시적으로 다운되어 메시지를 처리하지 못함
- 메시지 소비 후 처리 도중 장애 발생 → 재처리 불가능한 상태로 메시지 손실
- 네트워크 오류로 인해 중복 메시지가 발생, 처리 결과가 불일치함
이러한 상황은 곧바로 데이터 정합성 문제, 서비스 신뢰성 저하, 사용자 경험 악화로 이어질 수 있습니다. 따라서 단순히 메시지를 비동기로 보내는 것만으로는 부족하며, 반드시 다음과 같은 신뢰성 확보 전략이 필요합니다:
- 메시지가 정상적으로 브로커에 도달했는지
- 컨슈머가 메시지를 정확히 한 번만 처리했는지
- 장애가 발생했을 때 메시지를 재처리하거나 보상할 수 있는 방법이 있는지
이 문제를 해결하기 위한 기술적인 접근이 바로 트랜잭션 처리 전략입니다.
3. 메시지 신뢰성을 위한 트랜잭션 처리 전략
RabbitMQ와 같은 메시지 시스템에서 사용할 수 있는 주요 트랜잭션 전략들이 있습니다. 트랜잭션 메시징은 메시지를 보내는 동안 문제가 발생하면 메시지 상태를 롤백하거나 커밋하여 메시지가 성공적으로 처리되었음을 보장한다.

- 트랜잭션 메시징 (Transaction Messaging)
- Publisher Confirms
- Consumer Transaction 전략
- DLQ/PLQ 처리 전략
- TCC (Try-Confirm-Cancel) Pattern
- Transaction Outbox Pattern
3-1. 트랜잭션 메시징 (Transaction Messaging)
트랜잭션 메시징은 RabbitMQ에서 메시지를 큐에 전송하는 과정을 AMQP 트랜잭션으로 묶어 처리하는 방식입니다. AMQP 표준에서 제공하는 txSelect(), txCommit(), txRollback() API를 통해 메시지 전송의 원자성을 직접 제어할 수 있습니다.
이 전략은 Producer 측에서 메시지가 큐에 정확히 도달했는지 확실히 보장해야 할 때 사용됩니다. 하나의 트랜잭션 블록 내에서 여러 메시지를 전송할 수 있으며, 중간에 예외가 발생하면 전체 메시지 전송을 rollback할 수 있습니다.
특징
- txSelect()를 호출하면 트랜잭션이 시작되고, 전송된 메시지는 txCommit()이 호출되기 전까지 큐에 반영되지 않음
- txRollback() 호출 시 해당 트랜잭션 내 전송된 메시지 모두 무효 처리됨
- 트랜잭션 단위로 메시지 전송 결과를 완전하게 제어 가능
장점
- 메시지 전송의 완전한 원자성 보장
- 동일 트랜잭션 블록 내에서 메시지를 다수 전송할 수 있음
- MQ 자체 장애로 인한 메시지 유실 가능성을 방지
단점
- 성능 오버헤드가 매우 큼: 메시지 전송 시마다 디스크 flush가 발생
- 비동기 아키텍처의 이점을 무력화시킴 (처리량 저하)
- RabbitMQ 운영자 가이드에서도 대규모 시스템에서는 비권장
3-2. Publisher Confirms
Publisher Confirms는 RabbitMQ가 제공하는 경량화된 메시지 수신 확인 메커니즘입니다.
Producer가 메시지를 브로커에 전송한 후, RabbitMQ는 해당 메시지에 대해 ACK 또는 NACK 응답을 비동기적으로 반환하여 메시지가 실제 큐에 도달했는지 여부를 확인할 수 있습니다.

특징
- 메시지를 전송한 후, 브로커로부터 메시지 수신 여부에 대한 확인 응답(ACK/NACK) 수신
- 여러 메시지를 비동기로 처리할 수 있으며, 개별 메시지에 대한 확인도 가능
- 메시지 전송 실패 시 선별적으로 재전송 가능
장점
- 트랜잭션 메시징보다 성능이 매우 뛰어남
- 브로커에 도달했는지 여부만 체크하면 되는 경우 가장 적합
- 실시간성과 고처리량이 중요한 시스템에 적합
단점
- 메시지가 브로커에 도달했는지까지만 확인하며, Consumer가 실제 처리했는지는 보장하지 않음
- ACK/NACK 처리를 위한 콜백 관리 및 전송 추적 로직 구현 필요
https://www.rabbitmq.com/tutorials/tutorial-seven-java (RabbitMQ Publisher Confirms 공식문서)
3-3. Consumer Transaction 전략
Consumer Transaction 전략은 메시지를 수신한 후 수행되는 비즈니스 로직(DB 저장, 상태 변경 등)을 트랜잭션 단위로 처리하여, 메시지 처리의 신뢰성과 데이터 정합성을 확보하는 방식입니다.
Spring에서는 @Transactional을 이용해 메시지 처리 로직과 DB 작업을 하나의 트랜잭션으로 묶을 수 있습니다. 이때 메시지 처리가 실패하면 예외가 발생하고 트랜잭션은 롤백되며, RabbitMQ는 메시지를 Ack하지 않아 자동 재전송(requeue)이 발생합니다.
특징
- 메시지 수신 직후 DB 작업을 포함한 로직 전체를 트랜잭션으로 감쌈
- @RabbitListener와 함께 @Transactional 사용
- 예외 발생 시 DB 롤백과 함께 메시지 NACK 또는 Requeue 가능
장점
- 메시지 처리와 DB 변경을 하나의 논리적 단위로 묶어 데이터 정합성 확보
- 실패 시 자동 재처리로 일관성 유지 가능
단점
- idempotency(멱등성) 보장 필요: 메시지가 중복 소비되더라도 결과가 중복되면 안 됨
- Ack 타임아웃 위험: 트랜잭션이 오래 걸리는 경우 메시지 자동 재전송 또는 중복 소비 발생 가능성 있음
- 트랜잭션 실패가 반복되면 DLQ로 이동하거나 시스템에 부하를 줄 수 있음
3-4. DLQ / PLQ 처리 전략
Dead Letter Queue(DLQ)와 Parking Lot Queue(PLQ)는 메시지 소비 중 예외나 오류가 반복 발생할 경우, 해당 메시지를 분리하여 후속 조치를 할 수 있도록 설계된 장애 격리 메커니즘입니다.

RabbitMQ에서 Consumer가 메시지를 처리하지 못하고 NACK 처리하거나, 재시도 횟수 초과, TTL 만료 등의 조건이 만족되면 메시지는 자동으로 DLQ로 이동합니다.
PLQ는 DLQ와 유사하지만, 운영자가 수동으로 이동하거나 정책 기반으로 필터링한 메시지를 분리 저장하여 따로 처리하는 용도입니다.
이 전략은 문제가 있는 메시지를 본 처리 큐에서 분리하여 장애 전파를 차단하고, 시스템 안정성을 유지하는 데 큰 역할을 합니다.
RabbitMQ에 대해 알아보자 - (4) 메시지 실패 처리 DLQ, DLX, PLQ
특징
- 메시지가 일정 조건에 도달하면 DLQ로 이동 (예: x-death header 기준)
- PLQ는 운영자가 특정 메시지를 격리하거나 재처리 대상으로 지정할 때 사용
- 실패 메시지에 대한 모니터링, 로그 분석, 수동 보정 가능
장점
- 문제 메시지를 별도 큐로 격리하여 서비스 가용성 확보
- 실패 메시지를 모아서 분석 및 재처리 정책 수립 가능
- 시스템 전체의 안정성을 높여줌 (Fail-Safe 구조)
단점
- DLQ에 들어간 메시지는 자동으로 복구되지 않음 → 수동 분석 또는 후처리 로직 필요
- PLQ는 운영 도구나 모니터링 기반의 별도 인프라 필요
- DLQ 진입 기준이나 정책을 명확히 설정하지 않으면 디버깅 난이도 상승
3-5. TCC (Try-Confirm-Cancel) 패턴
TCC(Try-Confirm-Cancel)는 분산 시스템에서 전통적인 트랜잭션 처리가 불가능한 경우, 보상 로직을 기반으로 트랜잭션 유사 동작을 구현하는 패턴입니다.
서비스 간의 작업을 Try, Confirm, Cancel 세 단계로 나누어 각 상태를 명확하게 분리하여 처리 흐름의 제어권을 보장합니다. 예를 들어, 주문과 결제를 각각 다른 서비스에서 처리해야 할 경우,
- Try 단계에서 재고를 예약하고,
- Confirm 단계에서 결제까지 성공하면 재고를 확정 처리하고,
- Cancel 단계에서는 결제가 실패하거나 중간에 오류가 발생한 경우 예약된 재고를 취소합니다.
TCC는 일반적으로 메시지 큐 기반으로 구현되며, 각 단계의 결과를 이벤트 메시지로 다른 서비스에 전달합니다.
특징
- Try: 자원 확보 또는 가용성 점검 (예: 재고 hold, 잔액 확인)
- Confirm: 실제 커밋 처리 (예: 결제 확정, 재고 차감)
- Cancel: 작업 실패 시 자원 회수 또는 상태 복원 (예: 재고 해제, 예약 취소)
- Confirm 또는 Cancel은 메시지 기반으로 비동기 처리되거나, REST 호출 방식으로 구현됨
장점
- 이기종 시스템 간 트랜잭션 흐름 관리 가능
- 명확한 단계 분리로 상태 추적 및 보상 설계가 용이
- 네트워크 분리 환경, 마이크로서비스 간 통합에 적합
단점
- 구현 복잡도 높음: 각 단계별 API 또는 메시지 처리 로직 필요
- idempotency, 상태 추적, 타임아웃, 장애 복구 등 고려사항이 많음
- 보상 로직의 정확성과 안정성이 핵심
3-6. Transaction Outbox Pattern
Transaction Outbox Pattern은 애플리케이션에서 데이터베이스 트랜잭션과 메시지 발행 간의 원자성을 보장하기 위한 패턴입니다.
DB 트랜잭션 내에서 이벤트 메시지를 직접 MQ로 전송하지 않고, outbox 테이블에 메시지를 저장한 뒤, 비동기적으로 MQ로 전송하는 방식입니다.
이 패턴은 "DB는 커밋됐는데 메시지는 전송되지 않았다" 또는 그 반대의 상황을 방지하며, 로컬 트랜잭션 환경에서 Exactly-once-like 보장을 구현할 수 있습니다.
특징
- 메시지 전송 요청은 도메인 로직과 함께 DB 트랜잭션 안에서 outbox 테이블에 저장됨
- 별도 프로세스가 outbox 테이블을 polling하거나 CDC(Debezium)로 MQ에 전송
- 메시지 전송 성공 후 상태 플래그 업데이트 또는 삭제 처리
장점
- DB 작업 + 메시지 발행의 원자성(Atomicity) 보장
- 기존 RDB 기반 애플리케이션에 쉽게 적용 가능
- Consumer가 idempotent하게 설계되어 있으면 Exactly-once에 가까운 신뢰성 확보 가능
단점
- Polling 처리의 지연 가능성 (실시간성이 낮을 수 있음)
- outbox 테이블 정합성 유지 및 상태 관리 필요
- 전송 중복, 실패 처리를 위한 보조 테이블/로직 필요
'Kafka, RabbitMQ' 카테고리의 다른 글
| 카프카(kafka)에 대해 알아보자 (0) | 2025.05.23 |
|---|---|
| RabbitMQ에 대해 알아보자 - (12) Producer 트랜잭션 전략 (0) | 2025.05.20 |
| RabbitMQ에 대해 알아보자 - (10) Header 기반 라우팅 / Headers Exchange 전략 (0) | 2025.05.08 |
| RabbitMQ에 대해 알아보자 - (9) Topic 패턴 / Topic Exchange 전략 (0) | 2025.05.08 |
| RabbitMQ에 대해 알아보자 - (8) Routing 패턴 / Direct Exchange 전략 (0) | 2025.05.08 |