728x90
Outbox 패턴 기반 메시지 전송 시스템 설계
도입
마이크로서비스 아키텍처에서 도메인 이벤트를 외부 메시징 시스템(Kafka 등)으로 안정적으로 전송하는 것은 중요하면서도 까다로운 문제입니다.
특히 트랜잭션과 메시지 전송의 원자성을 보장하지 않으면, 메시지가 유실되거나 중복 전송되는 문제가 발생할 수 있습니다.
이를 해결하기 위해, 우리는 Outbox 패턴을 기반으로 한 메시지 전송 시스템을 설계하고 구현하였습니다.
설계 목표
- 트랜잭션과 메시지 전송의 분리: DB 저장과 메시지 발행을 물리적으로 분리
- 데이터 유실 없는 메시지 발송: 메시지 발송 실패 시 재시도 가능
- Kafka와의 결합 최소화: 전송 수단(Kafka 등)을 추상화하여 유연한 구조 확보
- 모듈 간 책임 분리: 메시징 로직은 infra-messaging 모듈, 이벤트 저장/스케줄링은 outbox 모듈로 구분
설계 고려사항
- 신뢰성 (Reliability): 메시지는 반드시 적어도 한 번 전송되어야 함
- 재전송 및 실패 처리: Kafka 전송 실패 시 retry + dead 상태 분리
- 유연성 (Flexibility): Kafka 외 다른 메시징 수단도 쉽게 도입 가능
- 테스트 용이성: 외부 인프라 없이도 테스트 가능한 구조 필요
- 확장성 (Scalability): 고빈도 메시지 발생 상황을 고려한 설계
구현 설명
📁 모듈 구조
public:outbox
├── config/
│ └── OutboxConfig.java ← Bean 등록
├── domain/
│ └── OutboxEventEntity.java ← 메시지 엔티티 (JPA)
├── repository/
│ └── OutboxEventRepository.java ← DB 접근
├── publisher/
│ └── OutboxEventPublisher.java ← 이벤트 저장
├── sender/
│ └── OutboxEventRelayScheduler.java ← Kafka 전송 스케줄러
주요 컴포넌트
OutboxEventEntity
@Entity
@Table(name = "event_outbox")
public class OutboxEventEntity {
@Id
private Long id;
private String type;
private String payload;
private boolean sent;
private int retryCount;
private boolean dead;
...
}
OutboxEventPublisher
public void publish(BindEvent event) {
OutboxEventEntity entity = OutboxEventEntity.from(event, snowflake);
repository.save(entity);
}
OutboxEventRelayScheduler
@Scheduled(fixedDelay = 1000)
public void relay() {
List<OutboxEventEntity> events = repository.findTop100BySentFalseOrderByCreatedAtAsc();
for (OutboxEventEntity e : events) {
try {
messagingSender.send(e.getType(), e.getPayload(), String.valueOf(e.getId()));
e.markSuccess();
} catch (Exception ex) {
e.markFailure(ex.getMessage());
}
}
repository.saveAll(events);
}
MessagingSender 추상화
public interface MessagingSender {
void send(String topic, String payload, String key);
}
KafkaEventProducer는 이 인터페이스를 구현하여 KafkaTemplate을 감쌈.
테스트 전략
OutboxEventPublisher의 저장 기능은 단위 테스트로 검증OutboxEventRelayScheduler는MessagingSender를 Mock 처리하여 전송 로직 테스트- 실제 Kafka 환경에서는 통합 테스트 환경 구성하여 시나리오별 전송 확인
- DB 초기 상태 → 발송 시도 → 실패 시 재시도 → dead marking 까지 end-to-end 검증
회고 및 확장 가능성
장점
- Kafka와의 결합이 제거되어 구조가 유연해졌음
- 테스트 코드 작성이 훨씬 쉬워짐
- 메시지 전송 실패에 대한 retry 및 dead queue 처리까지 완비
- messaging 방식이 변경되어도 Outbox는 변경 없이 재사용 가능
보완 및 확장
- dead 상태 메시지를 알림/Slack 등과 연동해 모니터링 가능
payload를 JSON 문자열이 아닌proto또는Avro등으로 직렬화 가능event_outbox테이블에 샤딩을 고려하거나, 큐 기반 전환도 가능- multi-topic 또는 multi-destination을 위한 라우팅 추상화도 추후 필요
마치며
Outbox 패턴은 단순한 구조지만, 제대로 설계하지 않으면 오히려 시스템의 병목이 되기 쉽습니다.
이번 구현에서는 infra 모듈과 도메인 모듈의 명확한 분리, MessagingSender 인터페이스를 통한 전송 책임 추상화 등을 통해
확장성과 안정성을 모두 만족하는 구조를 도출할 수 있었습니다.
728x90
'BindProject' 카테고리의 다른 글
| [Backend] BFF 구현 하기 앞서 구상 및 정리 (0) | 2025.06.17 |
|---|---|
| [중간 회고] 여기까지 구현 하면서... (1) | 2025.06.17 |
| [Backend] infra-messaging 모듈 설계 및 구현 (1) | 2025.06.17 |
| [Backend] Event(이벤트 역/직렬화)모듈 구현 (0) | 2025.06.17 |
| [Backend] exception(2) ( 예외 계층화 구현) (1) | 2025.06.17 |