제목, 태그, 카테고리로 검색

모든 글
약 4분 분량 프로젝트/오락가락

[트러블슈팅] Kafka 파티션 불균형으로 처리 지연

목차

한 줄 요약

userId 기반 파티셔닝 때문에 헤비 유저의 이벤트가 한 파티션에 몰리면서 Lag 편차가 10배까지 벌어졌어요. uploadId 기반으로 바꿔서 Lag 편차 1.2배, 처리 완료 시간 p99를 5분에서 1분으로 줄였어요.


증상

Kafka Exporter로 Consumer Lag을 파티션별로 확인하니, 특정 파티션에만 메시지가 몰려 있었어요. 파티션 0의 Lag이 5000인데 파티션 1은 5, 파티션 2는 3이었어요. 파티션 0에 붙은 Consumer만 바쁘게 돌고, 나머지 Consumer는 유휴 상태였어요.

결과적으로 파티션 0에 걸린 사용자의 음성 분석이 5분 넘게 대기하는 반면, 다른 파티션에 걸린 사용자는 바로 처리됐어요.

환경

  • Apache Kafka 3.x (Docker 컨테이너)
  • Spring Kafka
  • 단일 서버 구성

원인: 파티션 키가 userId

기존에 userId를 파티션 키로 쓰고 있었어요. “같은 사용자의 이벤트는 순서대로 처리되어야 한다”는 생각이었거든요.

문제는 활동적인 사용자 한 명이 하루에 100건의 녹음을 올리면, 그 100건이 전부 같은 파티션에 들어간다는 거예요. 비활동 사용자는 하루 3-5건이니, 파티션 간 부하 차이가 수십 배까지 벌어져요.


해결: uploadId 기반 파티셔닝

생각해보니 “같은 사용자의 모든 업로드”가 순서를 보장할 필요는 없었어요. 순서가 필요한 건 “같은 파일의 처리 단계” 뿐이거든요. UPLOADED → CONVERTING → COMPLETED가 순서대로 실행되면 되지, 사용자의 첫 번째 녹음과 두 번째 녹음 사이에 순서가 필요한 건 아니거든요.

uploadId를 파티션 키로 바꿨어요. 업로드마다 UUID가 다르니 해시 분포가 고르게 퍼져요. 같은 파일의 이벤트만 같은 파티션에 들어가면서 순서도 보장돼요.


Consumer Group 구성

각 토픽별로 독립적인 Consumer Group을 구성했어요.

토픽 구성

토픽용도파티션 키
upload-events업로드 완료 이벤트uploadId
processing-status처리 상태 변경uploadId
processing-results처리 결과uploadId
voice-analysis-events음성 분석 요청uploadId
upload-events-retry재시도 대기uploadId
upload-events-dlq최종 실패uploadId

모든 토픽에서 uploadId를 파티션 키로 통일했어요.


결과

지표개선 전개선 후
파티션별 처리량 편차10배1.2배
최대 Consumer Lag5000200
처리 완료 시간 p995분1분
유휴 Consumer 비율66%0%

Before

After


참고 자료

Summary

Resolved 10x partition lag variance caused by userId-based partitioning. Switching to uploadId-based partitioning reduced lag variance to 1.2x and p99 processing time from 5 minutes to 1 minute.


Symptoms

Kafka Exporter showed messages piling up on specific partitions. Partition 0 had a lag of 5000 while partitions 1 and 2 had 5 and 3 respectively. Only the Consumer on partition 0 was busy; the rest were idle.

Users whose events landed on partition 0 waited 5+ minutes for voice analysis, while others were processed immediately.

Environment

  • Apache Kafka 3.x (Docker container)
  • Spring Kafka
  • Single-server setup

Cause: userId as Partition Key

userId was the partition key, based on the assumption that “same user’s events must be processed in order.”

The problem: one active user uploading 100 recordings per day sends all 100 to the same partition. Inactive users upload 3-5 per day, creating tens-of-times load difference between partitions.


Solution: uploadId-based Partitioning

On reflection, ordering wasn’t needed across “all uploads from the same user.” Ordering was only needed for “processing stages of the same file.” UPLOADED → CONVERTING → COMPLETED must be sequential, but a user’s first and second recordings don’t need ordering.

Switching to uploadId as partition key distributes hashes evenly (each upload has a unique UUID). Events for the same file still land on the same partition, preserving ordering.


Consumer Group Configuration

Independent Consumer Groups per topic.

Topic Structure

TopicPurposePartition Key
upload-eventsUpload completion eventsuploadId
processing-statusProcessing state changesuploadId
processing-resultsProcessing resultsuploadId
voice-analysis-eventsVoice analysis requestsuploadId
upload-events-retryRetry queueuploadId
upload-events-dlqFinal failuresuploadId

All topics unified on uploadId as partition key.


Results

MetricBeforeAfter
Per-partition throughput variance10x1.2x
Max Consumer Lag5000200
Processing completion time p995min1min
Idle Consumer ratio66%0%

Before

After


References

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글