본문 바로가기

Kafka, RabbitMQ

카프카(kafka)에 대해 알아보자 - (5) 카프카 딥다이브 (프로듀서, 컨슈머)

들어가기 앞서

이번 포스팅에서는 프로듀서컨슈머에 대해 어떻게 운영환경에서 활용할 수 있는지 알아볼 예정입니다. 책의 챕터 4에 해당하니 참고 해주시면 감사하겠습니다.

 

카프카에 대해 기본적인 공부를 위해 방문하신 분들은 아래 글 추천 드립니다.

카프카(kafka)에 대해 알아보자

카프카(kafka)에 대해 알아보자 - (1) 카프카의 기본 개념 (브로커, 토픽, 파티션, 레코드)

카프카(kafka)에 대해 알아보자 - (2) 카프카의 기본 개념 (프로듀서, 컨슈머)

카프카(kafka)에 대해 알아보자 - (4) 카프카 딥다이브 (토픽, 파티션)

 

https://www.yes24.com/product/goods/99122569

 

아파치 카프카 애플리케이션 프로그래밍 with 자바 - 예스24

아파치 카프카 애플리케이션 개발을 위한 「실전 가이드」아파치 카프카란 무엇일까? 카프카 애플리케이션은 어떻게 만들까? 데이터 파이프라인을 만들기 위해 어떤 카프카 라이브러리를 사용

www.yes24.com


프로듀서 (Producer)

Kafka는 대용량 데이터를 안정적으로 처리하기 위해 널리 사용되는 메시지 큐 시스템이다. 이 시스템에서 프로듀서는 데이터를 Kafka에 전달하는 첫 번째 주체이므로, 프로듀서 설정은 시스템 전체의 안정성과 성능에 매우 큰 영향을 준다.

 

Kafka 클러스터는 보통 3대 이상의 브로커로 구성된다. 이처럼 다수의 브로커를 사용하는 이유는 특정 브로커에 장애가 발생하더라도 데이터 유실을 방지하고 서비스의 연속성을 보장하기 위해서이다. 하지만 클러스터만으로는 데이터 안정성이 완전히 보장되지 않으며, 프로듀서 쪽에서도 적절한 설정이 뒷받침되어야 한다.

 

이 글에서는 Kafka 프로듀서를 사용할 때 반드시 알고 있어야 할 acks 옵션, 멱등성 프로듀서, 트랜잭션 프로듀서에 대해 설명한다.


acks 옵션

프로듀서의 acks 옵션은 전송한 데이터가 Kafka 클러스터에 얼마나 안전하게 저장되었는지를 결정하는 중요한 설정이다. 성능과 안정성 사이에서 어떤 기준으로 동작할지를 선택하는 것이라 볼 수 있다. 설정값으로는 0, 1, all(-1)이 있으며, 각각의 차이는 다음과 같다.

1. ack = 0

acks를 0으로 설정하면, 프로듀서는 리더 파티션으로 데이터를 전송한 후 저장이 완료되었는지 확인하지 않고 다음 데이터를 계속 보낸다. 응답 자체를 받지 않기 때문에 전송 실패 여부를 알 수 없고, retries 옵션을 설정해도 재시도가 이루어지지 않는다. 이 방식은 네트워크 오류나 브로커 장애로 인한 데이터 유실 가능성이 매우 높다. 다만 확인 절차 없이 계속 데이터를 보내므로 전송 속도는 매우 빠르다. 데이터 유실이 허용되는 상황에서 고려해볼 수 있다.

ack=0 옵션은 데이터가 파티션에 저장되었는지에 대한 여부를 받지 않는다

 

2. ack = 1

acks를 1로 설정하면, 프로듀서는 리더 파티션에 데이터가 정상적으로 저장되었는지를 확인한다. 이 설정은 리더 브로커의 저장 여부만 확인하므로, 데이터가 팔로워 파티션에 복제되지 않은 시점에 리더 브로커가 장애를 일으키면 해당 데이터는 유실될 수 있다. 전송 속도는 ack=0보다 느리지만, 최소한의 안정성은 확보할 수 있다. 대부분의 일반적인 서비스에서는 이 설정으로도 충분한 경우가 많다.

ack=1 옵션은 최소한 리더 파티션에 데이터가 저장된 것을 보장한다

 

3. ack = all 또는 -1

