Post

Chapter 08. ‘정확히 한 번’ 의미 구조

8.1 멱등적 프로듀서 ✨

8.1.1 멱등적 프로듀서란 🤔

  • 멱등성은 동일한 작업을 여러 번 실행해도 결과가 동일한 작업을 의미
  • 데이터베이스 예시

    1
    2
    3
    4
    5
    
      -- 멱등적이지 않음 (실행할 때마다 결과가 달라짐)
      UPDATE t SET x = x + 1 WHERE y = 5;
        
      -- 멱등적임 (여러 번 실행해도 결과가 같음)
      UPDATE t SET x = 18 WHERE y = 5;
    
  • 카프카에서 메시지 전송 시 네트워크 지연이나 장애로 인해 중복 메시지 발생 가능
  • 파티션 리더 장애 발생 이후, 새로운 리더로 교체되면 중복 메시지가 저장될 가능성 존재

8.1.2 멱등적 프로듀서의 작동 원리 ⚙️

  • 메시지에 프로듀서 ID(PID)시퀀스 넘버를 추가해 메시지 고유성 식별
  • 브로커는 각 파티션에서 마지막 5개의 메시지를 추적해 중복 메시지 차단
  • 예상보다 높은 시퀀스 넘버가 수신되면 out of order sequence number 에러 발생

8.1.3 장애 발생 시 처리 방법 🚨

  1. 프로듀서 재시작
    • 장애 후 새로운 프로듀서 생성 시, 새로운 PID를 할당받아 기존 메시지 중복 감지 불가
  2. 브로커 장애 복구
    • 리더 변경 시 새로운 리더가 이전 리더의 시퀀스 상태를 이어받아 중복 메시지 방지
    • 종료 전 상태를 스냅샷으로 저장해 복구 시 활용

8.1.4 멱등적 프로듀서 사용법 📋

  • enable.idempotence=true를 설정해 활성화
  • acks=all과 함께 사용 시 성능 저하 없음
  • 메시지 순서를 유지하며 중복 메시지를 차단

8.1.5 멱등적 프로듀서의 한계 ⚠️

  • 내부 재시도로 발생한 중복만 방지
  • 애플리케이션에서 동일 메시지를 producer.send() 로 여러 번 호출하면 중복 발생 가능
  • 다중 프로듀서 환경에서 중복 가능성
    • 여러 프로듀서가 동일 데이터를 처리하면 중복 메시지가 발생할 수 있음

8.2 트랜잭션 🔄

8.2.1 트랜잭션 도입 배경 🏗️

  • 카프카의 스트림 처리 애플리케이션에서 정확히 한 번 처리를 보장하기 위해 도입
  • 입력 레코드와 처리한 결과를 출력하며, 집계 및 조인 작업에서 데이터의 정확성 보장

8.2.2 트랜잭션의 주요 기능 💡

  • 메시지 처리와 오프셋 커밋을 원자적으로 수행
  • 트랜잭션 실패 시 메시지 출력과 오프셋 커밋을 모두 중단
  • 동일 transactional.id를 가진 좀비 인스턴스를 차단해 메시지 중복 방지

8.2.3 트랜잭션이 해결하는 문제 ✅

  1. 애플리케이션 장애로 인한 중복 처리 방지
    • 메시지 출력 후 오프셋 커밋 전에 애플리케이션 장애 발생 시 중복 처리 방지
  2. 좀비 애플리케이션 문제 해결
    • 하트비트를 잃은 애플리케이션이 재활성화되어 동일 메시지 처리 시도를 차단

8.2.4 트랜잭션으로 해결할 수 없는 문제 ❌

  • 외부 시스템과의 작업은 트랜잭션으로 처리 불가 (ex. 이메일 발송, API 호출)
  • 카프카 메시지를 데이터베이스에 저장하는 작업은 보장 불가
  • 클러스터 간 데이터 복제에서 정확히 한 번 처리는 보장되지 않음

8.2.5 트랜잭션 사용법 🛠️

  1. 트랜잭션 활성화
    • enable.idempotence=true 설정 필수
    • 트랜잭션 ID(transactional.id) 지정 필요
  2. 코드 사용 흐름
    • beginTransaction(): 트랜잭션 시작
    • sendOffsetsToTransaction(): 소비한 오프셋을 트랜잭션에 포함
    • commitTransaction(): 메시지와 오프셋 확정
    • abortTransaction(): 메시지와 오프셋 롤백
  3. 주요 특징
    • 프로듀서 실패 시, 트랜잭션 복구 가능
    • 컨슈머 그룹의 정확히 한 번 처리(exactly-once semantics) 보장
    • 단일 트랜잭션 내에서 동일한 Topic-Partition에 대한 메시지 순서 보장
  4. 주의사항
    • 트랜잭션 처리 시간 제한이 있으며 초과 시 오류 발생

8.2.6 트랜잭션 ID와 펜싱 🆔

  • 트랜잭션 ID
    • 프로듀서 인스턴스마다 고유하게 설정
    • 트랜잭션 상태와 Epoch 번호로 인스턴스 충돌 방지
    • 동일한 트랜잭션 ID를 가진 이전 인스턴스(좀비 프로듀서)는 차단됨
  • 펜싱(Fencing)
    • Epoch 번호를 증가시켜 이전 세션의 작업 차단
    • 장애 복구 중 프로듀서 중복 실행을 방지

8.2.7 트랜잭션의 작동원리 🔍

  1. 트랜잭션 시작
    • beginTransaction() 호출로 상태 초기화
    • 트랜잭션 ID로 파티션과 프로듀서 연결
  2. 트랜잭션 진행
    • 메시지 기록 시 각 메시지에 트랜잭션 정보를 포함
  3. 트랜잭션 종료
    • commitTransaction() 호출로 메시지를 확정 (커밋 로그에 반영)
    • abortTransaction() 호출 시 메시지를 폐기
  4. 트랜잭션 관리
    • 컨트롤러와 코디네이터가 트랜잭션 상태 및 오프셋 처리 관리

8.3 트랜잭션 성능 🚀

프로듀서 측 성능 ⚡

  • 트랜잭션 초기화와 커밋 요청이 동기적으로 처리돼 데이터 전송 지연 가능
  • 트랜잭션 내 메시지 수가 많을수록 오버헤드 감소 및 처리량 증가

컨슈머 측 성능 📊

  • read_committed 모드에서 열린 트랜잭션 메시지가 반환되지 않아 지연 발생 가능
  • 브로커가 열린 트랜잭션 메시지를 반환하지 않아 처리량에는 영향 없음

참고 🕶️

This post is licensed under CC BY 4.0 by the author.