Apache Kafka 고성능 비결 및 면접 대비

Apache Kafka의 성능을 극대화하는 기술적 원리와 면접에서 자주 등장하는 핵심 질문들을 다룹니다.

1. Kafka는 왜 빠른가? (필수 질문!)

핵심 기술:
  • 순차 디스크 I/O: 로그 파일에 데이터를 순차적으로 추가하여 디스크 헤드의 무작위 탐색을 최소화합니다. 이는 테이프에 데이터를 쓰는 것과 유사한 방식입니다.
  • Zero-Copy 기술: sendfile 시스템 호출을 사용하여 데이터가 커널의 페이지 캐시에서 직접 네트워크 인터페이스 카드로 전송됩니다. 이 과정에서 사용자 공간으로의 데이터 복사가 생략되어 I/O 효율성을 크게 높입니다.
    // Linux 시스템 호출을 활용한 Kafka의 내부 구현 (개념적)
    // 실제 구현은 C 언어로 되어 있으나, 원리는 다음과 같습니다.
    // int sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
    
  • 배치 처리:
    • Producer: 여러 메시지를 모아 배치로 전송하여 네트워크 I/O 횟수를 줄입니다.
    • Consumer: 여러 메시지를 배치로 폴링(pull)하여 처리합니다.
  • 파티션 병렬 처리: 각 파티션은 독립적으로 처리될 수 있으며, 여러 소비자가 각기 다른 파티션을 병렬로 처리하여 전체 처리량을 높입니다.
  • 페이지 캐시 최적화: 운영체제의 페이지 캐시를 적극적으로 활용하여 JVM의 가비지 컬렉션(GC)으로 인한 성능 저하를 최소화합니다.

2. Rebalance란 무엇이며, 어떤 문제가 발생할 수 있는가?

Rebalance 발생 시나리오:
  • Consumer Group 내 멤버 변경 (새로운 Consumer 추가 또는 기존 Consumer 장애)
  • 구독 중인 토픽의 파티션 수 변경
  • Consumer의 Heartbeat 타임아웃 (기본값: session.timeout.ms = 45초)
Rebalance 과정:
  1. 모든 Consumer의 메시지 소비 일시 중단
  2. 파티션 재할당 (Range 또는 Round Robin 전략 사용)
  3. 새로운 파티션 할당 계획을 모든 Consumer에게 동기화
주요 문제점:
  • 소비 중단: Rebalance가 진행되는 동안 서비스가 일시적으로 중단됩니다.
  • 메시지 중복 소비: Offset이 커밋되기 전에 Rebalance가 발생하면, 해당 메시지가 다시 처리될 수 있습니다.
  • 데이터 불균형 (Data Skew): 파티션 할당 전략이 비효율적이거나 Consumer 간 처리 능력 차이로 인해 특정 Consumer에 부하가 집중될 수 있습니다.
해결 방안 (Consumer 설정 최적화):
// Consumer 설정 예시
Properties props = new Properties();
// ... 다른 설정 ...
// poll() 호출 간 최대 허용 간격 (기본값: 5분)
// Rebalance 발생 시 소비 중단 시간을 줄이려면 이 값을 조정해야 합니다.
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, "300000");
// Consumer가 Broker에게 Heartbeat를 보내는 주기 (기본값: 3초)
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "3000");
// ...

3. 파티션 수는 어떻게 결정해야 하는가?

핵심 고려 사항:
  • 처리량 목표: 전체 처리량 = 파티션 수 × 단일 파티션 처리 능력
참고 지표:
지표 계산 방식 예시 값
Producer 처리량 최대 메시지 전송량 / 단일 파티션 처리 능력 (약 10MB/s) 100MB/s → 10 파티션
Consumer 처리량 Consumer Group 내 Consumer 수 × 단일 스레드 처리 능력 3 Consumer × 5,000 메시지/s → 총 15,000 메시지/s 처리 능력
향후 확장성 현재 필요량의 20%~50% 여유분 확보 현재 8개 필요 → 실제 12개 생성
모범 사례:
  • 초기 배포 시에는 3~6개의 파티션으로 시작하는 것을 권장합니다.
  • 단일 Broker 당 총 파티션 수는 2000개를 넘지 않도록 관리하여 파일 핸들 고갈 문제를 방지합니다.

4. 메시지 순서를 어떻게 보장할 수 있는가?

전체 순서 보장 (전체 Topic에 대한 순서, 비권장):
  • 단일 파티션 사용: 모든 메시지를 단일 파티션으로 라우팅합니다. 이는 병렬 처리를 저해하여 성능 병목 현상을 유발합니다.
  • 동기 전송 및 재시도 비활성화: Producer 설정에서 acks=all을 사용하고 재시도를 비활성화합니다.
파티션 내 순서 보장 (일반적인 해결책):
  • 메시지 키(Key) 사용: 동일한 키를 가진 메시지는 항상 같은 파티션으로 라우팅되도록 합니다. 이를 통해 특정 키(예: 사용자 ID, 주문 ID)와 관련된 메시지들의 순서를 보장할 수 있습니다.
    // 메시지 키를 지정하여 동일 파티션으로 라우팅
    kafkaTemplate.send("order-topic", order.getUserId(), orderMsg);
    
