← 학습 카테고리

Learn

Kafka

42개 모듈 · 현재 13번째

Kafka 모듈 13/42 kafka-learn-13

신뢰성 있는 프로듀서·컨슈머 사용법과 시스템 검증

Kafka: The Definitive Guide (O'Reilly, 2017, 1st Edition) — Neha Narkhede, Gwen Shapira, Todd Palino Chapter 6: Reliable Data Delivery — Using Producers/Consumers in a Reliable System / Validating System Reliability (pp.121-133)

브로커를 아무리 안전하게 설정해도 클라이언트를 잘못 쓰면 데이터는 사라진다. 이 모듈에서는 acks 설정만으로는 막을 수 없는 두 가지 유실 시나리오, 재시도 가능한 오류와 그렇지 않은 오류의 구분, 신뢰성에 직결되는 네 가지 컨슈머 설정, 커밋 시점과 재처리·상태 유지·긴 처리 시간을 다루는 패턴, 외부 시스템에 대한 exactly-once 기법, 그리고 VerifiableProducer/VerifiableConsumer를 이용한 검증과 프로덕션 모니터링 지표를 다룬다.

브로커만 안전하게 설정해서는 부족하다

브로커를 가장 신뢰성 높은 설정으로 구성해도, 프로듀서를 신뢰성 있게 설정하지 않으면 시스템 전체는 여전히 데이터를 잃을 수 있다. 두 가지 시나리오가 이를 보여준다.

첫째, 복제본 3개에 unclean leader election을 끈 상태인데 프로듀서를 acks=1로 설정한 경우다. 메시지를 보내 리더에는 쓰였지만 아직 in-sync 복제본에는 복제되지 않았다. 리더는 "성공적으로 기록됨" 응답을 보내고 곧바로 크래시한다. 다른 복제본들은 아직 in-sync로 간주되고(복제본이 out of sync로 판정되기까지는 시간이 걸린다) 그중 하나가 리더가 된다. 그 메시지는 복제본에 쓰이지 않았으므로 사라지지만 프로듀서 애플리케이션은 성공했다고 믿는다. 컨슈머 관점에서는 아무도 그 메시지를 보지 못했으므로 시스템은 일관적이지만, 프로듀서 관점에서는 메시지가 유실된 것이다.

둘째, 같은 브로커 구성에 acks=all로 고쳤다고 하자. 메시지를 쓰려는데 마침 그 파티션의 리더가 크래시해 새 리더를 선출 중이면 Kafka는 "Leader not Available"로 응답한다. 이때 프로듀서가 오류를 올바르게 처리하지 않고 성공할 때까지 재시도하지 않으면 메시지는 유실된다. 브로커는 메시지를 받은 적이 없고 컨슈머도 본 적이 없으니 브로커의 신뢰성 문제도 일관성 문제도 아니지만, 프로듀서가 오류를 잘못 다루면 결국 메시지가 사라진다.

결론적으로 Kafka에 데이터를 쓰는 애플리케이션 개발자는 두 가지를 반드시 챙겨야 한다. 요구되는 신뢰성 수준에 맞는 올바른 acks 설정, 그리고 설정과 코드 양쪽에서의 올바른 오류 처리다.

핵심 포인트

  • 브로커 설정이 안전해도 acks=1이면 리더 크래시 시 프로듀서 관점의 유실이 생긴다
  • acks=all이어도 재시도를 하지 않으면 리더 선출 중 발생한 오류에서 메시지를 잃는다
  • 챙길 것은 두 가지: 올바른 acks, 그리고 올바른 오류 처리

acks 세 가지 모드의 실제 의미

acks=0은 프로듀서가 네트워크로 보내는 데 성공하면 Kafka에 성공적으로 쓰인 것으로 간주한다는 뜻이다. 직렬화할 수 없는 객체를 보내거나 네트워크 카드가 고장 나면 여전히 오류를 받지만, 파티션이 오프라인이거나 클러스터 전체가 장기 휴가를 떠나도 아무 오류를 받지 않는다. 즉 clean leader election이라는 정상적인 상황에서도, 새 리더가 선출되는 동안 리더가 없다는 사실을 모르기 때문에 메시지를 잃는다. acks=0은 매우 빠르고(벤치마크에서 이 설정이 자주 보이는 이유다) 대역폭을 최대한 활용할 수 있지만 일부 메시지 유실은 확정적이다.

acks=1은 리더가 메시지를 받아 파티션 데이터 파일에 쓴 순간(반드시 디스크에 sync된 것은 아니다) 확인 응답이나 오류를 보낸다는 뜻이다. 리더 선출이 진행 중이면 프로듀서는 LeaderNotAvailableException을 받고, 오류를 올바르게 처리한다면 재시도해 새 리더에게 안전하게 도착한다. 다만 리더가 크래시했는데 성공 응답까지 받은 메시지들이 팔로워로 복제되기 전이었다면 데이터를 잃는다.

acks=all은 리더가 모든 in-sync 복제본이 메시지를 받을 때까지 기다린 뒤 응답이나 오류를 보낸다는 뜻이다. 브로커의 min.insync.replicas 설정과 조합하면 확인 응답 전에 몇 개의 복제본이 메시지를 받아야 하는지를 제어할 수 있다. 가장 안전한 선택지이며, 프로듀서는 완전히 커밋될 때까지 전송을 포기하지 않는다. 동시에 가장 느린 선택지이기도 하다. 비동기 모드와 큰 배치로 영향을 줄일 수 있지만 대체로 처리량은 낮아진다.

핵심 포인트

  • acks=0: 네트워크 전송 성공 = 성공. clean leader election 중에도 유실이 발생한다
  • acks=1: 리더의 데이터 파일 기록 시점에 응답(디스크 sync 아님). 복제 전 리더 크래시 시 유실
  • acks=all: 모든 ISR 수신 후 응답. min.insync.replicas와 조합해 보장 수준을 정한다

재시도 가능한 오류와 그렇지 않은 오류

프로듀서의 오류 처리는 두 부분으로 나뉜다. 프로듀서 라이브러리가 자동으로 처리해 주는 오류와, 개발자가 직접 처리해야 하는 오류다.

브로커가 반환하는 오류 코드는 재시도로 해결될 수 있는 것과 그렇지 않은 것으로 나뉜다. LEADER_NOT_AVAILABLE은 새 브로커가 리더로 선출되어 두 번째 시도가 성공할 수 있으므로 재시도 가능한 오류다. 반면 INVALID_CONFIG는 같은 메시지를 다시 보낸다고 설정이 바뀌지 않으므로 재시도 불가능한 오류다.

메시지를 절대 잃지 않는 것이 목표라면, 재시도 가능한 오류를 만났을 때 계속 재시도하도록 프로듀서를 설정하는 편이 좋다. 리더 부재나 네트워크 연결 문제 같은 것은 보통 몇 초면 해소되므로, 성공할 때까지 프로듀서가 알아서 재시도하게 두면 직접 처리할 필요가 없기 때문이다. 재시도 횟수를 몇 번으로 잡을지는 프로듀서가 N번 재시도 후 포기하고 예외를 던졌을 때 무엇을 할 것인가에 달려 있다. "예외를 잡아 더 재시도하겠다"가 답이라면 애초에 재시도 횟수를 높게 잡는 편이 낫다. "그냥 메시지를 버리겠다" 또는 "다른 곳에 써 두고 나중에 처리하겠다"일 때 재시도를 멈춘다. 참고로 Kafka의 DC 간 복제 도구인 MirrorMaker는 메시지를 절대 버려서는 안 되는 도구이므로 기본적으로 무한 재시도(retries = MAX_INT)로 설정되어 있다.

재시도에는 대가가 있다. 실패한 메시지를 다시 보내면 두 메시지 모두 브로커에 기록되어 중복이 생길 위험이 작게나마 존재한다. 네트워크 문제로 브로커의 확인 응답이 프로듀서에 도달하지 못했지만 메시지 자체는 정상적으로 기록되고 복제된 경우, 프로듀서는 응답이 없는 것을 일시적 네트워크 문제로 보고 재전송한다(기록되었는지 알 수 없기 때문이다). 그러면 브로커에는 같은 메시지가 두 번 남는다. 재시도와 신중한 오류 처리는 메시지가 최소 한 번 저장됨(at-least-once)은 보장하지만, 책이 다루는 버전(0.10.0) 기준으로 정확히 한 번은 보장하지 못한다. 그래서 많은 실제 애플리케이션은 메시지마다 고유 식별자를 넣어 소비 시점에 중복을 감지·제거하거나, 메시지 자체를 멱등하게 만든다. "계좌 잔액은 110달러"는 여러 번 보내도 결과가 같아 멱등하지만, "계좌에 10달러를 더하라"는 보낼 때마다 결과가 바뀌므로 멱등하지 않다.

프로듀서 내장 재시도로 처리되지 않아 개발자가 직접 다뤄야 하는 오류도 있다. 메시지 크기나 인가 오류 같은 재시도 불가 브로커 오류, 직렬화 오류처럼 브로커로 보내기 전에 발생하는 오류, 그리고 재시도 횟수를 모두 소진했거나 재시도 중인 메시지를 담느라 프로듀서 가용 메모리가 한계에 찬 경우다. 다만 오류 핸들러가 하는 일이 결국 재전송뿐이라면 프로듀서의 재시도 기능에 맡기는 편이 낫다.

핵심 포인트

  • LEADER_NOT_AVAILABLE은 재시도 가능, INVALID_CONFIG는 재시도 불가
  • MirrorMaker는 기본적으로 retries = MAX_INT로 무한 재시도한다
  • 재시도는 중복을 낳을 수 있어 at-least-once까지만 보장된다(0.10.0 기준)
  • 고유 ID 기반 중복 제거 또는 멱등한 메시지 설계로 보완한다
  • 직렬화 오류·재시도 불가 오류·메모리 한계는 개발자가 직접 처리해야 한다

신뢰성에 직결되는 네 가지 컨슈머 설정

데이터는 커밋된 뒤에야 컨슈머에게 보이므로, 컨슈머가 받는 데이터는 일관성이 보장된다. 컨슈머가 할 일은 어디까지 읽었고 어디부터 안 읽었는지를 정확히 관리하는 것뿐이며, 이것이 소비 중 메시지를 잃지 않는 핵심이다. 컨슈머가 메시지를 잃는 주된 경로는 읽었지만 아직 완전히 처리하지 않은 이벤트의 오프셋을 커밋해 버리는 것이다. 그러면 다른 컨슈머가 그 일을 이어받을 때 그 이벤트들을 건너뛰고 영영 처리되지 않는다.

신뢰성 관점에서 이해해야 할 컨슈머 설정은 네 가지다.

- `group.id`: 두 컨슈머가 같은 그룹 ID로 같은 토픽을 구독하면 각자 파티션의 부분집합만 할당받아 메시지의 부분집합만 읽는다(그룹 전체로는 모든 메시지를 읽는다). 어떤 컨슈머가 구독한 토픽의 모든 메시지를 혼자 다 보아야 한다면 고유한 group.id가 필요하다. - `auto.offset.reset`: 커밋된 오프셋이 없거나(컨슈머 최초 기동) 브로커에 존재하지 않는 오프셋을 요청할 때의 동작이다. earliest면 유효한 오프셋이 없을 때 파티션의 처음부터 읽어 많은 메시지를 두 번 처리할 수 있지만 데이터 유실은 최소화된다. latest면 파티션의 끝에서 시작해 중복 처리는 최소화되지만 거의 확실히 일부 메시지를 놓친다. - `enable.auto.commit`: 스케줄에 따라 컨슈머가 오프셋을 커밋하게 할지, 코드에서 직접 커밋할지를 정하는 큰 결정이다. 자동 커밋의 장점은 신경 쓸 것이 하나 줄어든다는 점이며, 소비한 레코드의 처리를 전부 컨슈머 poll 루프 안에서 한다면 처리하지 않은 오프셋을 커밋하는 일은 없다는 보장을 얻는다. 단점은 처리할 중복 레코드 수를 제어할 수 없다는 것이다(레코드 일부를 처리한 뒤 자동 커밋이 돌기 전에 컨슈머가 멈추는 경우). 그리고 레코드를 다른 스레드로 넘겨 백그라운드에서 처리하는 식으로 하면, 자동 커밋이 읽기만 하고 아직 처리하지 않은 레코드의 오프셋을 커밋해 버릴 수 있다. - `auto.commit.interval.ms`: 자동 커밋을 선택했다면 얼마나 자주 커밋할지를 정한다. 기본값은 5초마다다. 자주 커밋하면 오버헤드가 늘지만 컨슈머가 멈췄을 때 생기는 중복은 줄어든다.

핵심 포인트

  • 컨슈머가 메시지를 잃는 주 경로는 '처리 전 커밋'이다
  • group.id: 모든 메시지를 혼자 봐야 하는 컨슈머는 고유한 그룹 ID가 필요하다
  • auto.offset.reset: earliest는 중복 처리 감수·유실 최소화, latest는 그 반대
  • enable.auto.commit: poll 루프 안에서 전부 처리한다면 안전하지만 중복 수를 제어할 수 없다
  • auto.commit.interval.ms 기본값은 5초

명시적 커밋에서 지켜야 할 원칙과 패턴

커밋 시점을 직접 통제해야 한다면 다음을 지켜야 한다.

**항상 이벤트를 처리한 뒤에 커밋한다.** poll 루프 안에서 모든 처리를 하고 루프 사이에 상태(예: 집계값)를 유지하지 않는다면 어렵지 않다. 자동 커밋을 쓰거나 poll 루프 끝에서 커밋하면 된다.

**커밋 빈도는 성능과 크래시 시 중복 수 사이의 트레이드오프다.** 가장 단순한 경우에도 루프 안에서 여러 번(심지어 이벤트마다) 커밋할 수도, 여러 루프에 한 번씩 커밋할 수도 있다. 커밋에는 acks=all 프로듀스와 비슷한 성능 오버헤드가 있으므로 상황에 맞게 고르면 된다.

**어떤 오프셋을 커밋하는지 정확히 알아야 한다.** poll 루프 중간에 커밋할 때 흔한 함정은 마지막으로 '처리한' 오프셋이 아니라 마지막으로 '읽은' 오프셋을 실수로 커밋하는 것이다. 읽었지만 처리하지 않은 메시지의 오프셋을 커밋하면 컨슈머가 메시지를 놓치게 된다.

**리밸런싱은 반드시 일어난다는 전제로 설계한다.** 큰 그림으로는 파티션이 회수되기 전에 오프셋을 커밋하고, 새 파티션을 할당받을 때 유지하던 상태를 정리하는 일이 필요하다.

**재시도가 필요할 수 있다.** poll 후 처리 중 일부 레코드가 완전히 처리되지 않아 나중에 다시 처리해야 할 수 있다(예: DB가 일시적으로 사용 불가). 전통적인 pub/sub과 달리 Kafka에서는 개별 메시지를 ack하는 것이 아니라 오프셋을 커밋한다. 그래서 레코드 #30 처리에 실패하고 #31에 성공했다고 #31을 커밋하면 #30을 포함해 #31까지 전부 커밋한 셈이 되므로 원하는 결과가 아니다. 두 가지 패턴이 있다.

- 재시도 가능한 오류를 만나면 성공적으로 처리한 마지막 레코드까지만 커밋한다. 그리고 아직 처리해야 할 레코드들을 버퍼에 저장해(다음 poll이 덮어쓰지 않도록) 계속 처리를 시도한다. 처리 시도 중에도 계속 poll을 호출해야 할 수 있는데, 컨슈머의 `pause()` 메서드를 쓰면 추가 poll이 새 데이터를 반환하지 않게 만들어 재시도를 쉽게 할 수 있다. - 재시도 가능한 오류를 만나면 별도의 토픽에 기록하고 진행한다. 재시도 토픽은 별도 컨슈머 그룹이 처리하거나, 한 컨슈머가 주 토픽과 재시도 토픽을 모두 구독하되 재시도 사이에는 재시도 토픽을 pause하는 방식으로 다룬다. 많은 메시징 시스템의 dead-letter-queue와 유사한 패턴이다.

**상태 유지가 필요할 수 있다.** 이동 평균처럼 여러 poll에 걸친 상태가 필요하다면, 프로세스 재시작 시 마지막 오프셋부터 소비를 재개하는 것뿐 아니라 그에 대응하는 상태도 복구해야 한다. 한 가지 방법은 오프셋을 커밋하는 시점에 최신 누적값을 '결과' 토픽에 함께 쓰는 것이다. 다만 이것으로 문제가 완전히 해결되지는 않는다. 결과를 쓴 뒤 오프셋 커밋 전에(또는 그 반대로) 크래시할 수 있기 때문이다. 이는 꽤 복잡한 문제이므로 직접 해결하기보다 집계·조인·윈도우 같은 고수준 DSL API를 제공하는 Kafka Streams 같은 라이브러리를 보는 것이 낫다.

**처리 시간이 길 때가 있다.** 블로킹되는 서비스를 호출하거나 매우 복잡한 계산을 할 때다. 일부 버전의 컨슈머에서는 몇 초 이상 poll을 멈출 수 없다는 점을 기억해야 한다. 추가 레코드를 처리하고 싶지 않더라도 클라이언트가 브로커에 하트비트를 보내려면 계속 poll해야 한다. 흔한 패턴은 가능한 경우 데이터를 스레드 풀에 넘겨 여러 스레드로 병렬 처리하고, 넘긴 뒤에는 컨슈머를 pause해 추가 데이터를 가져오지 않으면서 계속 poll하는 것이다. 워커 스레드가 끝나면 컨슈머를 resume한다. 컨슈머가 poll을 멈추지 않으므로 하트비트가 정상적으로 전송되고 리밸런싱이 유발되지 않는다.

핵심 포인트

  • 항상 '처리 후' 커밋하고, 읽은 오프셋과 처리한 오프셋을 혼동하지 않는다
  • 커밋 빈도 = 성능 대 중복 수의 트레이드오프
  • 개별 ack가 아니라 오프셋 커밋이므로 실패한 앞 레코드를 건너뛴 커밋은 유실을 낳는다
  • 재시도 패턴: (1) 버퍼 + pause()로 재시도, (2) 재시도 토픽(dead-letter-queue 유사)
  • 긴 처리: 스레드 풀에 위임 + pause/resume으로 하트비트를 유지한다

외부 시스템에 대한 exactly-once 기법

at-least-once(데이터 유실 없음)를 넘어 exactly-once가 필요한 애플리케이션도 있다. 책이 다루는 버전에서 Kafka는 완전한 exactly-once를 제공하지 않지만, 컨슈머가 각 메시지를 외부 시스템에 정확히 한 번 쓰도록 보장하는 몇 가지 방법이 있다(데이터를 Kafka에 생산하는 과정에서 생긴 중복까지 해결해 주지는 않는다).

가장 쉽고 흔한 방법은 고유 키를 지원하는 시스템에 결과를 쓰는 것이다. 모든 키-값 저장소, 모든 관계형 데이터베이스, Elasticsearch 등이 여기 해당한다. 레코드 자체에 고유 키가 있거나(꽤 흔하다), 없다면 토픽·파티션·오프셋 조합으로 Kafka 레코드를 유일하게 식별하는 키를 만들 수 있다. 고유 키와 함께 값을 쓰면, 실수로 같은 레코드를 다시 소비해도 완전히 동일한 키와 값을 다시 쓰게 되어 저장소가 기존 값을 덮어쓰므로 중복이 없었을 때와 같은 결과가 된다. 이 패턴을 멱등 쓰기(idempotent writes)라 하며 매우 유용하다.

또 다른 방법은 트랜잭션을 지원하는 시스템에 쓸 때 사용할 수 있다. 관계형 데이터베이스가 가장 쉬운 예이고, HDFS의 원자적 rename도 같은 목적으로 자주 쓰인다. 레코드와 그 오프셋을 같은 트랜잭션 안에 써서 둘이 항상 일치하게 만드는 것이다. 기동 시에는 외부 저장소에 기록된 최신 레코드의 오프셋을 조회한 뒤 `consumer.seek()`로 그 오프셋부터 다시 소비를 시작한다.

핵심 포인트

  • 고유 키 기반 멱등 쓰기가 가장 흔한 방법이다
  • 레코드에 고유 키가 없으면 (토픽, 파티션, 오프셋)으로 키를 만든다
  • 트랜잭션 지원 저장소에서는 레코드와 오프셋을 한 트랜잭션에 쓰고 재기동 시 consumer.seek()로 복귀한다

시스템 신뢰성 검증: 설정, 애플리케이션, 프로덕션 모니터링

요구사항을 정하고 브로커와 클라이언트를 설정하고 API를 잘 사용했다면 안심해도 될까? 세 겹의 검증 — 설정 검증, 애플리케이션 검증, 프로덕션 모니터링 — 을 권한다.

**설정 검증.** 애플리케이션 로직과 분리해 브로커·클라이언트 설정만 시험하는 것은 쉽고 권장된다. 선택한 설정이 요구사항을 충족하는지 확인할 수 있고, 시스템의 기대 동작을 스스로 추론해 보는 좋은 훈련이 된다. Kafka는 `org.apache.kafka.tools` 패키지에 VerifiableProducer와 VerifiableConsumer 클래스를 제공한다. 명령줄 도구로 실행하거나 자동화 테스트 프레임워크에 임베드할 수 있다. 검증 프로듀서는 1부터 지정한 값까지의 숫자를 담은 메시지 시퀀스를 생산하며, acks·retries·생산 속도 등을 실제 프로듀서와 동일하게 설정할 수 있고, 받은 ack에 따라 메시지마다 성공/오류를 출력한다. 검증 컨슈머는 그 반대의 확인을 수행해 소비한 이벤트를 순서대로 출력하고 커밋과 리밸런싱 정보도 함께 출력한다.

어떤 테스트를 돌릴지도 정해야 한다. 예를 들어 다음과 같다.

- 리더 선출: 리더를 죽이면 어떻게 되는가? 프로듀서와 컨슈머가 정상으로 돌아오기까지 얼마나 걸리는가? - 컨트롤러 선출: 컨트롤러를 재시작하면 시스템이 재개되기까지 얼마나 걸리는가? - 롤링 재시작: 브로커를 한 대씩 재시작하면서 메시지를 하나도 잃지 않을 수 있는가? - unclean leader election 테스트: 파티션의 모든 복제본을 하나씩 죽여 각각 out of sync가 되게 한 뒤, out of sync였던 브로커를 시작하면 어떻게 되는가? 운영을 재개하려면 무엇이 필요하며 그것이 수용 가능한가?

시나리오를 하나 골라 검증 프로듀서와 검증 컨슈머를 띄우고 실행한다. 짧은 정지 후 메시지 유실 없이 정상 재개되기를 기대했다면, 프로듀서가 생산한 메시지 수와 컨슈머가 소비한 메시지 수가 일치하는지 확인한다.

**애플리케이션 검증.** 설정이 요구사항을 만족하는 것을 확인했다면 애플리케이션이 필요한 보장을 제공하는지 시험한다. 커스텀 오류 처리 코드, 오프셋 커밋, 리밸런스 리스너 등 애플리케이션 로직이 클라이언트 라이브러리와 맞닿는 지점을 점검하는 것이다. 클라이언트 연결 상실, 리더 선출, 브로커 롤링 재시작, 컨슈머 롤링 재시작, 프로듀서 롤링 재시작 같은 다양한 장애 조건에서 테스트를 돌려 볼 것을 권한다. 예를 들어 컨슈머 롤링 재시작 시에는 리밸런스로 인한 짧은 정지 후 중복이 1,000건을 넘지 않는 선에서 소비가 이어지기를 계획했다면, 테스트로 실제 그렇게 되는지 확인한다.

**프로덕션 모니터링.** Kafka Java 클라이언트는 JMX 지표를 제공한다. 프로듀서에서 신뢰성에 가장 중요한 두 지표는 레코드당(집계) error-rate와 retry-rate다. 이 값이 올라가면 시스템에 문제가 있다는 신호다. 이벤트 전송 중 WARN 레벨로 남는 프로듀서 로그도 함께 보아야 한다. "Got error produce response with correlation id 5689 on topic-partition [topic-1,3], retrying (two attempts left). Error: …" 같은 메시지에서 남은 시도가 0인 이벤트가 보이면 프로듀서가 재시도를 소진하고 있다는 뜻이므로, 재시도 횟수를 늘리거나 애초의 원인을 해결해야 한다.

컨슈머 쪽에서 가장 중요한 지표는 컨슈머 lag이다. 브로커의 파티션에 커밋된 최신 메시지로부터 컨슈머가 얼마나 뒤처져 있는지를 나타낸다. poll()이 여러 메시지를 반환하고 컨슈머가 그것을 처리한 뒤 다음 fetch를 하므로 lag은 항상 조금씩 출렁인다. 중요한 것은 컨슈머가 결국 따라잡느냐, 아니면 점점 더 뒤처지느냐다. 이런 출렁임 때문에 전통적인 임계값 알림을 걸기가 까다로운데, LinkedIn의 컨슈머 lag 체커인 Burrow가 이를 쉽게 해 준다.

데이터 흐름 모니터링은 생산된 모든 데이터가 적시에 소비되는지 확인하는 것이기도 하다. 이를 위해서는 데이터가 언제 생산되었는지 알아야 한다. Kafka는 0.10.0부터 모든 메시지에 이벤트가 생산된 시각을 나타내는 타임스탬프를 포함한다. 그보다 낮은 버전의 클라이언트를 쓴다면 이벤트마다 타임스탬프, 메시지를 생산한 앱 이름, 생성된 호스트명을 기록해 두기를 권한다. 나아가 프로듀서 측에서 생산 이벤트 수(초당)를 기록하고, 컨슈머 측에서 소비 이벤트 수(초당)와 이벤트 타임스탬프를 이용한 생산~소비 지연을 함께 기록한 뒤, 양쪽 수치를 대조해 유실이 없는지와 시간 격차가 합리적인지 확인하는 시스템이 필요하다. 더 나아가 핵심 토픽에 모니터링 전용 컨슈머를 붙여 이벤트 수를 세면, 아무도 소비하지 않는 시점에도 프로듀서를 정확히 모니터링할 수 있다.

핵심 포인트

  • 검증은 세 겹: 설정 검증 → 애플리케이션 검증 → 프로덕션 모니터링
  • org.apache.kafka.tools의 VerifiableProducer/VerifiableConsumer로 설정을 검증한다
  • 테스트 시나리오: 리더 선출, 컨트롤러 선출, 롤링 재시작, unclean leader election
  • 프로듀서 핵심 지표: error-rate, retry-rate / 컨슈머 핵심 지표: consumer lag(Burrow)
  • 0.10.0부터 메시지 타임스탬프로 생산~소비 지연을 측정할 수 있다

이 모듈과 연관된 문항 4개가 문제 은행에 있습니다.

이 내용으로 문제 풀어보기