이번 주는 카프카를 활용해 외부 이벤트를 처리하며 다양한 고민을 해보는 시간이었습니다.
모든 이벤트를 정확히 한 번만 처리해야 할까? 때로는 처리되지 않아도 되는 경우가 있지 않을까? 빠른 처리가 중요한 작업과 정확성이 중요한 작업을 어떻게 처리할까?
이러한 질문들을 바탕으로 카프카의 특성을 활용해 각 작업의 요구사항에 맞는 처리 전략을 설계했습니다. 이 글에서는 그 과정에서 얻은 인사이트와 실제 적용한 설계 방법을 공유하고자 합니다.
카프카를 쓰며 배운 것들: 작업 특성에 맞춘 컨슈머 그룹 분리
카프카의 기본 구조
카프카는 물리적인 파티션을 논리적으로 묶은 '토픽'이라는 개념을 사용합니다. 토픽은 일정 기간 영속성을 가진 데이터로, 소비된 후에도 그 자리에 계속 남아있습니다. 마치 칠판에 적힌 글씨를 여러 학생이 각자의 속도로 서로 다른 부분을 읽어가는 것과 유사합니다.
컨슈머 그룹의 동작 방식
토픽에 관심 있는 컨슈머 그룹들은 각자의 처리 속도에 맞춰 데이터를 읽고, 어디까지 읽었는지 오프셋을 기록합니다. 이전 주에 다뤘던 어플리케이션 이벤트 리스너 방식과 비슷하게, 토픽이 발행되면 구독 중인 컨슈머 그룹들이 해당 메시지를 읽어 처리합니다.
동일한 작업을 수행하는 컨슈머들은 하나의 그룹으로 묶어 효율적으로 관리할 수 있습니다. 예를 들어, 사용자의 좋아요 클릭, 상품 조회, 구매 이벤트를 하나의 컨슈머 그룹이 집계하도록 구성할 수 있습니다. 또한 같은 이벤트를 여러 컨슈머 그룹이 구독하여 각자 다른 작업을 수행할 수도 있습니다.
어플리케이션 이벤트와 외부 이벤트의 차이
어플리케이션 이벤트는 특정 도메인 내부의 처리가 중심입니다. 반면 외부 이벤트(카프카)는 여러 도메인을 아우르는 작업 처리가 가능했습니다. 외부 이벤트 시스템을 통해 주문, 상품, 좋아요 등 서로 다른 도메인들이 자연스럽게 연결됩니다. 각 도메인은 단순히 이벤트를 외부로 발행하기만 하면, 관심 있는 "작업 처리기"가 이를 구독하여 필요한 처리를 수행합니다. 이런 구조는 여러 도메인 간 오케스트레이션이 필요할 때도 이용할 수 있다고 생각합니다.
카프카를 활용한 3가지 작업 처리 전략
구현한 작업들
- 감사 로그 - 모든 이벤트 수집
- 상품 집계 - 주문, 상품, 좋아요 이벤트 처리
- 캐시 삭제 - 주문, 상품, 좋아요 이벤트 처리
세 작업 모두 비슷한 도메인의 이벤트를 구독하고 있어 하나의 컨슈머 그룹에서 처리할 수도 있지만 각 작업의 특성을 고려하면 컨슈머 그룹 분리가 필요합니다. 아래는 각각의 작업 특성이 어떤 차이가 있기 때문에 분리하는 것이 좋은지 적어보았습니다.
각 작업의 특성과 처리 전략
📝 감사 로그 (Audit Log)
특징: 지연 허용, 데이터 무손실이 핵심
- 모든 이벤트를 빠짐없이 저장
- 예외 상황에서 충분한 재시도 보장
- 재시도 실패 시 DLQ(Dead Letter Queue)에 저장 후 별도 처리
- DLQ 메시지는 반드시 재처리
latest가 권고사항이지만, 무손실이 핵심이라면 earliest로 설정하여 빠짐없이 데이터가 처리되면서 멱등성을 보장해 중복 처리 되지 않게 개발해야 합니다.
📊 메트릭 집계 (Metrics)
특징: 높은 처리량(throughput)이 우선
- 통계 목적이므로 약간의 데이터 손실 허용
- 대량 데이터 처리를 위한 다수의 컨슈머 스레드 운영
- 단건 처리 대신 배치 처리로 효율성 극대화
- DLQ 메시지는 비율 모니터링만 수행, 통계적으로 미미하면 무시
파티션 전략 고민: 구현은 상품ID로 파티션을 분리했지만 재고려가 필요하다고 봅니다. 8:2 법칙처럼 인기는 특정 상품에 집중되므로 인기 상품의 집계가 지연될 수 있기 때문입니다. 또한 프로젝트의 현재 구현이 개별 카운트 증가 방식이라 순서가 크게 중요하지 않습니다. 라운드 로빈이나 스티키 파티셔닝이 더 적합할 수 있다고 생각합니다. 즉, 먼저 온 순서대로 빠르게 처리되는 것이 중요하지 상품ID별로 분리되어 순서가 중요한 작업이 아니라 생각합니다.
🔄 캐시 무효화 (Cache Evict)
특징: 실시간성이 최우선
- 가볍고 빠른 처리 필수
- 과거 데이터는 무시. 최신 데이터만 처리
- 장애 시 latest 설정으로 최신 데이터부터 처리
- DLQ는 장애 모니터링 용도로만 활용
컨슈머 그룹 분리의 이점
각 작업을 별도 컨슈머 그룹으로 분리하면 작업별 특성에 맞는 처리 속도와 오프셋 관리 전략을 적용할 수 있습니다. 그리하여 단일 컨슈머 그룹보다 훨씬 효과적인 운영이 가능합니다.
- 감사 로그: 느리더라도 꼼꼼한 처리
- 집계: 배치 처리로 효율성 극대화
- 캐시 무효화: 최신 이벤트 우선 처리
저장소 선택 전략
멱등성 요구사항에 따른 저장소 전략을 생각해보았습니다.
- 감사 로그: RDB (정확한 멱등성 보장)
- 집계: Redis 검토 (빠른 처리, 멱등성 검증 필요)
- 캐시 무효화: 별도 멱등성 관리 불필요
집계 데이터 처리 아키텍처 설계
Redis 기반 실시간 집계 + 주기적 RDB 영속화
- 실시간 집계: 원본 key에서 고속 카운팅
- 데이터 백업 및 초기화 (원자적 처리)
- 별도 Redis key에 원본 Key값 백업
- 원본 key 초기화
- 집계 재개: 초기화한 Key에서 새로운 집계 즉시 시작
- 영속화: 백업용 Key의 값을 RDB에 누적 반영
구현 시 고려사항
Redis 기반 실시간 집계 + 주기적 RDB 영속화 작업을 하려면 2번 "데이터 백업 및 초기화" 작업 할 때, Redis 분산 락으로 원자성을 보장해야 합니다. 구현 복잡성과 시간 제약으로 현재는 RDB로 구현했지만 이런 아키텍처 설계를 고민해보는 시간이었습니다.