Kafka Consumer의 핵심 동작 구조를 한 눈에 정리합니다.
Polling 방식의 메시지 처리부터 Offset, Commit 개념과 전략까지 설명합니다.
Kafka Consumer 동작 이해하기: Polling, Offset, Commit
목차1. Kafka Consumer,Consumer Group이란?
6. 수동 동기(Manual Synchronous) Commit
1. Kafka Consumer, Consumer Group 이란?
Kafka에서 Consumer는 브로커에 저장된 메시지를 가져와 처리하는 역할을 합니다.
하나 이상의 Topic을 구독(Subscribe)할 수 있으며, 일반적으로 독립된 애플리케이션 또는 프로세스로 실행됩니다.
여러 Consumer가 함께 묶인 Consumer Group은 데이터를 병렬로 처리할 수 있도록 구성됩니다.
이 경우 각 Consumer는 중복되지 않게 파티션을 나누어 담당합니다.
2. Polling 방식의 메시지 소비 구조
Kafka의 메시지 소비는 Push가 아닌 Pull 기반입니다.
즉, Consumer가 주기적으로 poll() 메서드를 호출해 브로커로부터 데이터를 가져오는 구조입니다.
이 구조는 다음과 같은 장점을 가집니다:
- Consumer가 자신의 속도에 맞게 데이터를 가져올 수 있음
- 백프레셔(Back Pressure)나 속도 차이로 인한 장애를 줄일 수 있음
하지만 poll()을 주기적으로 호출하지 않으면 Consumer가 그룹에서 제외되고 파티션 재할당이 발생할 수 있으니 주의해야 합니다.
3. Offset이란? : 메시지 위치 추적의 핵심
Kafka에서 메시지는 각 Partition에 저장되며, 각 메시지에는 고유한 번호인 Offset이 부여됩니다.
이 Offset은 Consumer가 어디까지 메시지를 읽었는지를 나타내는 지표입니다.
예를 들어, Partition 0에서 Offset 5까지 메시지를 읽었다면, 다음에 읽을 메시지는 Offset 6입니다.
Offset은 Partition 단위로 관리되며, 서로 다른 파티션 간에는 공유되지 않습니다.
이 Offset 덕분에 Kafka는 장애가 나도 "어디까지 읽었는지"를 기억하고 이어서 메시지를 읽을 수 있습니다.
◆ Consumer 동작 시나리오
- 아래 그림은 두 개의 파티션을 가진 Kafka Topic을 나타낸 것입니다.
- 각 메시지는 0부터 7까지의 Offset을 가지고 있으며, 이는 각 파티션 내부에서만 유효한 값입니다.
- Producer가 Partition 1으로 메시지를 Push하면 해당 Partition 1의 맨 뒤에 메시지가 추가되며, Offset이 증가(8)합니다.
- Consumer가 장애로 인해 중단되거나 새로운 Consumer로 교체되더라도, 마지막으로 Commit된 Offset을 기준으로 정확히 그 위치에서 다시 시작할 수 있습니다.
Partition 0에서 Consumer가 마지막으로 Commit한 Offset은 3
Partition 1에서 Consumer가 마지막으로 Commit한 Offset은 2
◆ Consumer Group에서 offset 관리
- 아래 그림에서 Consumer Group group-A은 consumer-1과 consumer-2를 포함합니다.
- Kafka는 Partition을 자동으로 분배하여 Consumer가 서로 다른 데이터를 처리합니다.
- Kafka는 동일한 Consumer Group 내에서 Offset을 개별 관리하여 효율적으로 병렬 처리합니다.
4. Commit이란? : Offset 저장의 의미
Commit은 Consumer가 "이 Offset까지 읽었어요!"라고 Kafka에게 알려주는 작업입니다.
이 정보를 Kafka는 내부의 __consumer_offsets 토픽에 저장해 둡니다.
Commit을 하지 않으면 Consumer가 다시 시작할 때 처음부터 다시 읽게 되어 중복 소비가 발생할 수 있습니다.
반대로 너무 일찍 Commit하면, 아직 처리하지 않은 메시지가 누락될 위험이 있습니다.
◆ Commit / Offset 동작 시나리오
- poll() 메서드가 실행되면 auto commit이 발생하고, offset 값이 변경됩니다.
- auto commit은 enable.auto.commit=true로 설정되어 있을 때 자동으로 수행됩니다.
- 필요에 따라 enable.auto.commit=false로 설정해 수동 커밋 방식으로 사용할 수 있습니다.
- poll() 메서드는 마지막으로 커밋한 offset 이후의 record를 읽어옵니다.
- 읽어온 record 중 마지막 offset을 커밋합니다.
- Consumer는 주기적으로 자동으로 Offset을 Commit하도록 선택할 수도 있고, 특정한 경우에만 수동으로 Commit하도록 선택할 수도 있습니다
- 이러한 Offset Commit 방식에 따라 데이터 유실 방지, 중복 처리 방지 등 다양한 전략을 적용할 수 있습니다.
Commit은 자동과 수동 방식으로 나뉘며, 각 방식은 처리 신뢰성과 성능에서 차이가 있습니다.
5. 자동(auto) Commit
Offset Commit을 가장 간단하게 하려면 enable.auto.commit=true로 설정하면 됩니다.
Kafka Consumer는 poll() 호출 시 반환된 가장 큰 Offset을 자동으로 Commit합니다.
기본적으로 5초마다 Commit이 이루어집니다.
Commit 간격은 auto.commit.interval.ms 속성으로 조정할 수 있습니다.
Properties props = new Properties();
props.setProperty("bootstrap.servers", "localhost:9092");
props.setProperty("group.id", "test");
props.setProperty("enable.auto.commit", "true");
props.setProperty("auto.commit.interval.ms", "1000");
◆ 자동 Commit 사용시 주의할 점
- 자동 Commit을 활성화하면 Kafka Consumer 클라이언트는 poll() 메서드에서 반환된 마지막 Offset을 항상 Commit합니다.
- 이는 해당 메시지가 실제로 처리되었는지 여부와 관계없이 Commit이 이루어진다는 점에서 주의가 필요합니다.
- 만약 Consumer가 다음 자동 Commit 간격 전에 장애로 중단된다면,
→ 마지막으로 Commit된 Offset 이후부터 다시 읽게 되므로 이전에 처리한 메시지가 중복 소비될 가능성이 있습니다.
◆ 예제 시나리오
- poll() 메서드가 Offset 0~10까지의 메시지를 반환했다고 가정합니다.
- 하지만 Consumer는 Offset 5까지만 처리한 후, 자동 Commit 간격이 지나기 전에 장애로 인해 중단되었습니다.
- 다시 시작하면, 마지막으로 Commit된 Offset이 10으로 표시되므로, 11부터 메시지를 읽기 시작합니다.
- 이로 인해 Offset 6~10 사이의 메시지가 유실되는 문제가 발생합니다.
◆ 해결방법
- 자동 Commit을 사용할 경우, poll() 메서드를 호출하기 전에 반드시 모든 메시지를 처리한 후 호출해야 합니다.
- 그렇지 않으면 처리되지 않은 메시지들이 유실될 위험이 있습니다.
- Kafka Consumer는 개발자가 직접 Offset Commit 시점을 제어할 수 있도록 API를 제공합니다.
- 자동 Commit을 비활성화하려면 enable.auto.commit=false로 설정하면 됩니다.
- 이렇게 하면, 개발자가 메시지를 정상적으로 처리한 후 직접 Commit할 수 있어 데이터 유실 및 중복 처리를 방지할 수 있습니다.
6. 수동 동기(Manual Synchronous) Commit
자동 Commit을 비활성화하기 위해 enable.auto.commit=false로 설정한 후,
명시적으로 consumer.commitSync() 메서드를 호출하여 Offset을 Commit하는 방식입니다.
◆ 동작방식
- poll() 메서드가 여러 메시지를 반환하면,
- 모든 메시지를 처리한 후 consumer.commitSync()를 호출하여 마지막 Offset을 Commit합니다.
- 이 방식에서는 Commit을 직접 제어할 수 있기 때문에, 데이터 유실 및 중복 처리를 방지할 수 있습니다.
◆ 주의할 점
- commitSync()를 호출하는 시점은 모든 메시지를 정상적으로 처리한 후여야 합니다.
- 만약 메시지 처리가 완료되기 전에 Commit하면 미처리된 메시지가 유실될 위험이 있습니다.
- commitSync() 메서드는 Kafka 브로커로부터 응답을 받을 때까지 애플리케이션이 블로킹됩니다.
- 즉, Commit 요청이 완료될 때까지 애플리케이션이 대기해야 하므로, 처리 속도(Throughput)가 저하될 가능성이 있습니다.
7. 수동 비동기(Manual Asynchronous) Commit
비동기 Commit을 사용하면 Commit 요청을 보내고 즉시 다음 작업을 수행할 수 있어 애플리케이션이 블로킹되지 않습니다.
이를 위해 consumer.commitAsync() 메서드를 사용합니다.
◆ 동작방식
- poll() 메서드가 여러 메시지를 반환하면,
- 모든 메시지를 처리한 후 consumer.commitAsync()를 호출하여 Offset Commit 요청을 Kafka에 보냅니다.
- Kafka 브로커의 응답을 기다리지 않고, 다음 작업을 즉시 수행할 수 있습니다.
◆ 장점
- 애플리케이션이 Commit 응답을 기다리지 않기 때문에 처리 속도(Throughput)가 향상됩니다.
- 병렬로 여러 Commit 요청을 보낼 수 있어 성능 최적화에 유리합니다.
◆ 단점
- Commit 요청이 실패해도 자동으로 재시도 되지 않으며, Kafka는 마지막 성공한 Commit 이후부터 데이터를 읽기 시작하기 때문에 중복 처리될 가능성이 있습니다.
- 이전 Commit이 실패했는지 확인할 방법이 없으며, Kafka는 비동기 Commit의 성공 여부를 추적하지 않기 때문에 중복 데이터 처리를 피하기 어렵습니다.
8. 동기 Commit vs 비동기 Commit
◆ 동기 Commit vs 비동기 Commit
동기 Commit : commitSync | 비동기 Commit: commitAsync | |
처리 방식 | Commit이 완료될 때까지 대기 | Commit 요청을 보내고 바로 다음 작업 수행 |
애플리케이션 블로킹 여부 | 블로킹 (성능 저하 가능) | 비블로킹 (성능 최적화 가능) |
에러 발생 시 동작 | 자동으로 재시도 (치명적 오류 제외) | 재시도 없음, 다음 Commit이 실패한 부분을 보완 |
중복 처리 가능성 | 낮음 | 가능성 있음 (Commit 실패 시 중복 발생 가능) |
- 데이터 유실이 절대 허용되지 않는 경우 → commitSync() (신뢰성이 높지만 성능 저하 가능)
- 고속 데이터 처리(Throughput)가 중요한 경우 → commitAsync() (성능 최적화, 하지만 중복 발생 가능)
- 일반적으로 commitAsync()를 사용하여 성능을 최적화하고,특정 상황에서 commitSync()를 사용하여 신뢰성을 보장하는 전략을 취할 수 있습니다.
Properties props = new Properties();
props.setProperty("bootstrap.servers", "localhost:9092");
props.setProperty("group.id", "test");
props.setProperty("enable.auto.commit", "false"); // 수동 Commit 설정
KafkaConsumer<Integer, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("my-topic"));
try {
while (true) {
ConsumerRecords<Integer, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<Integer, String> record : records) {
System.out.println("Received: " + record.value() + " (offset: " + record.offset() + ")");
}
consumer.commitAsync(); // 비동기 Commit
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
consumer.commitSync(); // 종료 전 동기 Commit으로 마무리
} finally {
consumer.close();
}
}
Kafka Consumer의 동작 방식은 polling, offset, commit 세 가지 개념을 이해하면 어렵지 않습니다.
Offset은 메시지 위치 추적의 핵심이며, Commit은 그 위치를 저장하는 장치입니다.
자동과 수동 커밋 방식은 처리 신뢰성과 성능 사이에서 전략적으로 선택해야 합니다.
Kafka를 사용하는 모든 시스템에서 안정적인 메시지 처리를 위해 이 구조는 꼭 이해하고 있어야 합니다.
관련 글 링크
2025.03.28 - [1.시스템&인프라/Apache Kafka] - 2. Kafka 단일 노드 동작 원리: 파티션 분배부터 Consumer 전략까지
'1.시스템&인프라 > Apache Kafka' 카테고리의 다른 글
10. Kafka 3.9 KRaft 모드 설치 (JDK 17 + 단일 노드 구성) (0) | 2025.03.31 |
---|---|
9. Kafka 하드웨어 요구사항 및 JVM 옵션 정리: KRaft 모드 (0) | 2025.03.31 |
7. Kafka Producer acks 설정 및 동작 이해하기: 데이터 유실 방지 (0) | 2025.03.31 |
6. Kafka 프로듀서 파티션 할당 방식(Round Robin vs Sticky 비교) (0) | 2025.03.28 |
5. Kafka 리더 장애 발생 시 Failover를 위한 Producer와 Consumer 설정 (0) | 2025.03.28 |