[Kafka] 좌석 예매 시스템의 성능 병목 해결기와 데이터 정합성 보장
- FeignClient에서 Kafka로의 변환 : 좌석 예매 시스템의 성능 병목 해결기
1. 도입 배경: FeignClient 기반 구조의 한계
초기 티켓팅 시스템은 서비스 간 통신에 FeignClient를 사용하고 있었습니다.
구현이 간편하고 직관적이지만 실서비스 시나리오에서 아래와 같은 문제를 마주했습니다:
- 수백 명이 동시에 좌석을 예매할 때 동기 호출의 병목 현상
- 일부 서비스 실패 시 장애 전파 위험
- 서비스 간 결합도가 높아 확장성 제한
이러한 문제들을 해결하기 위해 Kafka 기반의 이벤트 처리 구조로 전환을 결정했습니다.
2. Kafka 기반 구조로의 전환
Kafka를 도입하면서 서비스 간 통신을 비동기 이벤트 기반으로 재설계하였고 구조는 다음과 같습니다.
1. 공통 이벤트 구조 정의
모든 메시지를 공통 구조로 감싸 Kafka로 발행합니다.
public record GenericKafkaEvent<T>(String topic, T payload) implements KafkaEvent {}
- 다양한 도메인 메시지를 GenericKafkaEvent로 통일
- topic 필드로 토픽 라우팅
2. Kafka 발행 로직 (Producer)
예매 확정, 실패, 좌석 만료 등의 시점에 이벤트를 발행합니다.
GenericKafkaEvent<SeatConfirmedMessage> event =
new GenericKafkaEvent<>(KafkaTopicType.SEAT_CONFIRM.getTopic(), new SeatConfirmedMessage(userId));
applicationEventPublisher.publishEvent(event);
→ @TransactionalEventListener를 통해 실제 Kafka로 발행:
@EventListener
public void handleKafkaMessage(KafkaEvent event) {
String json = objectMapper.writeValueAsString(event);
kafkaTemplate.send(event.topic(), json);
}
3. Kafka 수신 로직 (Consumer)
Ticket 서비스에서 발행한 메시지를 수신하여 좌석 상태를 변경합니다.
@KafkaListener(topics = "ticket_confirm")
public void consumeTicketConfirm(String json) {
TicketConfirmedMessage message = parse(json);
seatService.confirmSeats(message.seatIds(), message.userId());
}
- 예매 확정(ticket_confirm)
- 예매 취소(ticket_cancel)
- 좌석 만료(seat_expired) 등 다양한 메시지를 수신 처리
4. DLQ(Dead Letter Queue) 구성
Kafka 메시지 수신 중 오류가 발생하면 .DLQ 토픽으로 전송하여 장애를 격리하고 추적합니다.
@KafkaListener(topics = "ticket_cancel.DLQ")
public void consumeTicketCancelDlq(ConsumerRecord<String, String> record) {
log.error("DLQ 처리 실패 메시지: {}", record.value());
}
3. 성능 개선 효과
항목 | V2 (FeignClient 기반) | V3 (Kafka 기반) |
처리량 | 246.4건/초 | 346.9건/초 (1.5배↑) |
응답 블로킹 | 전체 흐름이 지연에 취약 | 비동기 처리로 블로킹 제거 |
사용자 응답 속도 | 상대적으로 느림 | 대기 시간 감소로 향상 |
오류율 | 낮음 (정상 처리 유지) | 동일한 오류율 유지 (99.9%) |
👉 동기 호출로 인해 발생하던 응답 지연 및 전체 흐름 블로킹 문제를 Kafka 도입을 통해 해소했으며,
결과적으로 처리량은 약 1.5배 향상, 사용자 체감 속도도 대폭 개선되었습니다.
- Kafka 기반 구조 전환 이후: SAGA 패턴을 통한 데이터 정합성 보장
Kafka 기반 이벤트 처리 구조로 전환한 것은 단순한 메시징 시스템 도입을 넘어서
서비스 간 느슨한 결합과 함께 분산 환경에서의 데이터 정합성을 실질적으로 보장하기 위함이었습니다.
이를 위해 우리는 Kafka 메시지를 기반으로 하는 SAGA 패턴을 설계하고 적용했습니다.
1. 왜 SAGA 패턴이 필요한가?
티켓 예매와 결제 시스템에서는 다음과 같은 시나리오가 자주 발생합니다:
- 결제는 실패했지만 좌석은 이미 예약된 상태로 남아있는 경우
- 예매 도중 오류가 발생해 티켓은 생성되지 않았지만 좌석은 선점된 상태인 경우
- 예매 시간이 초과되었지만 좌석이 자동으로 풀리지 않아 사용자가 다시 시도할 수 없는 경우
이러한 상황은 마이크로서비스 아키텍처에서 서비스 간 직접적인 트랜잭션 공유가 불가능하기 때문에 정합성 유지에 매우 취약할 수 있습니다.
우리는 이 문제를 해결하기 위해 보상 트랜잭션 기반의 SAGA 패턴을 이벤트 흐름 안에 통합하였습니다.
2. 전체 SAGA 흐름 요약
Kafka 메시지를 기반으로 서비스 간 상태 변화를 트래킹하며 각 단계에서 실패하거나 취소되는 경우에는 이전 상태로 복원하는 보상 메시지를 발행합니다.
[결제 요청 → 예매 완료] 흐름 예시
PAYMENT (payment_create)
↓ Kafka
TICKET (ticket_confirm)
↓ Kafka
SEAT (seat_confirm)
↓ Kafka
NOTIFICATION (예매 완료 안내 전송)
- 정상 플로우에서는 각 단계별 상태가 확정되며 최종적으로 알림이 발송됩니다.
[결제 실패 → 좌석/티켓 복구] 보상 흐름 예시
PAYMENT (payment_fail)
↓ Kafka
TICKET (ticket_cancel)
↓ Kafka
SEAT (seat_cancel)
↓ Kafka
NOTIFICATION (예매 실패 안내 전송)
- 실패 발생 시 자동으로 보상 트랜잭션이 순차적으로 실행되며,
- 좌석은 다시 AVAILABLE 상태로 복원되고 티켓은 생성되지 않습니다.
3. 구현 포인트
- 각 서비스는 Kafka 메시지를 수신하고, 본인의 상태만 처리
- 메시지 구조는 GenericKafkaEvent<T> 형태로 일관성 유지
- DLQ(Dead Letter Queue)를 통해 실패 메시지 추적 가능
- Redis TTL 기반으로 좌석 만료 복구 → seat_expired 이벤트 발행
@KafkaListener(topics = "ticket_cancel")
public void consumeCancel(String message) {
// Ticket 취소 시 seat 복구
seatService.revertSeats(...);
}
또한 좌석 보상 처리 외에도 예매 만료 시 자동 복구 로직도 포함되어 있어 시간 초과로 인한 선점 좌석도 정상적으로 AVAILABLE 상태로 돌아갑니다.
4. Kafka + SAGA의 효과
SAGA 패턴을 적용한 후 다음과 같은 운영상 안정성과 복원력을 확보할 수 있었습니다.
항목 | 적용 전 | 적용 후 |
장애 발생 시 좌석 상태 | 수동 복구 필요 | 자동 복구 (seat_cancel) |
트랜잭션 보장 | 서비스 단위 처리 | 이벤트 체인 기반 보상 처리 |
사용자 경험 | 예외 발생 시 오류 | 실패 상황 안내 및 자동 좌석 반환 |
확장성 | 서비스 간 직접 호출로 제한 | 메시지 기반 유연한 흐름 가능 |
5. 마무리 요약
Kafka 기반 구조 전환은 단순한 기술 변경이 아닌 서비스 안정성, 확장성, 고가용성을 확보하기 위한 필수 선택이었습니다.
Kafka 기반 구조를 도입한 후 SAGA 패턴을 활용한 보상 트랜잭션 흐름까지 적용함으로써 다음을 실현할 수 있었습니다.
- ✅ 서비스 간 독립성과 느슨한 결합 유지
- ✅ 실패 복구 및 상태 정합성 확보
- ✅ 사용자에게 더 안정적인 예매 경험 제공
현재는 Kafka 기반 단순 이벤트 발행/소비 구조를 도입한 상태이며,
향후 다음 기능도 점진적으로 적용할 예정입니다.
- 📦 Outbox 패턴 도입 → DB 트랜잭션과 메시지 발행의 원자성 보장
- 🔁 이벤트 재처리 및 재시도 정책 구성 → DLQ 외 복구 전략 고도화
- 🧪 Schema Registry 및 메시지 검증 → 서비스 간 안정성 강화
이 구조는 단순한 기술 적용을 넘어서 신뢰할 수 있는 분산 시스템 아키텍처의 실전 예시로 활용될 수 있습니다.
티켓팅처럼 고부하 환경에서도 안전한 트랜잭션 흐름을 만들고자 하는 분들께 도움이 되길 바랍니다.