# Node.js에서 효과적인 Kafka 메시지 처리: 컨슈머, 리밸런싱 및 스케일링
Node.js에서 Kafka 컨슈머를 제대로 구현하려면 기본 API뿐 아니라 이 분산 큐의 아키텍처적 특징까지 이해해야 합니다. 파티션 설정, 읽기 전략, 오프셋 관리에서 실수하면 메시지 손실, 과도한 리밸런싱, 처리량 저하가 발생합니다. 이 글에서는 KafkaJS를 사용해 안정적이고 고성능 컨슈머를 만드는 핵심 요소를 자세히 살펴봅니다.
스케일링의 기반이 되는 파티션
Kafka 토픽에서 메시지 소비를 스케일링하는 데는 파티션 수가 제한 요인입니다. 아무리 많은 인스턴스를 실행해도 그룹 내 활성 컨슈머 수는 토픽의 파티션 수를 초과할 수 없습니다. 공식은 간단합니다: N_consumers ≤ N_partitions. 이 비율을 초과하면 일부 인스턴스가 할당 없이 유휴 상태로 남아 그룹 코디네이터에 불필요한 부하를 줍니다.
초기 파티션 수 선택은 전략적 결정입니다. Kafka는 파티션을 동적으로 늘릴 수 있지만, 이는 키에 따른 메시지 순서 보장을 깨뜨립니다: 동일 키의 새 메시지가 이전 메시지와 다른 파티션에 배치될 수 있습니다. 유연성을 위해 1부터 24까지의 컨슈머 수에 고르게 분배되는 숫자(예: 24)를 선택하세요.
주의할 점: 전체 클러스터의 총 파티션 수는 200,000개를 초과하지 마세요. 트래픽이 적은 토픽에는 과도한 파티션은 무의미합니다.
병렬 처리와 복제본 선택
하나의 컨슈머에 여러 파티션이 할당되어도 기본적으로 처리는 순차적입니다. 인스턴스 내 병렬 처리를 위해 partitionsConsumedConcurrently 옵션을 사용하세요:
consumer.run({
partitionsConsumedConcurrently: 3,
eachMessage: async ({ topic, partition, message }) => {
// processing
},
});
이 설정은 단일 파티션 내 순서 보장은 유지하지만 파티션 간에는 하지 않습니다. 처리 로직을 깨뜨리지 않으면서 처리량을 안전하게 높여줍니다.
지리적으로 분산된 클러스터에서는 네트워크 지연이 중요합니다. 기본적으로 컨슈머는 쓰기도 담당하는 리더 복제본에서 읽어 병목이 발생합니다. 해결책은 랙 인식 복제입니다. 브로커에 broker.rack 매개변수를 설정하고 클라이언트에서 rackId를 지정하세요:
const consumer = kafka.consumer({
groupId: 'user-service',
rackId: 'sibir_tomsk'
});
그러면 Kafka가 같은 "랙 도메인"의 복제본으로 요청을 라우팅해 지연을 최소화합니다. 대부분의 시나리오에서 약간의 복제 지연은 허용 가능하며, Kafka는 엄격한 실시간 처리를 위해 설계되지 않았습니다.
오프셋 관리와 초기 읽기
새 컨슈머 그룹은 흔히 이런 문제를 겪습니다: 과거 메시지를 보지 못합니다. 원인은 auto.offset.reset 매개변수로, 기본값이 latest이기 때문입니다. __consumer_offsets에 커밋된 오프셋이 없으면 파티션 끝부터 시작합니다.
대안은 earliest지만, 안전장치 없이 사용하면 수년치 이력 처리가 서비스와 하류 시스템을 과부하할 위험이 있습니다. 더 안전한 방법:
- 전체 이력이 필요하면 구독 시
fromBeginning: true사용:
await consumer.subscribe({ topic: 'user_events', fromBeginning: true });
이 옵션은 첫 연결 시에만 작동합니다. 첫 오프셋 커밋 후 Kafka는 fromBeginning을 무시합니다.
- 정확한 복구를 위해 컨슈머를 중지한 후 CLI나 UI 도구(KafkaUI, Kadeck)로 오프셋을 수동 설정하세요.
이 제어로 우발적인 오프셋 되감기를 막고 처리 무결성을 보장합니다.
리밸런싱 전략과 함정
리밸런싱은 그룹 멤버십 변경 시 파티션을 재분배합니다. KafkaJS는 Eager 전략만 지원하며, 모든 컨슈머가 처리를 일시 중지("stop-the-world")하고 현재 작업을 마무지른 후 오프셋을 커밋하고 새 할당을 기다립니다.
빈번한 리밸런싱은 가용성을 떨어뜨리고 지연을 증가시킵니다. 주요 원인:
- 하나의 프로세스에 혼합 기능—HTTP 서버, 백그라운드 작업, 컨슈머가 같은 인스턴스에 있음. 메시지 처리 외 치명적 오류가 전체 프로세스를 크래시시켜 리밸런싱을 유발합니다.
- 잘못된 타임아웃—
sessionTimeout과heartbeatInterval을 맞춰야 합니다. 경험칙:heartbeatInterval ≤ sessionTimeout / 3. - 느린 메시지 처리—한 메시지 처리 시간이
rebalanceTimeout을 초과하면 그룹에서 추방됩니다.
진단을 위해 REBALANCING 이벤트에 구독하세요:
consumer.on(consumer.events.REBALANCING, ({ payload }) => {
console.log('Rebalancing started for group:', payload.groupId);
});
이렇게 메트릭을 수집해 비정상 리밸런싱 빈도를 파악할 수 있습니다.
Kafka 2.3+는 Static Membership와 CooperativeStickyAssignor를 제공하지만 KafkaJS는 아직 지원하지 않습니다. 안정성을 위해 아키텍처적 수정을 해야 합니다.
주요 요약
- 파티션 수는 컨슈머 수평 스케일링의 상한을 정합니다.
- 병렬 처리(
partitionsConsumedConcurrently)는 파티션별 순서를 잃지 않으면서 속도를 높입니다. - 로컬 복제본 읽기(
rackId)는 지리 분산 환경에서 네트워크 지연을 줄입니다. - 새 컨슈머 그룹은 기본적으로 이력을 건너뜁니다—
fromBeginning이나 수동 오프셋 설정을 사용하세요. - 불필요한 리밸런싱을 피하기 위해 컨슈머를 다른 앱 구성 요소와 분리하세요.
— Editorial Team
아직 댓글이 없습니다.