학습 목표

이 도메인이 묻는 것

이 섹션의 전형적인 질문 형태
질문 형태실제로 확인하는 것
"재할당 단계를 순서대로 배열하시오" (list order)--generate--execute--verify
"재할당이 끝났는데 정상 복제가 느리다"스로틀 해제를 잊은 상태
"이 설정을 재시작 없이 바꿀 수 있는가?"갱신 모드 3종 구분
"특정 사용자만 대역을 제한하려면?"쿼터 엔티티 계층과 --entity-default
"파티션을 40에서 20으로 줄이는 명령은?"줄일 수 없다는 사실
"복제 계수를 3으로 올리는 방법은?"kafka-topics.sh가 아니라 재할당 JSON

핵심 개념 요약 — 운영 관점

파티션 재할당 3단계 — 이 섹션의 핵심

kafka-reassign-partitions.sh상호 배타적인 세 모드로 동작합니다. 도구는 데이터 분포를 스스로 분석하지 않으므로, 무엇을 옮길지는 운영자가 정합니다.

재할당 3모드
순서모드하는 일놓치면 생기는 일
1 --generate 대상 토픽 목록과 브로커 목록을 받아 후보 계획을 만듭니다. 현재 배치와 제안 배치를 함께 출력합니다. 계획 없이 손으로 JSON을 쓰다 실수합니다. 현재 배치는 롤백용으로 반드시 저장하세요.
2 --execute JSON 계획대로 이동을 시작합니다. 완료를 기다리지 않고 반환합니다. 스로틀 없이 실행하면 정상 트래픽이 복제에 밀려 지연이 튑니다.
3 --verify 진행 상태를 확인하고, 완료되었으면 스로틀을 해제합니다. 스로틀이 영구히 남아 이후 모든 복제가 조여집니다.

스로틀은 다섯 개의 설정으로 구현되며, kafka-reassign-partitions.sh가 자동으로 세팅합니다. 직접 손대는 것보다 도구를 통하는 것이 안전합니다.

스로틀 관련 설정 5개
설정범위역할
leader.replication.throttled.rate브로커 (동적)리더 측 복제 전송 상한(B/s)
follower.replication.throttled.rate브로커 (동적)팔로워 측 복제 수신 상한(B/s)
replica.alter.log.dirs.io.max.bytes.per.second브로커 (동적)브로커 내부 디스크 간 이동 상한
leader.replication.throttled.replicas토픽리더 스로틀이 적용될 레플리카 목록
follower.replication.throttled.replicas토픽팔로워 스로틀이 적용될 레플리카 목록

파티션 수 — 늘릴 수만 있고 공짜가 아닙니다

Kafka는 파티션 수 감소를 지원하지 않습니다. 늘리는 것은 되지만 부작용이 있습니다.

  1. 키 분배가 바뀝니다. 기본 파티셔너는 hash(key) % 파티션수로 배정하므로, 파티션 수가 바뀌면 같은 키가 다른 파티션으로 갑니다. 기존 데이터는 재분배되지 않습니다. 결과적으로 기존 키의 순서 보장이 깨집니다.
  2. auto.offset.reset=latest 컨슈머가 메시지를 놓칠 수 있습니다. 새 파티션이 생기고 컨슈머가 그것을 발견하기 전에 들어온 메시지가 건너뛰어집니다.
  3. 메타데이터 전파 지연이 있습니다. metadata.max.age.ms 주기까지는 클라이언트가 새 파티션을 모르므로 분배가 한동안 불균등하고 lag이 튑니다.

복제 계수 변경 — kafka-topics.sh로는 안 됩니다

복제 계수는 --alter로 바꿀 수 없습니다. 재할당 JSON에 레플리카를 추가하고 --execute하는 방식만 있습니다. 예를 들어 브로커 5에만 있던 파티션을 5·6·7 세 곳으로 늘리면 RF가 1에서 3이 됩니다.

동적 설정 — 갱신 모드 3종

브로커 설정은 재시작 없이 바꿀 수 있는 것과 그렇지 않은 것이 나뉩니다. 공식 설정 표의 Dynamic Update Mode 열이 그 구분입니다.

갱신 모드 3종
모드의미
read-only 브로커 재시작이 필요합니다 listeners, advertised.listeners, broker.rack, replica.lag.time.max.ms, log.dirs
per-broker 브로커별로 동적 변경 가능 listener.security.protocol.map, ssl.keystore.location, sasl.enabled.mechanisms
cluster-wide 클러스터 기본값으로 동적 변경 가능. 테스트용으로 브로커별 지정도 가능 min.insync.replicas, unclean.leader.election.enable, num.io.threads, message.max.bytes, log.segment.bytes

쿼터 — 엔티티 계층

