카프카

[Kafka] Parallel-Consumer을 통한 알림 성능 개선 과정(2)

27200 2026. 3. 21. 21:48
‼️💡 최종 개선 결과 💡‼️

- 초당 처리량 362.12건 → 6,767.27건
- 약 18.7배 성능 향상

총 3편의 내용으로 구성된 개선 과정이다.

더보기

3차 구현

조회는 단일 서버, 처리는 Kafka Consumer 분산 처리

 

2차 구현에서는 target 단위 Redis 분산 락을 통해 중복 처리를 줄였지만,

여전히 모든 서버가 동시에 due target을 조회한다는 문제가 남아 있었다.

즉 처리 중복은 줄일 수 있었지만, 조회 부하는 서버 수만큼 반복될 수 있었다.

이 문제를 해결하기 위해 3차 구현에서는 역할을 다음과 같이 분리했다.

구현 방식

1. 조회 단계

  • 스케줄러가 due target을 조회한다.
  • 이 단계는 Redis 기반 전역 분산 락을 통해 오직 한 서버만 수행한다.

2. 메시지 적재 단계

  • 조회된 target을 Kafka 이벤트로 발행한다.
  • 각 target은 개별 메시지로 큐에 적재된다.

3. 처리 단계

  • 여러 서버에 떠 있는 Kafka consumer가 메시지를 분산 소비한다.
  • 각 consumer는 자신이 가져온 메시지에 대해서만 DB 저장 및 상태 갱신을 수행한다.

이 구조의 장점은 다음과 같다.

  • due target 조회는 한 서버만 수행하므로 DB 조회 중복이 제거된다.
  • 실제 처리 단계는 Kafka consumer group을 통해 여러 서버가 병렬 처리할 수 있다.
  • 처리량 확장이 쉽다.
  • target별 분산 락 없이도 consumer group 기준으로 메시지 분산 처리가 가능하다.

다만 다음과 같은 보완은 여전히 필요하다.

  • consumer 장애 시 재처리 전략
  • 발행 실패/소비 실패에 대한 DLQ 처리

정리하면, 3차 구현은 다음과 같은 방향이다.

  • 조회: 전역 Redis 락으로 단일 서버만 수행
  • 처리: Kafka consumer group으로 분산 처리
  • 확장성: 서버 수 증가에 따라 처리량 확장 가능
  • 중복 방지: 조회 단계는 분산 락, 처리 단계는 queue consumer로 처리

보완점이 분명 존재하지만 이는 카프카 자체에 대한 에러 핸들링으로 추후 고려하면 큰 문제가 되지 않을 것이라고 판단하였다.


4차 구현

여러 가지 문제가 해결되었다.

  1. 하나의 target에 대한 단일 알림이 보장된다.
  2. 중복 조회를 하지 않아 db 부하가 감소한다.
  3. 배치 처리를 통해 db 조회를 효율적으로 진행한다.(주요 흐름은 아니라 별도 기술하지는 않았다.)
  4. 컨슈머 그룹 내의 파티션 단위로 병렬 처리가 가능하다.

테스트 및 문제

그렇게 기능 구현이 완료되었다 생각했고, 테스트를 진행해 보았다.

[GREENROOM KAFKA BENCHMARK] 
eligible=100000, 
outboxCreated=100000, 
eventPublishSec=0.578,  
totalSec=276.153, 
msgsPerSecond=362.12

이벤트를 발행하는 시간은 크게 소요되지 않았지만, 이를 처리하고 데이터베이스에 적재하는 작업까지의 시간이 오래 소요되었다.

 

10만 건의 알림을 처리하는 데 있어 약 5분이 소요된 것이다.

 

즉, 362.12 * 60 인 약 18000명 정도의 유저만 정각에 알림을 받고, 지연이 발생하는 문제가 있었다.

db저장이 제일 큰 병목이었기에 이를 비동기로 처리할까 했지만, 사용자가 db 조회를 통해 알림 정보를 확인하는 만큼 결과적으로 똑같을 것이라 판단하였다.

문제 해결

기존 Kafka consumer 구현은 메시지를 하나 consume할 때마다 즉시 DB에 저장하는 구조였다.

이 방식은 구현은 단순하지만, 메시지 수가 많아질수록 insert/update/flush가 매우 자주 발생하여 DB 부하가 커질 수 있다.

이를 줄이기 위해 Kafka consumer에서 메시지를 바로 저장하지 않고, 일정 개수만큼 모아서 배치 저장하는 방식을 적용했다.

 

처리 방식은 다음과 같다.

  1. Kafka consumer가 메시지를 consume한다.
  2. consume한 메시지를 메모리 버퍼에 적재한다.
  3. 버퍼에 200건이 쌓이면 한 번에 DB 저장을 수행한다.
  4. 메시지가 더 이상 들어오지 않는 idle 상태가 되면, 200건이 되지 않았더라도 남은 메시지를 저장한다.

