BindProject

[Backend] 유저 활동 로그 수집 모듈 개발기 2-1편 : Kafka Outbox 패턴 기반 유저 활동 로그 수집기 리팩토링기(문제 확인)

dding-shark 2025. 6. 21. 21:48
728x90

도입

서비스에 유저 활동 로그를 남기기로 결정한 이후, 나는 곧 중요한 딜레마에 부딪혔다. "유저 활동 로그는 쓰기 트래픽이 압도적으로 많고, 유실되어서는 안 되며, 속도도 중요하다." 이 세 가지 요구사항을 만족하기 위해 기존 Outbox 구조를 되돌아보게 되었다.

기존의 Outbox는 단건 이벤트를 즉시 저장하고, Kafka로 전송하는 방식으로 구성되어 있었다. 하지만 로깅 시스템은 본질적으로 다르다. 수많은 작은 이벤트가 순식간에 쏟아지기 때문에, 기존 구조로는 병목이 생기고, 네트워크 사용량 또한 비효율적으로 늘어난다.


기존 Outbox 구조의 한계

초기 Outbox 구조는 단순하고 직관적이다.

  • OutboxEventEntity에는 sent: boolean만 존재
  • Kafka 전송은 개별 이벤트 단위
  • 상태 관리나 재시도, 실패 관리 없음

이 구조는 다음과 같은 한계를 갖는다.

1. 상태 관리 부족

private boolean sent;

이 한 줄로는 전송 실패, 재시도 필요, 완료, 보류 등의 다양한 상태를 표현할 수 없다. Kafka 전송 실패 시 별도 로그 외에는 어떤 이벤트가 실패했는지도 추적하기 어렵다.

2. 배치 전송 부재

유저 로그 시스템은 초당 수십~수천 건의 이벤트가 들어올 수 있다. 이걸 한 건씩 Kafka로 보내는 건 너무 비효율적이다. 적절한 버퍼링과 배치 전략이 필요했다.

3. 실패 처리 미흡

Kafka 네트워크 오류, 직렬화 실패, Kafka 브로커 다운 등 다양한 장애 상황에 대비한 retry 로직이 없다.


리팩토링 목표

이러한 문제를 해결하기 위해 다음과 같은 방향으로 Outbox를 리팩토링하기로 결정했다.

개선 목표 설명
상태 기반 전송 관리 SENT, PENDING, FAILED 상태를 enum으로 명시
배치 전송 방식 도입 일정 단위로 묶어서 Kafka에 전송
재시도 및 실패 감지 실패 횟수, 마지막 에러 메시지, 재시도 시간 기록
정리 스케줄러 도입 오래된 전송 완료 이벤트 정리

리팩토링 전 코드 스냅샷

// 기존 구조 (간소화)
private boolean sent;

public boolean isReadyToSend() {
    return !sent;
}

단순한 sent 여부만으로 전송 대상 여부를 판단했기에, 전송 실패에 대한 재시도 로직을 추가하거나 실패 사유를 저장하기 어려웠다.


마무리

Outbox는 단순한 이벤트 전송 테이블 같지만, 실제로는 이벤트 처리의 신뢰성과 성능을 좌우하는 핵심 인프라다. 특히 로그 시스템처럼 이벤트 밀도가 높은 시나리오에서는 기존 구조로는 한계가 분명했다.

그래서 다음 편에서는 우리가 도입한 상태 기반 Outbox 구조배치 전송 로직, 그리고 스케줄러 기반 클린업에 대해 상세히 소개하겠다.


다음 편 예고

🔧 [Outbox 리팩토링 개발기] 2편 - Kafka 전송 최적화를 위한 상태 기반 배치 전송 구조 만들기


 

728x90