메시지 전달 의미론

메시지 전달 의미론

카프카에서 메시지가 로그에 "커밋(committed)" 되었다는 것은, 그 파티션의 in-sync replicas(ISR) 에 속한 모든 복제본이 그 메시지를 자기 로그에 적용했음을 뜻해요. 일단 커밋되면, 그 파티션을 복제하는 브로커가 하나라도 살아있는 한 메시지가 유실되지 않아요. 이 "커밋" 이라는 개념이 카프카 전달 의미론의 출발점이에요.

출처: https://kafka.apache.org/43/design/design/#message-delivery-semantics

본문

카프카는 기본적으로 at-least-once 배달을 보장해요. 즉 메시지가 최소 한 번은 전달되는 걸 보장하지요. at-most-once 배달도 구현할 수 있는데, 이 경우 프로듀서의 재시도(retry)를 끄고 컨슈머가 배치(batch)를 처리하기 전에 오프셋을 커밋하면 돼요. 실패 시 메시지를 잃을 수는 있지만, 중복 없이 최대 한 번만 전달되어요.

0.11.0.0 이후부터는 프로듀서가 멱등(idempotent) 전달 옵션도 지원해요. 이 옵션을 쓰면 네트워크 문제로 인해 재전송이 일어나도 로그에 중복 항목이 생기지 않도록 해줘요. 무슨 일이 있어도 같은 메시지가 두 번 기록되지 않는다는 것이지요.

그렇다면 정확히 한 번(exactly-once) 은 어떨까요? 카프카 토픽에서 읽어서 다른 토픽으로 쓰는 작업(예: Kafka Streams 어플리케이션)에서는, 0.11.0.0에 도입된 트랜잭션 프로듀서(transactional producer) 기능을 활용할 수 있어요. Kafka Streams 는 이를 활용해 exactly-once 처리를 지원해요.

카프카의 트랜잭션은 다른 메시징 시스템의 트랜잭션과는 조금 달라요. 카프카에서는 컨슈머와 프로듀서가 분리되어 있고, 트랜잭션을 거는 쪽은 오직 프로듀서뿐이에요. 정확히 한 번 처리를 목표로 한다면 컨슈머는 isolation.level=read_committed 격리 수준을 쓰고 enable.auto.commit=false 로 설정하는 것이 일반적으로 좋은 관행이에요.

예를 들어 HDFS 에 데이터를 채우는 Kafka Connect 커넥터는, 읽은 데이터의 오프셋을 함께 저장해서 "데이터와 오프셋이 둘 다 갱신되거나, 둘 다 갱신되지 않거나" 하는 원자성을 보장해요. 이렇게 대상 시스템과의 협력 없이도 카프카 토픽 위에서 읽고·처리하고·쓰는 작업에 대해서는 exactly-once 배달을 일반적으로 달성할 수 있어요. 다른 외부 시스템으로의 exactly-once 는 그 시스템의 협력이 필요한 경우가 많지만, 카프카는 이를 구현 가능하게 하는 원시 요소(primitive)들을 제공해줘요.

트랜잭션 처리 중에 예외가 발생하면 이를 구분해서 대응해야 해요. 설정 관련 예외인 InvalidConfigurationException 은 어플리케이션이 처리해야 하고 프로듀서 재시작이 필요 없지만, 일반적인 KafkaException 은 상황에 맞춰 어플리케이션이 재시작을 선택하기도 해요.

더 알아보기

  • "커밋"과 ISR 의 관계는 [복제와 ISR] 문서에서 자세히 다뤄요.
  • 컨슈머의 오프셋 커밋 타이밍이 의미론을 어떻게 바꾸는지는 [오프셋과 커밋 관리] 문서를 참고해요.
  • enable.auto.commitisolation.level 설정은 consumer-config 문서에서 확인해요.