Consumer Internals
컨슈머는 토픽의 메시지를 읽어 처리하는 클라이언트이며, 내부적으로는 컨슈머 그룹이라는 개념을 통해 확장성과 안정성을 보장받는다.
graph TD T[Topic: 4 Partitions] --> P0[P0] T --> P1[P1] T --> P2[P2] T --> P3[P3]
subgraph CG [Consumer Group] C1[Consumer 1] C2[Consumer 2] end
P0 --> C1 P1 --> C1 P2 --> C2 P3 --> C2그룹과 코디네이터
섹션 제목: “그룹과 코디네이터”컨슈머 그룹
섹션 제목: “컨슈머 그룹”컨슈머 그룹은 단순히 컨슈머의 집합이 아니라, 카프카 브로커에 의해 중앙에서 관리되는 하나의 단위이다.
- 컨슈머(Consumer): 토픽에서 이벤트를 가져와서(Pull) 처리하는 클라이언트 애플리케이션
- 각 컨슈머는 자신이 읽은 이벤트의 오프셋(Offset)을 관리하며, 필요에 따라 오프셋을 커밋하여 어디까지 읽었는지 기록
- 오프셋 커밋 방식에는 자동 커밋(auto-commit)과 수동 커밋(manual commit)이 있으며, 일반적으로 처리 완료 후 배치 단위로 커밋하는 방식을 권장
- 컨슈머 그룹(Consumer Group): 동일한 목적을 위해 특정 토픽을 구독하는 컨슈머들의 집합
- 카프카 컨슈머의 확장성과 고가용성을 구현하는 핵심 개념
- 토픽의 각 파티션은 컨슈머 그룹 내 단 하나의 컨슈머에게만 할당
- 예시
- 10개의 파티션을 가진 토픽이 있다면, 한 컨슈머 그룹은 최대 10개의 컨슈머를 투입하여 병렬로 데이터를 처리 가능
- 더 추가하더라도 파티션 수가 부족하여 추가 컨슈머는 유휴 상태
- 만약 그룹 내 컨슈머 중 하나에 장애가 발생하면, 카프카는 리밸런스(Rebalance) 과정을 통해 다른 컨슈머에게 자동으로 재할당
- 10개의 파티션을 가진 토픽이 있다면, 한 컨슈머 그룹은 최대 10개의 컨슈머를 투입하여 병렬로 데이터를 처리 가능
- 예시
- 각 그룹이 독립적인 오프셋 관리
- 동일한 토픽을 여러 컨슈머 그룹이 각자 독립적으로 구독 가능(동일한 이벤트를 서로 다른 목적으로 처리 가능)
그룹 코디네이터
섹션 제목: “그룹 코디네이터”각 컨슈머 그룹은 브로커 중 하나를 그룹 코디네이터(Group Coordinator)로 할당받아 관리된다.
- 그룹 코디네이터(Group Coordinator)
- 각 컨슈머 그룹은 브로커 중 하나를 코디네이터로 할당
- 코디네이터는 그룹 내 컨슈머들의 상태를 추적하고, 새로운 컨슈머가 참여하거나 기존 컨슈머가 이탈할 때 파티션 재할당(리밸런싱)을 주도하는 역할
- 컨슈머의 그룹 참여 과정
- 컨슈머는 시작 시 코디네이터에게 요청을 보내 그룹에 참여
- 그룹 내 첫 번째로 참여한 컨슈머가 리더 역할 수행
- 리더는 코디네이터로부터 그룹 멤버 목록과 구독 토픽 정보를 받아 어떤 컨슈머가 어떤 파티션을 할당받을지 결정
- 리더가 결정한 할당 계획을 코디네이터에게 전달하면, 코디네이터는 이 계획을 모든 그룹 멤버에게 전파하여 각자 자신이 담당할 파티션을 인지하고 메시지 처리 시작
그룹 생명주기
섹션 제목: “그룹 생명주기”하트비트
섹션 제목: “하트비트”코디네이터는 컨슈머가 정상 동작 중인지 주기적으로 확인하며, 이를 위해 하트비트(Heartbeat) 메커니즘을 사용한다.
heartbeat.interval.ms- 컨슈머가 코디네이터에게 자신이 살아있음을 알리기 위해 하트비트를 보내는 주기
session.timeout.ms보다 반드시 낮게 설정
session.timeout.ms- 코디네이터가 컨슈머로부터 하트비트를 받지 못했을 때, 해당 컨슈머가 비정상 종료되었다고 판단하기까지 기다리는 최대 시간
- 이 시간이 초과되면 코디네이터는 해당 컨슈머를 그룹에서 제외하고 리밸런싱을 시작
리밸런싱
섹션 제목: “리밸런싱”리밸런싱은 컨슈머 그룹의 확장성과 고가용성을 위해 파티션의 소유권을 동적으로 재분배하는 과정이다.
- 리밸런싱 발생 시점
- 그룹에 새로운 컨슈머가 참여할 때
- 그룹의 기존 컨슈머가 종료되거나, 장애로 인해 세션 타임아웃을 초과할 때
- 구독 중인 토픽의 파티션 수가 변경될 때
- 리밸런싱 과정과 영향
- 리밸런싱이 발생하는 동안, 해당 컨슈머 그룹의 모든 컨슈머는 일시적으로 메시지 처리를 중단
- 이 중단 시간을 최소화하는 것이 중요
session.timeout.ms,heartbeat.interval.ms등의 설정을 통해 리밸런스 감지 민감도 조절 가능
파티션 할당 전략
섹션 제목: “파티션 할당 전략”각 컨슈머 그룹 내에서 파티션을 컨슈머들에게 어떻게 분배할지 결정하는 전략이다.
Range: 토픽별로 파티션을 연속된 범위로 계산하여 할당RoundRobin: 모든 토픽의 파티션을 모아 라운드로빈 방식으로 순서대로 할당Sticky: 기존의 파티션 할당을 최대한 유지하려 시도(리밸런싱 시 최소한의 파티션 이동만 발생시켜 안정성을 높임)CooperativeSticky:Sticky전략을 개선하여, “Stop-the-world” 없이 일부 컨슈머는 계속해서 기존 파티션을 처리하도록 허용하는 점진적 리밸런싱을 지원
데이터 수신 방식
섹션 제목: “데이터 수신 방식”컨슈머는 브로커가 밀어주는(Push) 방식이 아닌, 능동적으로 데이터를 가져오는(Pull) 모델을 사용하며, poll() 메소드를 통해 주기적으로 데이터를 요청한다.
max.poll.interval.mspoll()호출 사이의 최대 허용 시간.- 만약 메시지 처리 로직이 너무 오래 걸려 이 시간을 초과하면, 컨슈머는 비정상으로 간주되어 그룹에서 이탈되고 리밸런싱 발생
- 데이터 페치 관련 주요 설정
fetch.min.bytes: 브로커가 컨슈머에게 응답을 주기 전까지 기다리는 데이터의 최소 크기fetch.max.wait.ms:fetch.min.bytes에 도달하지 못하더라도, 브로커가 응답을 주기까지 대기하는 최대 시간max.poll.records:poll()한번의 호출로 반환받는 최대 레코드 수
트랜잭션과 데이터 격리
섹션 제목: “트랜잭션과 데이터 격리”트랜잭셔널 프로듀서가 보낸 메시지를 처리할 때, 컨슈머는 어떤 상태의 메시지까지 읽을지 결정할 수 있다.
read_uncommitted: 트랜잭션의 커밋 여부와 상관없이 모든 메시지를 읽음read_committed: 성공적으로 커밋된 트랜잭션의 메시지만 읽음
메시지 커밋 전략
섹션 제목: “메시지 커밋 전략”컨슈머가 메시지를 어디까지 처리했는지에 대한 오프셋(Offset)을 기록하는 행위를 커밋(Commit)이라고 하며, 이 커밋 전략에 따라 메시지 처리의 신뢰성 수준이 결정된다.
- 자동 커밋(
enable.auto.commit=true)poll()호출 시,auto.commit.interval.ms주기에 맞춰 이전에poll()로 반환된 마지막 오프셋을 자동으로 커밋- 메시지 처리 완료 여부와 관계없이 커밋이 발생할 수 있어 데이터 유실이나 중복의 위험 존재
- 유실: 메시지 처리에 실패했으나 다음
poll()에서 자동 커밋되면, 해당 메시지는 처리된 것으로 간주 - 중복: 메시지 처리는 성공했으나 커밋 전에 장애가 발생하면, 재시작 후 마지막 커밋 지점부터 다시 처리
- 유실: 메시지 처리에 실패했으나 다음
- 수동 커밋(
enable.auto.commit=false)- 개발자가 코드에서 명시적으로 커밋 시점을 제어하는 방식
- 동기 커밋(
commitSync)- 브로커로부터 커밋 성공 응답을 받을 때까지 블로킹
- 커밋이 확실하게 보장되어 신뢰성이 높음
- 비동기 커밋(
commitAsync)- 커밋 요청 후 응답을 기다리지 않고 즉시 다음 로직을 수행
- 처리량은 높지만 커밋 실패 시 재시도가 복잡하고, 순서가 중요한 경우 주의 필요