컬리 검색팀이 색인 파이프라인을 개편하며 겪은 카프카 컨슈머 적체와 리밸런싱 문제를 설정 튜닝만으로 해결한 과정을 다룬다. max.poll 설정으로 불필요한 리밸런싱을 막고, 리스너를 Batch로 바꿔 색인 스루풋을 크게 끌어올렸다.
핵심 포인트- 색인에 부가 정보가 별도 토픽으로 추가되며 검색엔진 외부에서 여러 메시지를 조합해야 하는 구조로 변경
- 컨슈머 처리 속도가 발행을 못 따라가 메시지가 적체되고, 컨슈머 그룹에 예기치 못한 리밸런싱이 반복
- 레디스 지표는 정상이라 병목을 카프카로 좁힘
- max-poll-records를 1로 줄여 5분 처리 초과로 인한 리밸런싱을 제거
- 리스너를 Record에서 Batch로 바꿔 poll로 받은 전체 레코드를 한 번에 색인 요청, 스루풋 비약적 향상
상세 정리- AS-IS: 단일 토픽 메시지가 색인에 필요한 모든 정보를 담아 간단한 정제만으로 색인이 가능했다.
- TO-BE: 요구사항 고도화로 부가 정보가 별도 토픽으로 유통돼, 복수 메시지를 하나로 조합해 인덱싱하는 설계가 필요해졌다.
- 1차 구현: 색인을 이루는 상품정보들을 각각 레디스에 저장하고, 나머지 데이터가 레디스에 있으면 합쳐 색인을 요청하는 방식을 택했다.
- 문제 발생: 상품 메시지를 일괄 발행하자 컨슈머가 못 따라가 적체됐고, 컨슈머 그룹 리밸런싱까지 겹쳐 색인 반영이 지연돼 정상 검색이 불가했다.
- 원인 좁히기: 레디스 CPU 등 과부하 정황이 없어 병목을 카프카로 판단했다.
- 리밸런싱 원인: 컨슈머가 기본 설정상 500개 레코드를 5분 내 처리하도록 돼 있는데, 계속 5분을 넘겨 그룹 내 리밸런싱이 발생하는 것으로 추정했다.
- 해결 1: max-poll-records를 1로 극단적으로 줄이자 메시지 한 건 처리는 5분을 넘지 않아 리밸런싱 없이 색인을 구축할 수 있었다.
- 근본 문제: 애초에 500건을 5분 안에 처리하면 리밸런싱이 없었을 것이므로 스루풋 개선이 본질이라 판단했다.
- 리스너 구조: 기본 Record Listener(RecordMessagingMessageListenerAdapter)는 onMessage가 레코드 1건씩만 받아 처리한다.
- 해결 2: listener.type을 batch로 바꾸면 BatchMessagingMessageListenerAdapter가 poll로 가져온 레코드 리스트를 한 번에 받는다.
- 효과: 운영 중인 검색엔진이 Batch 색인 요청을 지원해, 건건이 요청하던 색인을 모아 요청하도록 바꾸자 소비·색인 속도가 비약적으로 올랐고 같은 설정에서도 리밸런싱이 사라졌다.
왜 읽나스프링 카프카 컨슈머에서 리밸런싱과 처리 적체로 고생하는 백엔드 개발자에게 max.poll·Batch Listener 튜닝의 원인 진단과 처방 레퍼런스.