컨슈머는 커밋된 데이터만 읽으므로 일관성은 이미 보장되어 있다. 컨슈머가 할 일은 하나뿐이다. 어디까지 읽었는지 정확히 기록하는 것.

누락이 생기는 지점

메시지 누락은 한 지점에서 생긴다. 읽기는 했지만 아직 처리하지 않은 이벤트의 오프셋을 커밋하면, 다른 컨슈머가 그 파티션을 이어받았을 때 그 메시지들을 건너뛰고 영원히 처리하지 않는다. 그래서 언제 어떻게 커밋되는지를 신경 써야 한다.

신뢰성에 관여하는 설정

신뢰성에 관여하는 컨슈머 설정부터 본다.

group.id컨슈머 그룹 쪽 이야기다. 구독한 토픽의 모든 메시지를 혼자 읽어야 한다면 고유한 값이어야 한다.

auto.offset.reset은 커밋된 오프셋이 없거나 브로커에 없는 오프셋을 요청했을 때 어디서 시작할지 정한다. earliest는 파티션의 맨 앞에서 시작해서 유실을 최소화하는 대신 중복 처리가 많아지고, latest는 끝에서 시작해서 중복을 최소화하는 대신 일부 데이터 누락이 거의 확실해진다. 둘 다 되는 값은 없다.

enable.auto.commit은 일정한 시간에 맞춰 알아서 커밋할지 코드에서 직접 할지를 고르는 것이다. 자동으로 두면 신경 쓸 것이 줄고 처리하지 않은 오프셋을 실수로 커밋하는 사태가 없다. 대신 중복 처리를 개발자가 제어할 수 없어서, 커밋되지 않은 상태로 컨슈머가 멈추면 재시작 후의 중복을 피할 방법이 없다.

auto.commit.interval.ms는 자동 커밋 주기로 기본값이 5초다. 자주 커밋할수록 오버헤드가 늘고 정지 시 발생할 중복의 수는 줄어든다.

직접 커밋할 때 지킬 것

직접 커밋하기로 했다면 지켜야 할 것들이 있다.

메시지 처리가 먼저, 오프셋 커밋이 나중이다. 순서가 뒤바뀌면 그것이 곧 누락이다.

커밋 빈도는 성능과 크래시 시 중복 개수 사이의 트레이드오프다. 커밋은 오버헤드가 크다. acks=all로 쓰기 작업을 하는 것과 비슷한 수준인데, 특정 컨슈머 그룹의 모든 오프셋 커밋이 동일한 브로커로 간다는 점이 다르다. 메시지를 읽어올 때마다 커밋하는 것은 아주 드물게 들어오는 토픽에나 쓸 수 있다.

정확한 오프셋을 커밋한다. 폴링 루프 중간에서 커밋할 때 가장 흔한 실수가 여기다. 마지막으로 처리된 메시지가 아니라 마지막으로 읽어온 메시지의 오프셋을 커밋해버린다.

리밸런스가 일어날 것을 전제한다. 할당된 파티션이 해제되기 전에 오프셋을 커밋하고, 새 파티션을 할당받으면 애플리케이션이 들고 있던 상태를 지운다.

확인해야 할 지표

설정을 맞췄다고 끝이 아니라서 지표로 확인해야 한다.

가장 중요한 지표는 컨슈머 랙이고, 값 자체보다 계속 따라붙고 있는지가 중요하다. 프로듀서 쪽에서는 레코드별 에러율(error-rate)과 재시도율(retry-rate)을 본다. WARN 레벨의 재시도 로그는 정상 범위지만 ERROR 레벨은 재시도 불가능한 에러이거나 재시도를 소진한 것이라 그 자체로 유실이다.

데이터 흐름 전체를 보는 것도 필요하다. 프로듀서 쪽에서 초당 쓴 이벤트 수를, 컨슈머 쪽에서 초당 읽은 이벤트 수와 쓰인 시점부터 읽힌 시점까지의 간격을 각각 기록하고 양쪽 수를 맞춰본다. 쓰인 모든 데이터가 적절한 시간 안에 읽히고 있는지는 이렇게 확인한다.

관련

출처