acks를 all로 설정하면, 프로듀서는 리더 파티션뿐만 아니라 동기화가 완료된 팔로워 파티션까지 모두 저장되었는지 확인한 후 응답을 받는다. 이는 가장 높은 수준의 신뢰도를 보장하지만, 동기화까지 기다리기 때문에 가장 느리다.

ack=-1 옵션은 모든 파티션에 데이터가 저장된 것을 보장한다

 

특히 이 설정을 사용할 경우 주의해야 할 점이 하나 있다. 토픽 단위로 설정할 수 있는 min.insync.replicas 옵션 값이 이를 만족하지 못하면 NotEnoughReplicasException 또는 NotEnoughReplicasAfterAppendException이 발생하여 데이터 전송이 실패하게 된다. 예를 들어 브로커가 3대인 경우 min.insync.replicas를 3으로 설정하면, 브로커 하나만 다운돼도 데이터 전송이 불가능해진다. 일반적으로 브로커 수보다 낮은 값을 설정하는 것이 안전하다. 

 

안정적인 운영을 위해서는 다음과 같은 구성을 추천할 수 있다.

  • 브로커 수: 3대
  • 복제 개수: 3
  • min.insync.replicas: 2
  • 프로듀서 acks: all

멱등성 프로듀서 (Idempotent Producer)

멱등성 프로듀서는 프로듀서가 여러번 전송하되 브로커가 여러번 전송된 데이터를 확인하고 중복된 데이터는 적재하지 않는 다는 것이다. Kafka는 기본적으로 at-least-once 메시지 전달 방식을 사용한다. 이는 데이터 유실은 막을 수 있지만, 네트워크 재시도 등으로 인해 중복 메시지가 발생할 수 있다는 단점이 있다.

멱등성 프로듀서를 적용하지 않으면, 네트워크 단절등 장애로 인해 데이터 중복이 발생할 수 있다.

 

이를 방지하기 위해 Kafka 0.11.0 이후 버전에서는 멱등성 프로듀서를 도입했다. 멱등성은 같은 메시지를 여러 번 보내더라도 브로커에는 한 번만 저장되도록 보장한다. 이 기능을 사용하려면 enable.idempotence 옵션을 true로 설정하면 된다. 기본값은 false이므로 명시적으로 설정해야 한다.

 

멱등성 프로듀서는 메시지를 전송할 때마다 고유한 Producer ID(PID)와 시퀀스 넘버를 포함해 보낸다. 브로커는 이를 통해 같은 메시지가 중복으로 저장되지 않도록 처리한다. 주의할 점은 멱등성이 동일 세션에서만 보장된다는 것이다. 애플리케이션이 재시작되면 PID가 새로 부여되기 때문에 중복 방지가 적용되지 않는다. 따라서 멱등성 프로듀서는 애플리케이션이 정상적으로 실행 중인 상태에서만 중복을 방지할 수 있다.

멱등성 프로듀서를 적용하면, PID와 시퀀스 번호를 통해 데이터 중복을 방지할 수 있다.

 

이 옵션을 true로 설정하면 Kafka는 다음과 같은 설정을 강제로 적용한다.

  • acks = all
  • retries = Integer.MAX_VALUE

이는 "한 번만 전송"하는 게 아니라, 브로커가 여러 번 전송된 메시지를 구분해서 하나만 저장하기 위한 기반 설정이다. 시퀀스 넘버는 0부터 시작해서 매번 1씩 증가하며, 브로커는 이 값의 연속성을 확인한다. 만약 예기치 않게 시퀀스 넘버가 어긋난 메시지가 도착하면 예외가 발생할 수 있어, 순서가 중요한 데이터를 전송하는 프로듀서는 해당 Exception이 발생했을 경우 대응 방안을 고려해야 한다.


트랜잭션 프로듀서 (Transactional Producer)

Kafka 트랜잭션은 복수의 파티션에 데이터를 보낼 때, 해당 데이터를 하나의 원자 단위로 처리하도록 도와준다. 즉, 일부 파티션에만 저장되는 일이 없도록 보장하는 기능이다.

 

트랜잭션을 사용하면 메시지를 전송한 이후 전체를 커밋하거나, 전체를 롤백할 수 있다. 이를 위해서는 멱등성 설정이 반드시 선행되어야 하며, 아래 두 가지가 필요하다.

  • enable.idempotence = true
  • transactional.id = 고유한 트랜잭션 ID

