auto.offset.reset는 카프카 컨슈머를 다루는데 있어 아주 중요한 부분입니다. 해당 옵션이 가질 수 있는 값은 다음과 같습니다.
- earliest : 마지막 커밋 기록이 없을 경우, 가장 예전(낮은 번호 오프셋) 레코드부터 처리
- latest : 마지막 커밋 기록이 없을 경우, 가장 최근(높은 번호 오프셋) 레코드부터 처리
- none : 커밋 기록이 없을 경우 throws Exception
해당 옵션은 필수 옵션이 아닌 선택 옵션으로서 입력을 하지 않으면 자동으로 latest로 설정됩니다. 일반적으로 컨슈머를 운영할 때 이 옵션을 건드리는 경우는 거의 드문데요. 그러다보니 기본값인 latest로 설정할 경우 우리도 모르게 운영 중 데이터의 유실이 발생할 수 있다는 사실을 놓치기도 합니다.
그러다보니, 예전과 다르게 해당 옵션은 다음과 같은 경고문이 붙게 되었습니다.
Note that altering partition numbers while setting this config to latest may cause message delivery loss since producers could start to send messages to newly added partitions (i.e. no initial offsets exist yet) before consumers reset their offsets.
왜 데이터가 유실 될까?
파티션의 개수가 변경될 경우 컨슈머는 metadata.max.age.ms 만큼 메타데이터 리프래시 기간 이후 리밸런싱이 일어나면서 파티션 할당과정을 거치게 됩니다. 문제는 메타데이터 리프레시(파티션 변경 여부를 알아차리는 시간) 기간동안 새로운 파티션에 데이터가 들어올 수 있다는 사실입니다. 다음 그림은 컨슈머의 auto.offset.reset이 latest일 경우 데이터가 일부 유실되는 모습을 그렸습니다.
1) 파티션2개인 토픽에 컨슈머 2개가 연동되어 있는 그림
2) 파티션을 3개로 늘린 그림
3) 새로운 파티션에 데이터가 들어가는 그림
4) 메타데이터가 리프래시 되고 리밸런싱이 일어나는 그림
5) 리밸런싱이 완료되고 가장 최근 데이터(오프셋 2번)부터 컨슈머가 가져가는 그림
위와 같은 상황에서 1,2 오프셋에 해당하는 레코드들이 컨슈머에서 처리되지 않고 지나간 것을 알 수 있습니다. 이는 데이터 양이 적을때는 유실량이 겨우 2개 밖에 안되? 라고 생각할 수도있겠지만, 초당 1억건 이상 들어올 때는 이 데이터양이 매우 많을 수 있습니다. 또한 metadata의 리프래시 주기에 따라서도 유실량이 달라 질 수 있습니다.
사실상 컨슈머가 알 수 있는 warning 구문 조차 없기 때문에 많은 개발자들은 유실이 되었는지도 모르고 지나갈 때가 많습니다.
유실을 막기 위해서 어떻게 해야 하나
파티션 증설에 따른 유실을 막기 위해서는 단순히 컨슈머의 옵션을 auto.offset.reset earliest로 설정하는 것이 가장 손쉬운 방법입니다. 그러나 개발자가 컨슈머를 운영하는 상황에서 offset reset을 다 챙기지 못하는 상황이 많고, 파티션을 늘리면 당연히 처음 데이터부터 가져간다고 이해하는 경우가 많기 때문에 오해를 일으키기 좋습니다.
이에 대해 많은 카프카 개발자들은 우려를 표했고 카프카 3.6(23년 10월)이 되어서야 드디어 경고 문구를 DOCS에 추가하는 수준에 끝나게 되었습니다. 관련 PR은 다음과 같습니다. DOCS에 추가된지 6개월도 안되었으니 이미 많은 카프카 컨슈머 개발자들은 이를 놓쳤을거라 생각됩니다.
https://github.com/apache/kafka/pull/10167/files
이에 대한 대응 책으로 텐센트 개발자 hudeqi는 기존 컨슈머 그룹의 auto.offset.reset을 기존 earliest, latest, none에서 더 세분화하여 개발자로 하여금 오해가 없이 운영할 수 있도록 하는 방안을 KIP-842에서 제안했고 이는 현재도 논의 진행 중에 있습니다.
애석하게도 이런 논의가 진행되는 discusstion thread를 살펴보면 이렇게 변경하는 안에 대해 그다지 호의적이지는 않은 것 같습니다. latset, earliest, none 외에 추가하는 것이 개발자로 하여금 더 어렵게 만들고, seekToEnd()와 같은 컨슈머에서 다룰 수 있는 오프셋 지정 메서드들이 제공되기 때문입니다.
For the original use-case you mentioned, that you want to start from "latest" when the app starts, but if a new partition is added you want to start from "earliest" it seem that the right approach would be to actually configure "earliest", and when the app is deployed for the first time, use a `seekToEnd()` to avoid triggering auto-offset-reset?
- Matthias J. Sax
아무쪼록 해당 기능에 대해 잘 이해하고 예상치 못한 데이터 유실을 경험하지 않길 바라는 마음에 작성해 보았습니다.
'빅데이터 > Kafka' 카테고리의 다른 글
standalone 카프카(kraft모드 in local) 실행을 위한 준비와 실행 (0) | 2024.06.23 |
---|---|
카프카에서 계층 저장소(Tiered storage)가 필요한 이유 (0) | 2023.10.20 |
신뢰성 있는 카프카 애플리케이션을 만드는 3가지 방법 (0) | 2023.09.22 |
카프카 프로듀서의 acks=all 옵션은 사실(?) 느리지 않다! (0) | 2023.08.08 |
기존에 생성된 compact topic의 cleanup.policy를 변경하는 방법 (1) | 2023.06.30 |
Compacted topic에 null key 레코드를 전송하면? (1) | 2023.06.30 |