Node.js에서 Kafka 프로듀서를 사용할 때의 도전과 해결책
메시지에 대한 파티션을 선택하는 것은 소비자 간의 부하 분산에 영향을 미치는 중요한 단계입니다. 잘못된 파티셔닝 로직은 불균형을 초래하고 확장성 손실을 야기할 수 있습니다.
흔한 실수는 메시지 키(key)를 기반으로 정수 나눗셈을 사용하는 파티셔닝 함수를 사용하는 것입니다. 키가 숫자가 아니거나 적절히 정수로 변환되지 않는 경우(예: MongoDB ObjectID), 모든 메시지는 하나의 파티션에 집중됩니다.
const producer = await kafka.producer(topic, {
createPartitioner: function () {
return ({ partitionMetadata, message }: PartitionerArgs) => {
return partitionMetadata.length
? partitionMetadata[Number(message.key) % partitionMetadata.length]?.partitionId || 0
: 0;
};
},
});
이 경우 파티션 그룹 내 소비자가 부하를 효율적으로 분산할 수 없게 됩니다. 전체 데이터가 한 파티션에 몰리기 때문입니다.
해결책:
- 프로듀서 레벨에서 파티션 키의 타입과 형식을 검증합니다.
- 파티션 크기 불균형을 모니터링하기 위한 메트릭을 설정합니다.
- 문자열 키를 해싱하는 등 더 신뢰할 수 있는 파티셔닝 알고리즘을 사용합니다.
존재하지 않는 파티션에 메시지 발행
토픽 내 파티션 번호는 0부터 시작합니다. 프로듀서가 사용 가능한 파티션 수를 초과하는 인덱스(예: 파티션 1이 하나뿐인 토픽에 대해)에 메시지를 발행하려 하면 KafkaJS는 에러를 발생시키지 않습니다. send() 메서드는 빈 배열을 반환하며, 메시지는 실제로 기록되지 않습니다.
const result = await producer.send({
topic: 'user_events',
messages: [
{ key: '2', value: 'user_created', partition: 1 }
]
});
console.log(result); // []
해결책:
- 배포 시 구성 파일을 통해 토픽과 파티션을 생성하여 수동 오류를 방지합니다.
- 응용 프로그램 레벨에서
send()응답을 검증합니다. 결과가 비어 있거나 오류가 포함되어 있으면(예:errorCode가 0이 아님) 예외를 던지거나, 로깅하거나, 백업 토픽에 발행하거나, 메트릭을 전송하는 등의 조치를 취합니다. - 발행 전에 토픽 메타데이터(사용 가능한 파티션 목록)를 정기적으로 확인합니다.
비효율적인 프로듀서 사용
각 요청이나 작업마다 새로운 프로듀서 인스턴스(kafka.producer())를 생성하는 것은 상당한 오버헤드를 유발하는 일반적인 실수입니다. 각 새 프로듀서는 Kafka 브로커와 무거운 네트워크 연결을 설정합니다.
app.post('/send-message', async (request) => {
const { topic, message } = request.body;
const producer = kafka.producer(); // 요청별로 새 인스턴스
// ...
});
해결책:
- 싱글톤 패턴 또는 프로듀서 풀을 사용합니다. 하나의 프로듀서 인스턴스를 만들어 모든 발행 작업(
producer.send())에 재사용합니다. 이는 처리량을 개선하고 브로커 부하를 줄입니다. - 멀티스레딩 또는 마이크로서비스 아키텍처에서는 공유된 Kafka 클라이언트와 공유된 프로듀서를 고려합니다.
네트워크 문제 및 재시도 전략
프로듀서와 Kafka 클러스터 사이의 네트워크가 불안정할 수 있습니다. 메시지 전달 신뢰성을 높이기 위해:
request_timeout이벤트를 구성하여 네트워크 문제를 추적합니다.
producer.on('producer.network.request_timeout', (error) => {
console.error({
code: 'producer_network_timeout',
message: error.message,
data: { stack: error.stack },
});
})
- 프로듀서 생성 시 재시도 메커니즘(
retry)을 구성합니다. 기본적으로 재시도 횟수는 5회입니다.
kafka.producer({
retry: { retries: 10 }
});
중요: 적절한 전략 없이(예: 재시도 간격을 늘리지 않고) 재시도 횟수를 늘리는 것은 브로커에 DDoS처럼 부담을 가중시킬 수 있습니다. 증가하는 간격(지수 백오프)을 갖춘 설정을 사용하세요.
큰 메시지 처리
Kafka는 1MB 이상의 메시지를 설계하지 않았습니다. 큰 데이터(5–10MB)를 발행하면 심각한 문제가 발생합니다:
- 자주 발생하는 세그먼트 전환: 최대 세그먼트 크기가 작을 경우(예: 512MB), 큰 메시지는 빠른 전환을 유도하여 시스템 부하를 증가시킵니다.
- 높은 네트워크 트래픽: 모든 In-Sync Replicas(ISR) 간에 큰 메시지를 복제하면 네트워크 부담이 커집니다.
- 처리량 감소: 프로듀서의 메시지 버퍼가 커져 데이터 손실 위험이 증가하고,
acks=-1일 때 확인 응답 대기 시간이 길어집니다. - 자원 소모: 브로커의
os page cache와 Kafka JVM에 더 많은 RAM이 필요해지고, 가비지 컬렉션(GC)으로 인해 CPU 부하가 증가합니다.
메시지 크기 권고사항:
- 다음 공식을 사용합니다:
message.max.bytes = 평균 메시지 크기 * 2, 하지만 1MB 미만. - 큰 파일(영상, 음성, 문서)의 경우 외부 저장소(예: S3)를 사용합니다. Kafka 메시지에는 객체 참조만 전송합니다.
- 외부 저장소 없이 큰 JSON/XML을 전송해야 한다면, 메시지에 다른 소비자에게 중복된 데이터가 포함되어 있을 가능성이 큽니다. 주제를 여러 개의 특화된 주제로 분할하여 필요한 최소 필드만 포함하도록 합니다.
- 대안으로, 더 복잡한 접근법: 큰 메시지를 청크로 나누고 소비자 쪽에서 재조립합니다.
핵심 요약
- 파티셔닝: 잘못된 파티션 계산은 부하 불균형과 확장성 손실을 초래합니다. 키 검증과 메트릭 모니터링은 필수입니다.
- 파티션 존재 여부: 존재하지 않는 파티션에 발행해도 KafkaJS는 에러를 발생시키지 않지만, 메시지는 기록되지 않습니다.
send()응답 검증이 중요합니다. - 프로듀서 효율성: 요청마다 새 프로듀서를 생성하는 것은 비용이 큽니다. 싱글톤 또는 풀을 사용하세요.
- 네트워크 및 재시도: 네트워크 타임아웃 처리 및 재시도 전략을 구성하면 전달 신뢰성이 향상됩니다.
- 메시지 크기: 1MB 이상의 데이터를 전송하면 클러스터 성능에 부정적 영향을 미칩니다. 큰 파일은 외부 저장소를 사용하고, 메시지 구조를 최적화하세요.
— Editorial Team
아직 댓글이 없습니다.