기본적으로 클라이언트는 무제한 쿼터를 받습니다. 쿼터는 (user, client-id) · user · client-id 세 수준에 설정할 수 있고, 각 수준에 구체 이름(--entity-name)과 기본값(--entity-default)을 줄 수 있습니다.

쿼터 설정 키 3종
제한 대상단위
producer_byte_rate프로듀서 쓰기 대역bytes/sec
consumer_byte_rate컨슈머 읽기 대역bytes/sec
request_percentage요청 처리 스레드 점유율%

리텐션과 정리 정책

디스크 사용량을 실제로 좌우하는 것은 리텐션 이 아니라 리텐션과 세그먼트 크기의 조합입니다. 삭제는 세그먼트 단위이고, 활성 세그먼트는 삭제되지 않습니다.

retention 삭제 단위 — 레코드가 아니라 세그먼트 단위로 지워집니다 시간축 위에 네 개의 세그먼트로 이루어진 파티션 로그를 그린 그림입니다. 가로축은 10일 전부터 지금까지이고, retention.ms 기본값 604800000 즉 7일에 해당하는 지금에서 7일 전 지점에 세로 경계선이 그려져 있습니다. 세그먼트 S0 과 S1 은 세그먼트 안의 가장 큰 타임스탬프가 경계보다 오래되어 세그먼트 전체가 삭제됩니다. 세그먼트 S2 는 8일 전부터 3일 전까지의 레코드를 담고 있습니다. 이 세그먼트의 가장 큰 타임스탬프는 3일 전이므로 아직 보관 대상입니다. 그 결과 S2 안에 있는 8일 전, 7일 반 전 레코드까지 경계를 넘겼는데도 함께 남습니다. 세그먼트 S3 은 활성 세그먼트여서 애초에 삭제 대상이 아닙니다. 즉 실제 보관량은 retention.ms 만으로 정해지지 않고 segment.ms 와 segment.bytes 가 함께 정합니다. 삭제는 레코드 단위로 일어나지 않습니다. retention 삭제 단위 — 삭제는 세그먼트 통째로 일어납니다 retention.ms 경계 = 지금 − 7일 파티션 로그 orders-0 S0 S1 S2 — 8일 전 ~ 3일 전 레코드 S3 (활성) 10일 전 8일 전 6일 전 4일 전 2일 전 지금 판정 세그먼트 삭제 보관 — 최대 타임스탬프(3일 전)가 경계 안쪽 활성 — 삭제 대상 아님 핵심 — S2 안의 8일 전 · 7일 반 전 레코드는 경계를 넘겼는데도 남아 있습니다. 시간 판정은 세그먼트 안 가장 큰 타임스탬프 하나로 합니다. 레코드마다 따로 지우지 않습니다. 그래서 실제 보관량은 항상 retention.ms 이상이 됩니다. 보관량을 실제로 좌우하는 설정 segment.ms 기본 604800000 (7일) segment.bytes 기본 1073741824 (1 GiB) 보관 기준 설정 retention.ms 기본 604800000 (7일) retention.bytes 기본 −1 (크기 제한 없음) 7일이 지나면 정확히 사라진다고 기대하려면 segment.ms 를 보관 기간보다 훨씬 짧게 잡아야 합니다.
retention 삭제 단위 — 세그먼트 단위로 삭제되므로 segment.ms/segment.bytes가 실제 보관량을 좌우함

필수 CLI 명령어

재할당 1단계 — 계획 생성
# 옮길 토픽 목록을 JSON 으로 작성
cat > topics-to-move.json <<'EOF'
{
  "topics": [ { "topic": "orders" }, { "topic": "payments" } ],
  "version": 1
}
EOF

# 대상 브로커를 지정해 후보 계획 생성
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --topics-to-move-json-file topics-to-move.json \
    --broker-list "5,6" --generate

# 출력의 "Current partition replica assignment" 를 rollback.json 으로 저장하고,
# "Proposed partition reassignment configuration" 을 reassign.json 으로 저장한다
재할당 2단계 — 스로틀을 걸고 실행
# 브로커 간 50MB/s, 브로커 내부 디스크 간 100MB/s 로 제한
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --reassignment-json-file reassign.json --execute \
    --throttle 50000000 --replica-alter-log-dirs-throttle 100000000

# 진행 중에 스로틀을 올리려면 --additional 로 같은 JSON 을 다시 실행
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --additional --execute --reassignment-json-file reassign.json --throttle 700000000
재할당 3단계 — 확인과 스로틀 해제 (반드시 실행)
# --execute 에 쓴 것과 "같은" JSON 을 넘겨야 합니다
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --reassignment-json-file reassign.json --verify

# 완료 시 출력에 다음이 보여야 스로틀이 해제된 것입니다
#   Clearing broker-level throttles on brokers 1,2,3
#   Clearing topic-level throttles on topic orders