트랜잭션은 일반 데이터 레코드 외에 트랜잭션 레코드를 하나 더 전송한다. 이 레코드는 실제 데이터를 포함하지 않고, 트랜잭션의 종료 여부만 나타낸다. 컨슈머는 이 트랜잭션 레코드를 보고 해당 데이터가 커밋된 것인지 판단할 수 있다.

트랜잭션 프로듀서가 보낸 데이터를 구분하기 위해 commit이라는 트랜잭션 레코드를 추가로 전송한다

 

컨슈머가 트랜잭션 데이터를 정확히 처리하기 위해서는 isolation.level을 read_committed로 설정해야 한다. 이 설정을 적용하면 아직 커밋되지 않은 데이터는 컨슈머가 가져가지 않는다. 트랜잭션이 끝나야만 레코드를 읽을 수 있으므로 데이터 일관성을 확보할 수 있다.

commit 레코드가 없으면, 컨슈머는 트랜잭션이 완료되지 않았다고 판단하여 메시지를 가져가지 않는다.
컨슈머는 트랜잭션이 끝난 레코드를 가져간다


컨슈머 (Consumer)

Kafka에서 Consumer는 클러스터에 적재된 데이터를 가져와 처리하는 핵심 역할을 수행합니다. Kafka Consumer를 운영할 때 멀티 스레드 전략, Consumer Lag 모니터링, 그리고 배포 방식까지 고려해야할 내용을 공부하려고 합니다.


멀티 스레드 Consumer 전략

Kafka는 처리량 향상을 위해 파티션 수에 맞춰 Consumer 수를 늘릴 수 있습니다. 하나의 파티션은 하나의 Consumer에만 할당되므로, 병렬 처리를 위해 파티션 수만큼 Consumer 스레드를 운영하는 것이 일반적입니다. 운영 방식은 개발자의 선택에 달려 있으며, 멀티 스레드를 지원하지 않는 언어나 환경에서는 여러 개의 프로세스를 실행하는 방식을 사용할 수도 있습니다.

컨슈머 그룹 A : 하나의 프로세스와 3개의 스레드 / 컨슈머 그룹 B : 3개의 프로세스

 

1. 멀티 워커 스레드 전략

1개의 Consumer 스레드가 poll() 메서드로 데이터를 가져오고, 각 레코드를 병렬로 처리하기 위해 별도의 Worker 스레드를 실행하는 방식입니다. 데이터를 for 반복문으로 순차 처리하는 대신, 각 레코드를 개별 스레드에서 처리하여 전체 처리 속도를 향상시킬 수 있습니다. 처리 시간이 긴 레코드가 많을 경우에 특히 효과적입니다.

병렬 처리를 활용한다면 전체적인 속도를 향상 시킬수 있다.

 

ExecutorService를 활용하여 스레드를 효율적으로 관리하며, 특히 newCachedThreadPool을 사용하면 짧은 생명주기의 스레드 실행에 적합합니다. ConsumerWorker 클래스는 Runnable 인터페이스를 구현하고, run() 메서드에 레코드 처리 로직을 작성하면 됩니다.

컨슈머 멀티 워커 스레드 전략

 

주의사항

  • 스레드에서 처리 완료 전 poll()이 호출되고 auto-commit 설정이 되어 있다면 커밋 타이밍 이슈로 데이터 유실이 발생할 수 있습니다.
  • 병렬 처리 시 처리 순서가 뒤바뀔 수 있으므로 순서가 중요한 데이터에는 적합하지 않습니다.

이 전략은 처리 속도가 중요한 서버 리소스 모니터링, IoT 센서 데이터 수집 파이프라인 등에 적합합니다. 금융권처럼 순서 보장이 중요한 환경에서는 주의가 필요합니다.

 

2. Consumer 멀티 스레드 전략

각 스레드가 독립된 KafkaConsumer 인스턴스를 가지고 poll()을 수행하는 방식입니다. 구독하려는 토픽의 파티션 수만큼 Consumer 스레드를 생성하는 것이 이상적입니다.

파티션 개수만큼 컨슈머 스레드를 운영

 

각 Consumer 스레드는 서로 다른 파티션을 할당받아 데이터를 병렬로 처리할 수 있습니다. 단, Consumer 스레드 수가 파티션 수보다 많으면 일부 스레드는 파티션을 할당받지 못해 유휴 상태가 됩니다. 따라서 적절한 스레드 수를 조정하는 것이 중요합니다.


Consumer Lag (컨슈머 랙)