Producer 설정 최적화 (단일 파티션 내 순서 보장 강화):
// Producer 설정 예시
Properties props = new Properties();
// ... 다른 설정 ...
// 동일 커넥션 내에서 미확인 요청의 동시성을 제한 (기본값 5)
// 1로 설정하면 순차적 전송을 보장하여 단일 파티션 내 순서 보장을 강화합니다.
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, "1");
// 재시도 횟수를 최대로 설정하여 메시지 유실 방지
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
// ...

5. Exactly-Once 처리는 어떻게 구현하는가?

3단계 보장 메커니즘:
  1. Idempotent Producer (멱등성 Producer): 동일한 메시지가 여러 번 전송되더라도 Broker에서 단 한 번만 처리되도록 합니다. Producer 설정에서 enable.idempotence=true로 설정하면 내부적으로 acks=allmax.in.flight.requests.per.connection=5 (Kafka 0.11 버전 이상)가 활성화됩니다.
    // Producer 설정 예시
    Properties props = new Properties();
    // ...
    // 멱등성 Producer 활성화 (Kafka 0.11 이상)
    props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
    // ...
    
  2. Transactional Messages (트랜잭션 메시지): 여러 토픽에 걸쳐 또는 Producer와 Consumer 간의 작업을 원자적으로 처리할 수 있게 합니다.
    // Producer 측 트랜잭션 사용 예시
    kafkaTemplate.executeInTransaction(transactionTemplate -> {
        transactionTemplate.send("topic1", msg1);
        transactionTemplate.send("topic2", msg2);
        return null; // 성공 시 null 반환
    });
    
    // Consumer 측 읽기 격리 수준 설정
    Properties props = new Properties();
    // ...
    // 읽기 격리 수준 설정: read_committed (트랜잭션 메시지 포함) 또는 read_uncommitted (기본값)
    props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");
    // ...
    
  3. External Deduplication (외부 저장소를 통한 중복 제거): 최종적인 데이터 정합성을 위해 데이터베이스 등의 외부 시스템에서 중복을 제거하는 메커니즘을 추가합니다.
    -- 예시: 데이터베이스 고유 제약 조건을 활용한 중복 제거
    CREATE TABLE processed_messages (
        message_unique_id VARCHAR(255) PRIMARY KEY,
        processed_timestamp TIMESTAMP
    );
    -- 메시지 처리 시 INSERT ... ON CONFLICT ... 등을 사용하여 중복 삽입 방지
    

6. Consumer Offset 커밋 방식에는 어떤 차이가 있는가?

커밋 방식 자동 커밋 (Auto Commit) 수동 커밋 (Manual Commit)
설정 파라미터 enable.auto.commit=true enable.auto.commit=false
커밋 시점 주기적으로 자동 커밋 (기본값: 5초) Consumer가 명시적으로 commitSync() 또는 commitAsync() 호출 시
메시지 유실 위험 자동 커밋 후 Consumer 장애 발생 시, 커밋된 이후 메시지 유실 가능성 있음 커밋 시점 이전 Consumer 장애 발생 시, 해당 메시지가 다시 처리될 수 있음 (중복 소비)
주요 사용 사례 약간의 메시지 중복을 허용할 수 있는 일반적인 애플리케이션 금융 거래 등 정확한 한 번 처리가 필수적인 미션 크리티컬한 애플리케이션
수동 커밋 예시 (Spring Kafka):
@KafkaListener(topics = "payment-events")
public void processPayment(@Payload String paymentData, Acknowledgment ack) {
    try {
        // 실제 결제 처리 로직
        processPaymentLogic(paymentData);
        // 성공적으로 처리 후 Offset을 동기적으로 커밋
        ack.acknowledge();
    } catch (Exception e) {
        // 처리 실패 시, 일정 시간 후 재시도 (예: 1초 후)
        // nack()은 Kafka Consumer API의 표준 메소드는 아니며,
        // Spring Kafka의 Acknowledgment에서 제공하는 기능입니다.
        // 내부적으로는 커밋을 지연시키거나 에러를 기록하는 방식으로 동작할 수 있습니다.
        ack.nack(1000);
    }
}

추가 면접 팁

  1. 개념 확장: "Kafka가 빠른 이유"에 대한 질문을 받으면, 아키텍처 외에도 Producer/Consumer 설정 튜닝(예: 배치 크기, linger.ms)에 대해 추가적으로 설명하여 깊이를 더하십시오.
  2. 상황 기반 답변: 실제 경험을 예로 들어 답변하십시오. "이전 전자상거래 프로젝트에서 주문 ID를 파티션 키로 사용하여 주문 관련 메시지의 순서를 성공적으로 보장했습니다." 와 같이 구체적인 사례를 제시하는 것이 좋습니다.
  3. 시각 자료 활용: 필요하다면 종이에 Producer, Broker, Consumer 간의 데이터 흐름과 주요 설정 파라미터를 직접 그려 설명하면 이해를 돕는 데 효과적입니다.

마지막 조언: 원리 이해가 암기보다 중요합니다. 모르는 질문에는 솔직하게 인정하고, 문제 해결을 위한 접근 방식이나 트러블슈팅 과정을 설명하는 능력을 보여주는 것이 좋습니다!

태그: kafka Message Queue Performance rebalance Exactly-Once

9월 8일 12:48에 게시됨