# 스로틀이 남아 있는지 직접 확인
bin/kafka-configs.sh --describe --bootstrap-server localhost:9092 --entity-type brokers
bin/kafka-configs.sh --describe --bootstrap-server localhost:9092 --entity-type topics
복제 계수 올리기 — 커스텀 재할당 JSON
# 브로커 5 에만 있던 파티션에 6,7 을 추가 → RF 1 에서 3 으로
cat > increase-rf.json <<'EOF'
{"version":1,"partitions":[{"topic":"orders","partition":0,"replicas":[5,6,7]}]}
EOF

bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --reassignment-json-file increase-rf.json --execute

bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --reassignment-json-file increase-rf.json --verify
동적 설정 — 토픽 · 브로커
# 토픽 설정 추가 / 삭제
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name orders --alter --add-config retention.ms=86400000
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name orders --alter --delete-config retention.ms

# 브로커 개별 설정 (per-broker)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers \
    --entity-name 0 --alter --add-config log.cleaner.threads=2

# 현재 동적 설정 조회
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers \
    --entity-name 0 --describe

# 브로커 로거 레벨 변경 (재시작 없이)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --broker-logger 0 \
    --alter --add-config kafka.server.ReplicaManager=DEBUG

# 오프라인 브로커의 설정을 다루려면 컨트롤러로
bin/kafka-configs.sh --bootstrap-controller localhost:9093 --entity-type brokers \
    --entity-name 1 --describe
쿼터 설정과 조회
# (user, client-id) 조합
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
    --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' \
    --entity-type users --entity-name user1 --entity-type clients --entity-name clientA

# user 단위
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
    --add-config 'producer_byte_rate=1048576' --entity-type users --entity-name user1

# 모든 user 의 기본 쿼터 (--entity-name 대신 --entity-default)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
    --add-config 'producer_byte_rate=1048576' --entity-type users --entity-default

# 조회 — 이름을 생략하면 해당 타입 전체
bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe --entity-type users
로그 디렉터리 · 브로커 정리 작업
# 특정 로그 디렉터리를 신규 배치 대상에서 제외 (cordon)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
    --add-config cordoned.log.dirs=/data/dir1 --entity-type brokers --entity-name 1

# 브로커 전체를 cordon
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
    --add-config cordoned.log.dirs="*" --entity-type brokers --entity-name 1

# cordon 해제 (브로커가 오프라인이면 컨트롤러로)
bin/kafka-configs.sh --bootstrap-controller localhost:9093 --alter \
    --delete-config cordoned.log.dirs --entity-type brokers --entity-name 1

반드시 외워야 할 설정값

Kafka 4.3 기준. 갱신 모드를 함께 표기했습니다.
설정 기본값 갱신 모드 운영 포인트
min.insync.replicas 1 cluster-wide 장애 중에도 즉시 조정할 수 있습니다. RF=3이면 2가 표준입니다.
unclean.leader.election.enable false cluster-wide 최후의 수단. 켠 뒤 반드시 되돌리세요.
message.max.bytes 1048588 cluster-wide 토픽 레벨 max.message.bytes가 오버라이드합니다.
log.segment.bytes 1073741824 cluster-wide 토픽 레벨은 segment.bytes입니다.
num.io.threads 8 cluster-wide RequestHandlerAvgIdlePercent를 보고 조정합니다.
num.network.threads 3 cluster-wide NetworkProcessorAvgIdlePercent를 보고 조정합니다.
num.replica.fetchers 1 cluster-wide 복제가 못 따라잡을 때 올립니다.
background.threads 10 cluster-wide
log.cleaner.threads 1 cluster-wide compaction이 밀리면 올립니다.
advertised.listeners null read-only 변경에 롤링 재시작이 필요합니다.
replica.lag.time.max.ms 30000 read-only 동적으로 못 바꿉니다. 장애 중 회피 수단이 아닙니다.
log.retention.check.interval.ms 300000
(5분)
read-only 리텐션 정리가 5분 주기로 돕니다. 즉시 줄어들지 않는 이유입니다.
retention.bytes
topic
-1
(무제한)
크기 기준 리텐션은 기본적으로 꺼져 있습니다. 디스크 보호에 유용합니다.
min.cleanable.dirty.ratio
topic
0.5 compaction 시작 임계값. 낮추면 더 자주 정리하지만 CPU를 씁니다.
delete.retention.ms
topic
86400000
(1일)
compacted 토픽에서 tombstone을 유지하는 시간입니다.
group.initial.rebalance.delay.ms 3000 read-only 그룹 최초 형성 시 멤버를 모으려고 일부러 기다리는 시간입니다.
delete.topic.enable true read-only false면 삭제 요청이 무시됩니다.

