다른 조직이 운영하는 Kafka 클러스터의 배송 토픽을 구독하는 주문팀이, 권한 없이도 컨슈머를 중단하지 않고 오프셋을 이동해 메시지를 재처리하는 방법을 찾은 과정이다. CLI/Admin API가 모두 컨슈머 그룹 비활성을 요구하는 제약을 Spring Kafka의 seek 기능으로 넘고, 분산 환경까지 확장한다.
핵심 포인트- kafka-consumer-groups.sh reset-offsets와 Admin API의 alterConsumerGroupOffsets는 모두 컨슈머 그룹이 empty/비활성이어야 해서 무중단 요건을 못 채운다.
- Spring Kafka의 ConsumerSeekAware/ConsumerSeekCallback(내부적으로 Consumer.seek API)을 쓰면 애플리케이션을 멈추지 않고 오프셋을 이동할 수 있다.
- AbstractConsumerSeekAware를 상속하면 파티션별 seek 콜백 등록·제거가 쉬워지고, 컨슈머는 poll 전에 seek 대기열을 처리한다.
- Spring Kafka는 개별 호스트 단위로만 동작하므로, HTTP API + Redis Pub/Sub로 seek 요청을 분산 서버 전체에 전파해 컨슈머 그룹 레벨로 확장했다.
상세 정리- 제약 배경: 물류팀 Kafka 클러스터에 관리자 권한이 없어 오프셋 이동 때마다 타 팀 도움이 필요했고, 기존 방식은 명령 실행 전 컨슈머를 반드시 중지해야 했다.
- 중단의 비용: 실시간 배송(컬리나우, 주문 1시간 내 배달) 특성상 가용성 저하, Lag 처리 지연, 한 그룹 내 다른 컨슈머까지 동반 중단되는 문제가 있었다.
- 대안1(권한 획득): 조직 간 권한 공유는 관리가 어렵고, 얻어도 CLI reset-offsets가 비활성 상태를 요구해 무중단에 실패한다.
- 대안2(Admin API): alterConsumerGroupOffsets도 "group must be empty"를 명시해 무중단에 실패한다.
- 대안3(Spring Kafka): Java 21 + Spring Boot 3.3.4 + Spring for Apache Kafka 3.2.4 스택에서 seek 기능으로 무중단 이동이 가능해 채택.
- 코드: AbstractConsumerSeekAware를 상속한 리스너에서 파티션별 콜백으로 seekToBeginning 등을 호출해, 앱·컨슈머 중단 없이 오프셋이 처음으로 이동하는 로그를 확인.
- 버전 주의: getTopicsAndCallbacks는 3.3.0부터 지원되고, 이전 getSeekCallbacks는 같은 토픽을 다른 그룹이 구독하면 콜백이 누락될 수 있다(GH-3328).
- 분산 확장: seek 요청용 HTTP API(topics/partitions/seekAt)를 정의해 분산 서버 중 하나가 받아 Redis 채널에 게시하고, 각 서버의 Redis 리스너가 수신해 자기 오프셋을 이동한다. 이미 쓰던 Redis라 추가 인프라가 불필요했다.
- 부가 확장·기여: 같은 플로우로 컨슈머 시작/중지 기능도 확장했고, seek 조사 중 발견한 Spring Kafka 이슈를 메인테이너와 논의해 오픈소스로 기여했다.
왜 읽나분산 환경에서 Kafka 컨슈머를 무중단으로 재처리해야 하는 백엔드 개발자에게 CLI/Admin API의 한계와 Spring Kafka seek + Redis Pub/Sub 전파 설계를 보여주는 사례.