이 방식의 장점은 다음과 같다.

  • 메시지마다 DB 쓰기를 수행하지 않으므로 DB 부하를 줄일 수 있다.
  • 마지막에 남은 소량의 데이터도 idle flush로 유실 없이 처리할 수 있다.

여기서 idle 상태란 다음을 의미한다.

  • 마지막 메시지를 consume한 이후 일정 시간 동안 새로운 메시지가 들어오지 않는 상태

예를 들어 마지막 메시지를 받은 뒤 1초 동안 새 메시지가 없다면,

버퍼에 200건이 모이지 않았더라도 남은 데이터를 모두 DB에 저장한다.

흐름 및 테스트

[GREENROOM KAFKA BATCH BENCHMARK] 
eligible=100000, 
outboxCreated=100000, 
eventPublishSec=0.649, 
totalSec=38.944, 
msgsPerSecond=2567.79

정말 끝?

거의 모든 문제가 해결되었다.

하지만 이러한 궁금증이 남았다. 10만 건의 알림이 아니라, 더 많은 알림을 전송해야 한다면 어떻게 해야 할까?

단순하게 생각하면 해결책은 쉬워 보인다.

  • Kafka 토픽의 파티션 수를 늘린다.
  • 이를 처리할 서버 수를 늘린다.
  • 서버를 늘릴 수 없다면, 하나의 서버에서 더 많은 스레드로 여러 파티션을 처리한다.

하지만 파티션을 추가하는 것은 생각보다 단순한 작업이 아니다.

파티션 수를 늘리면 처리량과 병렬성은 높일 수 있지만, 동시에 다음과 같은 문제를 같이 고려해야 한다.

1. 메시지 순서 보장 문제가 달라진다

Kafka는 파티션 내부에서만 순서를 보장한다.

즉 파티션 수를 늘리면, 같은 키를 가진 메시지가 새로운 파티션 정책에 따라 다른 파티션으로 분산될 수 있고, 이 경우 기존과 같은 순서 보장을 기대하기 어려워질 수 있다.

특히 기존에 key 기반 파티셔닝을 사용하고 있었다면, 파티션 수를 변경하는 순간 같은 key의 메시지가 이전과 다른 파티션으로 라우팅 될 수 있다.

Confluent 문서도 파티션 수를 증가시킬 때 keyed message의 ordering guarantee에 주의해야 한다고 설명하고 있다.

2. Consumer 수를 늘린다고 무조건 성능이 선형 증가하지 않는다

Kafka에서 병렬성의 기본 단위는 partition이다.

즉 파티션이 5개면 같은 consumer group 안에서 동시에 의미 있게 일하는 consumer도 최대 5개다.

그 이상 consumer를 늘려도 일부는 idle 상태가 된다.

Apache Kafka 공식 문서도 consumer 수가 partition 수보다 많으면 일부 consumer는 아무 일도 하지 않게 된다고 설명한다.

즉 단순히 서버나 스레드를 늘리는 것만으로는 성능이 무한히 늘어나지 않는다.

3. 파티션 수가 많아질수록 운영 비용도 증가한다

파티션은 단순한 논리 단위가 아니라, 브로커 입장에서는 메타데이터, 파일 핸들, 세그먼트 관리, 복제 부담을 모두 증가시키는 대상이다.

즉 파티션 수가 너무 많아지면 브로커 메모리 사용량, 네트워크 I/O, 디스크 I/O, 리밸런싱 비용이 커질 수 있다.

Confluent 문서에서도 파티션은 throughput을 높이는 수단이지만, producer/consumer/broker 설정과 처리 로직을 함께 고려해야 한다고 설명한다.

4. Rebalancing 비용이 커질 수 있다

파티션 수를 늘리거나 consumer 수를 바꾸면 consumer group rebalancing이 발생한다.

이 과정에서 일시적으로 소비 지연이 생길 수 있고, 처리 중단 구간이 길어질 수 있다.

특히 대규모 토픽과 많은 consumer가 붙은 환경에서는 리밸런싱 자체가 운영상 부담이 될 수 있다.

5. 파티션 증설은 쉽지만, 되돌리기는 쉽지 않다

Kafka는 보통 기존 토픽의 파티션 수를 늘릴 수는 있어도 줄이기는 어렵다.

즉 처음에 너무 단순하게 파티션을 늘렸다가, 이후 운영상 부담이 커져도 쉽게 되돌릴 수 없는 경우가 많다.

Confluent Cloud FAQ에서도 기존 토픽의 partition 수는 증가 가능하지만 감소는 불가능하다고 설명한다.