장애 시나리오와 대응

시나리오 1 — 재할당이 끝난 뒤부터 URP가 만성적으로 남는다

  1. 재할당 이력을 확인합니다. 최근에 --execute --throttle을 했다면 1순위 의심 대상입니다.
  2. kafka-configs.sh --describe --entity-type brokersleader.replication.throttled.rate·follower.replication.throttled.rate가 남아 있는지 봅니다.
  3. --entity-type topics*.throttled.replicas도 확인합니다.
  4. 원래 JSON이 있으면 --verify를 실행해 정상 경로로 해제합니다.
  5. JSON을 잃었다면 kafka-configs.sh --alter --delete-config로 다섯 개 설정을 직접 지웁니다.

시나리오 2 — 재할당이 며칠째 still in progress

  1. --verify로 어느 파티션이 남았는지 확인합니다.
  2. FetcherLagMetricsConsumerLag감소 추세인지 봅니다. 줄지 않으면 진행이 멈춘 것입니다.
  3. 브로커의 BytesInPerSec와 스로틀 값을 비교합니다. max(BytesInPerSec) > throttle이면 구조적으로 따라잡을 수 없습니다.
  4. --additional --execute --throttle로 상한을 올립니다. 트래픽이 적은 시간대에 올리는 것이 안전합니다.
  5. 대상 브로커의 디스크가 가득 차 가는지도 확인합니다. 목적지 용량 부족도 같은 증상을 냅니다.

시나리오 3 — 설정을 바꿨는데 반영되지 않는다

설정 미반영의 원인 판별
확인원인조치
그 설정이 read-only인가 동적 변경이 불가능한 설정 롤링 재시작이 필요합니다
토픽 레벨 오버라이드가 있는가 브로커 값을 바꿔도 토픽 값이 우선 --entity-type topics --describe로 확인 후 토픽 값을 바꾸거나 지웁니다
리텐션인데 디스크가 안 줄어드는가 정리 주기(log.retention.check.interval.ms 기본 5분)와 세그먼트 단위 삭제 기다리거나 segment.ms/segment.bytes를 줄여 세그먼트가 굴러가게 합니다
클라이언트 쪽 설정인가 브로커 설정으로 클라이언트 동작을 바꾸려 함 프로듀서·컨슈머 설정을 함께 조정해야 합니다(설정 치트시트)

시나리오 4 — 한 애플리케이션이 클러스터 전체를 느리게 만든다

  1. RequestHandlerAvgIdlePercent가 0.3 미만으로 떨어졌는지 확인합니다.
  2. 사용자·클라이언트별 kafka.server:type={Produce|Fetch},user=...,client-id=... 메트릭으로 어느 주체가 대역을 먹는지 특정합니다.
  3. 해당 주체에 producer_byte_rate 또는 consumer_byte_rate 쿼터를 부여합니다.
  4. 요청 수 자체가 문제(작은 배치를 대량으로)라면 request_percentage로 스레드 점유를 제한합니다.
  5. 재발 방지를 위해 --entity-default전체 기본 쿼터를 걸고, 신뢰된 클라이언트만 개별 상향합니다.

시나리오 5 — 4.3으로 롤링 업그레이드

이 절차는 list order 유형의 단골입니다. 순서를 그대로 외우세요.

  1. 전제 확인: KRaft 모드이고 소프트웨어·메타데이터 버전이 최소 3.3.x여야 합니다. 3.3.x보다 오래된 KRaft 클러스터는 3.9.x를 먼저 경유하는 것이 권장됩니다.
  2. 브로커를 한 대씩 종료하고, 바이너리를 교체하고, 재시작합니다. 각 노드마다 URP가 0으로 돌아오는지 확인하고 다음 노드로 갑니다.
  3. 모든 노드가 새 버전으로 올라오면 동작과 성능을 검증합니다. 이 단계에서는 메타데이터 버전이 아직 예전 값이라 되돌릴 수 있습니다.
  4. 검증이 끝나면 kafka-features.sh --bootstrap-server localhost:9092 upgrade --release-version 4.3으로 업그레이드를 확정(finalize)합니다.
  5. 확정 후에는 메타데이터 다운그레이드가 불가능합니다. 4.3은 메타데이터 변경을 포함하므로, 사이에 메타데이터 변경이 없는 버전 간에만 다운그레이드가 가능합니다.

자주 나오는 함정

관련 케이스 스터디

설정값 전체 목록은 설정 치트시트, 명령 목록은 CLI 치트시트, 스토리지 배경은 7장 스토리지·리텐션·컴팩션에 있습니다. 이 페이지의 조치가 어떤 메트릭으로 확인되는지는 Observability에서 다룹니다.

미니 퀴즈

절차 배열과 갱신 모드 구분이 중심입니다.

공식 문서 출처