Consumer Internals
컨슈머는 토픽의 메시지를 읽어 처리하는 클라이언트이며, 내부적으로는 컨슈머 그룹이라는 개념을 통해 확장성과 안정성을 보장받는다.
그룹과 코디네이터
컨슈머 그룹
컨슈머 그룹은 단순히 컨슈머의 집합이 아니라, 카프카 브로커에 의해 중앙에서 관리되는 하나의 단위이다.
컨슈머(Consumer): 토픽에서 이벤트를 가져와서(Pull) 처리하는 클라이언트 애플리케이션
각 컨슈머는 자신이 읽은 이벤트의 오프셋(Offset)을 관리하며, 필요에 따라 오프셋을 커밋하여 어디까지 읽었는지 기록
오프셋 커밋 방식에는 자동 커밋(auto-commit)과 수동 커밋(manual commit)이 있으며, 일반적으로 처리 완료 후 배치 단위로 커밋하는 방식을 권장
컨슈머 그룹(Consumer Group): 동일한 목적을 위해 특정 토픽을 구독하는 컨슈머들의 집합
카프카 컨슈머의 확장성과 고가용성을 구현하는 핵심 개념
토픽의 각 파티션은 컨슈머 그룹 내 단 하나의 컨슈머에게만 할당
예시
10개의 파티션을 가진 토픽이 있다면, 한 컨슈머 그룹은 최대 10개의 컨슈머를 투입하여 병렬로 데이터를 처리 가능
더 추가하더라도 파티션 수가 부족하여 추가 컨슈머는 유휴 상태
만약 그룹 내 컨슈머 중 하나에 장애가 발생하면, 카프카는 리밸런스(Rebalance) 과정을 통해 다른 컨슈머에게 자동으로 재할당
각 그룹이 독립적인 오프셋 관리
동일한 토픽을 여러 컨슈머 그룹이 각자 독립적으로 구독 가능(동일한 이벤트를 서로 다른 목적으로 처리 가능)
그룹 코디네이터
각 컨슈머 그룹은 브로커 중 하나를 그룹 코디네이터(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.ms
poll()
호출 사이의 최대 허용 시간.만약 메시지 처리 로직이 너무 오래 걸려 이 시간을 초과하면, 컨슈머는 비정상으로 간주되어 그룹에서 이탈되고 리밸런싱 발생
데이터 페치 관련 주요 설정
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
)커밋 요청 후 응답을 기다리지 않고 즉시 다음 로직을 수행
처리량은 높지만 커밋 실패 시 재시도가 복잡하고, 순서가 중요한 경우 주의 필요
Last updated
Was this helpful?