1.시스템&인프라/Apache Kafka

8. Kafka Consumer 동작 이해하기: Polling, Offset, Commit

쿼드큐브 2025. 3. 31. 13:38
728x90
반응형

Kafka Consumer의 핵심 동작 구조를 한 눈에 정리합니다.
Polling 방식의 메시지 처리부터 Offset, Commit 개념과 전략까지 설명합니다.

 

Kafka Consumer 동작 이해하기: Polling, Offset, Commit

 

목차

1. Kafka Consumer,Consumer Group이란?

2. Polling 방식의 메시지 소비 구조

3. Offset이란? : 메시지 위치 추적의 핵심

4. Commit이란? : Offset 저장의 의미

5. 자동(auto) Commit

6. 수동 동기(Manual Synchronous) Commit

7. 수동 비동기(Manual Asynchronous) Commit

8. 동기 Commit vs 비동기 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 이후부터 다시 읽게 되므로 이전에 처리한 메시지가 중복 소비될 가능성이 있습니다.

  예제 시나리오

  1. poll() 메서드가 Offset 0~10까지의 메시지를 반환했다고 가정합니다.
  2. 하지만 Consumer는 Offset 5까지만 처리한 후, 자동 Commit 간격이 지나기 전에 장애로 인해 중단되었습니다.
  3. 다시 시작하면, 마지막으로 Commit된 Offset이 10으로 표시되므로, 11부터 메시지를 읽기 시작합니다.
  4. 이로 인해 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하는 방식입니다.

 

  동작방식

  1. poll() 메서드가 여러 메시지를 반환하면,
  2. 모든 메시지를 처리한 후 consumer.commitSync()를 호출하여 마지막 Offset을 Commit합니다.
  3. 이 방식에서는 Commit을 직접 제어할 수 있기 때문에, 데이터 유실 및 중복 처리를 방지할 수 있습니다.

  주의할 점

  • commitSync()를 호출하는 시점은 모든 메시지를 정상적으로 처리한 후여야 합니다.
  • 만약 메시지 처리가 완료되기 전에 Commit하면 미처리된 메시지가 유실될 위험이 있습니다.
  • commitSync() 메서드는 Kafka 브로커로부터 응답을 받을 때까지 애플리케이션이 블로킹됩니다.
  • 즉, Commit 요청이 완료될 때까지 애플리케이션이 대기해야 하므로, 처리 속도(Throughput)가 저하될 가능성이 있습니다.

 

7. 수동 비동기(Manual Asynchronous) Commit

비동기 Commit을 사용하면 Commit 요청을 보내고 즉시 다음 작업을 수행할 수 있어 애플리케이션이 블로킹되지 않습니다.

이를 위해 consumer.commitAsync() 메서드를 사용합니다.

 

  동작방식

  1. poll() 메서드가 여러 메시지를 반환하면,
  2. 모든 메시지를 처리한 후 consumer.commitAsync()를 호출하여 Offset Commit 요청을 Kafka에 보냅니다.
  3. 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 전략까지

 

728x90
반응형