Consumer Lag은 Producer가 전송한 최신 오프셋과 Consumer가 마지막으로 커밋한 오프셋 간의 차이로, Consumer가 정상적으로 데이터를 처리하고 있는지 확인할 수 있는 중요한 지표입니다.

컨슈머 랙은 가장 최신 오프셋과 컨슈머 오프셋 간의 차이다.
파티션의 개수 만큼 Lag이 있다.

 

  • Producer의 전송량 > Consumer의 처리량 → Lag 증가
  • Consumer의 장애 또는 성능 저하 → 특정 파티션에서 Lag 증가

Consumer Lag은 Consumer Group, Topic, Partition 단위로 각각 생성됩니다. 예를 들어 3개의 파티션을 가진 토픽을 1개의 Consumer Group이 구독하면 총 3개의 Consumer Lag이 발생합니다.

 

모니터링 방법

  1. Kafka 명령어 : 단발성 확인
  2. metrics() 메서드 : 프로세스 종료 시 모니터링 불가
  3. 외부 모니터링 툴 : 지속적 확인 가능 / Burrow, Datadog, Confluent Control Center

예를 들어, 명절과 같이 사용자 데이터가 급증하는 시점에는 Producer의 데이터양이 급증하므로 Consumer Lag이 발생할 수 있습니다. 이때는 일시적으로 파티션과 Consumer 수를 늘려 병렬처리량을 확보하는 것이 효과적입니다.


 Kafka Burrow

Burrow는 LinkedIn에서 만든 오픈소스 Consumer Lag 모니터링 도구로, 여러 Kafka 클러스터의 Consumer 상태를 REST API로 조회할 수 있습니다. HTTP 알람, 이메일 알람을 기본으로 지원하지만, 과거 데이터를 저장하지 않기 때문에 별도 저장소 및 대시보드 구축이 필요합니다.

 

Burrow의 핵심 기능은 단순 임계치 비교가 아닌, 슬라이딩 윈도우 계산을 통한 Consumer 상태 평가입니다.

  • 파티션 상태: OK / STALLED / STOPPED
  • Consumer 상태: OK / WARNING / ERROR

임계치를 넘는다고 즉시 알람을 보내지 않으며, 일정 시간 동안의 변화 추이를 바탕으로 상태를 판단합니다. 이는 알람 노이즈를 줄이고 의미 있는 경보만을 제공하기 위함입니다.


Consumer 배포 전략

Kafka Consumer 애플리케이션은 내부 로직의 변경이나 기능 추가 등으로 인해 배포가 빈번히 발생합니다. 이때 선택할 수 있는 배포 방식은 크게 두 가지입니다.

 

중단 배포

  • 기존 Consumer 애플리케이션을 완전히 종료한 후 새 버전을 배포
  • 서버 자원이 한정적인 환경에서 유리
  • 오프셋 기준이 명확해 롤백이 쉬움
  • 단점: 배포 지연 시 Consumer Lag이 급증하고 파이프라인 전체가 중단될 수 있음

 

무중단 배포

중단이 허용되지 않는 환경에서는 무중단 배포가 필수입니다. 가상 서버 환경에서 인스턴스 발급이 자유로운 경우에 적합하며, 대표적인 방식은 다음과 같습니다.

  1. 블루/그린 배포
    • 현재 버전과 신규 버전을 동시에 띄우고, 트래픽을 전환
    • 리밸런스가 1회만 발생하여 빠른 배포가 가능함
  2. 롤링 배포
    • 전체 인스턴스를 순차적으로 교체하며 배포
    • 리밸런스가 여러 번 발생하므로, 파티션 수가 적을 때 효과적
  3. 카나리 배포
    • 전체 중 일부 파티션에만 신규 버전 배포하여 문제 유무를 사전 탐지
    • 문제 없으면 롤링 또는 블루/그린 배포로 확대 가능

카프카를 운영하면서 배포 방식은 매우 중요한 요소라고 생각합니다. 리밸런싱도 중요하지만, 배포 방식에 따라 컨슈머 간의 버전 차이가 발생할 수 있기 때문에 이러한 차이를 허용할지 여부를 명확히 결정하는 것이 필요합니다. 만약 컨슈머 간의 버전 차이를 허용하지 않는 정책을 선택한다면, 롤링 배포 환경에서 이러한 차이를 어떻게 해소할 것인지에 대한 고민과 구체적인 수단이 함께 마련되어야 합니다.