CCAAK · 섹션 4
Deployment Architecture
이 섹션은 한 번 정하면 오래 유지되는 결정들을 다룹니다. 컨트롤러를 몇 대 둘지, 역할을 분리할지, 랙을 어떻게 나눌지, 디스크를 어떻게 붙일지, 그리고 데이터센터를 어떻게 잇을지입니다. 장애 빈도는 낮지만 숫자를 정확히 외우면 확실히 맞힐 수 있는 문항이 많아 투자 효율이 좋습니다.
학습 목표
- 컨트롤러 수를 3 또는 5로 정하는 근거(
2N+1)와 각각의 내결함성을 계산할 수 있습니다. process.roles3가지 조합의 용도와 combined 모드의 한계를 설명할 수 있습니다.- 정적 쿼럼과 동적 쿼럼을 구분하고, 컨트롤러를 추가·제거하는 절차를 순서대로 말할 수 있습니다.
broker.rack이 복제 배치에 미치는 효과를min(랙 수, RF)로 설명할 수 있습니다.- 멀티 DC에서 단일 클러스터 확장이 아니라 MirrorMaker 2를 쓰는 이유를 말할 수 있습니다.
이 도메인이 묻는 것
| 질문 형태 | 실제로 확인하는 것 |
|---|---|
| "컨트롤러 5대 중 몇 대까지 잃어도 되는가?" | 2N+1과 과반 규칙 |
"프로덕션에서 권장되는 process.roles는?" | 역할 분리 권장, combined 모드의 한계 |
| "랙 3개, RF=3일 때 복제 배치는?" | min(랙 수, RF)개 랙에 분산 |
| "두 데이터센터에 걸친 단일 클러스터의 문제는?" | 복제 지연과 권장 구성(DC별 클러스터 + 미러링) |
| "MM2로 복제된 토픽의 이름은?" | DefaultReplicationPolicy의 {source}.{topic} |
| "컨트롤러 추가 절차를 순서대로" (list order) | 프로비저닝 → 기동 → 복제 확인 → add-controller |
핵심 개념 요약 — 운영 관점
노드 롤 — process.roles
| 값 | 역할 | 용도 |
|---|---|---|
broker |
데이터 저장·서빙. 메타데이터 쿼럼에는 observer로만 참여 | 프로덕션 브로커 노드 |
controller |
메타데이터 쿼럼의 voter. 데이터 파티션은 갖지 않음 | 프로덕션 컨트롤러 노드 |
broker,controller |
둘 다 (combined 서버) | 개발·소규모 환경. 중요 운영 환경에서는 권장되지 않습니다 |
컨트롤러 쿼럼 사이징 — 3 vs 5
메타데이터 쿼럼은 Raft이므로 과반이 살아 있어야 쓰기(메타데이터 변경)가 가능합니다.
동시 장애 N건을 견디려면 컨트롤러가 2N+1대 필요합니다.
| 컨트롤러 수 | 과반 | 견딜 수 있는 동시 장애 | 선택 기준 |
|---|---|---|---|
| 1 | 1 | 0 | 개발 전용. 쿼럼 개념 실습용 |
| 3 | 2 | 1 | 대부분의 프로덕션. 비용과 안전의 균형 |
| 5 | 3 | 2 | 가용 영역 3개 이상, 유지보수 중 추가 장애까지 견뎌야 할 때 |
| 짝수(예: 4) | 3 | 1 | 의미 없음 — 3대와 내결함성이 같은데 비용만 늘어납니다 |
__cluster_metadata, Raft 리더, 스냅샷의 관계
정적 쿼럼 vs 동적 쿼럼
KRaft에는 두 가지 운영 방식이 있습니다. 어느 쪽인지는 포맷 시점에 결정됩니다.
| 구분 | 정적 쿼럼 | 동적 쿼럼 |
|---|---|---|
| 쓰는 설정 | controller.quorum.voters에 모든 컨트롤러의 ID·호스트·포트를 명시 |
controller.quorum.bootstrap.servers. 클라이언트의 bootstrap.servers처럼 일부만 적어도 동작 |
| 컨트롤러 추가·제거 | 불가 | 가능 |
| 판별 방법 | kraft.version의 FinalizedVersionLevel이 0 또는 없음 |
kraft.version이 1 이상 |
| 도입 시점 | 초기 KRaft 방식 | kraft.version=1 (release-version 4.1)에서 추가 |
랙 인식 — broker.rack
브로커에 broker.rack=my-rack-id를 붙이면
토픽 생성·수정·재배치 시 같은 파티션의 레플리카가 서로 다른 랙에 퍼집니다.
정확히는 한 파티션이 min(랙 수, replication factor)개의 랙에 걸칩니다.
클라우드에서는 가용 영역(AZ)을 랙으로 쓰는 것이 일반적입니다.
| 랙 수 | RF | 파티션이 걸치는 랙 수 | 한 랙 전체 장애 시 |
|---|---|---|---|
| 3 | 3 | 3 | 레플리카 1개 손실. ISR 2개 유지 → min.insync.replicas=2로 쓰기 계속 |
| 2 | 3 | 2 | 한 랙에 2개가 있을 수 있어 레플리카 2개 손실 가능 |
| 3 | 2 | 2 | 레플리카 1개 손실. ISR 1개 → min.insync.replicas=2면 쓰기 중단 |
브로커 사이징과 디스크
| 항목 | 기준 | 근거 |
|---|---|---|
| 메모리 | 대략 쓰기 처리량 × 30초를 버퍼로 잡습니다 |
활성 리더·라이터를 버퍼링할 만큼의 페이지 캐시가 필요합니다 |
| 디스크 | 디스크 처리량이 병목입니다. 개수가 많은 편이 유리합니다 | 공식 문서가 예로 든 구성은 7200rpm SATA 8개입니다 |
| 디스크 구성 | JBOD(여러 log.dirs) 또는 RAID. 애플리케이션 로그와 데이터 디스크를 분리하세요 |
RAID는 디스크 간 부하 분산에 유리하지만 쓰기 처리량과 용량을 잃습니다 |
| 파일시스템 | XFS 권장. 마운트 옵션 noatime |
XFS가 EXT4보다 Request Local Time이 낮고 변동성도 작았습니다(160ms 대 250ms+) |
| 파일 디스크립터 | 최소 100000을 시작점으로 | 세그먼트 파일 + 연결 수만큼 필요합니다 |
vm.max_map_count |
파티션 수에 맞춰 상향. 세그먼트 1개 = map area 2개 | 기본값이 65535 근처인 배포판에서 파티션을 과하게 올리면 Map failed로 죽습니다 |
| flush 정책 | 기본값 유지(애플리케이션 fsync 비활성) | 내구성은 복제로 확보합니다. fsync를 강제하면 지연이 늘고 디스크 사용 패턴이 나빠집니다 |
| Java | 17 · 21 · 25 완전 지원. 브로커는 최소 17 | Java 8은 4.0에서 제거되었습니다. 11은 clients·streams 등 일부 모듈만 |
| OS | Linux 권장. Windows는 잘 지원되는 플랫폼이 아닙니다 | 공식 문서 명시 |
멀티 데이터센터
권장 구성은 DC마다 로컬 Kafka 클러스터를 두고, 애플리케이션은 자기 DC의 클러스터만 쓰고, 클러스터 사이를 미러링하는 것입니다. 전체 데이터가 필요한 애플리케이션을 위해 집계(aggregate) 클러스터를 따로 두고 각 DC에서 미러링해 채웁니다.
MirrorMaker 2
MM2는 Kafka Connect 프레임워크 위에 만들어진 클러스터 간 복제 도구입니다. 클러스터 내부 복제(레플리카)와는 완전히 다른 것입니다. 토픽 데이터와 설정, 컨슈머 그룹과 오프셋, ACL을 복제하고 파티셔닝을 보존합니다.
| 구성 | 흐름 표기 | 용도 |
|---|---|---|
| Active/Passive | A->B | 재해 복구 대기 클러스터 |
| Active/Active | A->B, B->A | 양방향 고가용성 |
| 집계 | A->K, B->K, C->K | 여러 DC를 한 클러스터로 모으기 |
| 팬아웃 | K->A, K->B, K->C | 중앙에서 엣지로 배포 |
# 클러스터 별칭 정의
clusters = us-west, us-east
us-west.bootstrap.servers = broker3-west:9092
us-east.bootstrap.servers = broker5-east:9092
# 기본 복제 대상: 모든 토픽
topics = .*
# 흐름 활성화와 흐름별 오버라이드
us-west->us-east.enabled = true
us-west->us-east.topics = orders.*, payments.*
# 워커 수보다 태스크를 넉넉히 (기본값 1은 너무 작습니다)
tasks.max = 5
# 클러스터별 클라이언트 설정 오버라이드
us-west.consumer.isolation.level = read_committed
us-east.producer.compression.type = zstd
# 이 프로세스가 담당할 대상 클러스터만 지정 (대상 클러스터 근처에서 실행하는 것이 일반적)
bin/connect-mirror-maker.sh connect-mirror-maker.properties --clusters us-east
# 지정하지 않으면 설정에 정의된 모든 흐름을 이 프로세스가 담당합니다
bin/connect-mirror-maker.sh connect-mirror-maker.properties
| 설정 | 기본값 | 의미 |
|---|---|---|
replication.policy.class |
DefaultReplicationPolicy |
복제된 토픽 이름을 {source}.{topic}으로 만듭니다. 구분자는 replication.policy.separator(기본 .) |
replication.factor |
2 | 대상 클러스터에 만들 복제 토픽의 RF. 기본값이 3이 아닙니다 |
tasks.max |
1 | 공식 문서는 최소 2, 가능하면 더 크게 두라고 권고합니다 |
sync.group.offsets.enabled |
false |
대상 클러스터의 __consumer_offsets에 오프셋을 직접 쓰는 기능. 기본은 꺼져 있습니다 |
emit.checkpoints.enabled |
true |
체크포인트 토픽에 오프셋 매핑을 기록 (주기 기본 60초) |
sync.topic.configs.enabled |
true |
토픽 설정도 복제 (주기 기본 600초) |
sync.topic.acls.enabled |
true |
ACL도 복제 (주기 기본 600초) |
refresh.topics.interval.seconds |
600 | 새 토픽·파티션 탐지 주기. 새 토픽이 늦게 복제되는 이유입니다 |
offset.lag.max |
100 | 오프셋 동기 레코드를 새로 쓰기 전 허용하는 최대 lag |
groups.exclude |
console-consumer-.*,connect-.*,__.* |
기본적으로 제외되는 그룹 패턴 |
topics.exclude |
mm2.*\.internal,.*\.replica,__.* |
내부 토픽과 이미 복제된 토픽을 제외해 순환 복제를 막습니다 |
필수 CLI 명령어
# 클러스터 ID 생성 (전 노드가 같은 값을 씁니다)
CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
# (권장) 첫 컨트롤러 하나로 부트스트랩한 뒤 나머지를 동적으로 추가
bin/kafka-storage.sh format --cluster-id $CLUSTER_ID --standalone \
--config config/controller.properties
# 또는 처음부터 여러 voter 로 부트스트랩 (모든 컨트롤러에서 같은 값으로 실행)
bin/kafka-storage.sh format --cluster-id $CLUSTER_ID \
--initial-controllers "0@controller-0:9093:$UUID0,1@controller-1:9093:$UUID1,2@controller-2:9093:$UUID2" \
--config config/controller.properties
# 기존 클러스터에 합류할 브로커·컨트롤러
bin/kafka-storage.sh format --cluster-id $CLUSTER_ID \
--config config/server.properties --no-initial-controllers
# 리더·에폭·voter·observer
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status
# 노드별 Lag / LastFetchTimestamp / LastCaughtUpTimestamp
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --replication
# kraft.version 이 0 이면 정적, 1 이상이면 동적 쿼럼
bin/kafka-features.sh --bootstrap-controller localhost:9093 describe
# 기능 버전 업그레이드
bin/kafka-features.sh --bootstrap-server localhost:9092 upgrade --feature kraft.version=1
# 추가: 프로비저닝 → 기동 → --replication 으로 따라잡았는지 확인 → add-controller
bin/kafka-metadata-quorum.sh --command-config config/controller.properties \
--bootstrap-controller localhost:9093 add-controller
# 제거: 노드를 내리기 전에 먼저 쿼럼에서 뺀다
bin/kafka-metadata-quorum.sh --bootstrap-controller localhost:9093 remove-controller \
--controller-id 3 --controller-directory-id <directory-id>
# 1) cordon: 이 브로커의 로그 디렉터리를 신규 배치 대상에서 제외
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter \
--add-config cordoned.log.dirs="*" --entity-type brokers --entity-name 1
# 2) 이 브로커의 모든 파티션을 다른 브로커로 재할당 (계획은 직접 작성)
# 절차는 Cluster Configuration 페이지 참고
# 3) 브로커 종료 후 클러스터에서 등록 해제
bin/kafka-cluster.sh unregister --bootstrap-server localhost:9092 --id 1
# 메타데이터 로그 세그먼트 디코딩
bin/kafka-dump-log.sh --cluster-metadata-decoder \
--files metadata_log_dir/__cluster_metadata-0/00000000000000000000.log
# 스냅샷을 대화식으로 탐색
bin/kafka-metadata-shell.sh \
--snapshot metadata_log_dir/__cluster_metadata-0/00000000000000007228-0000000001.checkpoint
반드시 외워야 할 설정값
| 설정 | 기본값 | 의미 | 운영 포인트 |
|---|---|---|---|
process.roles |
비어 있음 | 노드 역할 | KRaft에서 필수 설정입니다. |
node.id |
비어 있음 | 노드 식별자 | 클러스터 내 유일해야 합니다. read-only. |
controller.quorum.bootstrap.servers |
빈 문자열 | 컨트롤러 탐색 주소 | 모든 브로커·컨트롤러가 설정해야 합니다. |
controller.quorum.fetch.timeout.ms |
2000 | 이 시간 동안 리더에서 fetch를 못 하면 새 선거를 시작 | 컨트롤러 노드 간 네트워크가 불안정하면 선거가 반복됩니다. |
controller.quorum.election.timeout.ms |
1000 | 선거 결론이 안 날 때 재선거까지 대기 | — |
metadata.log.dir |
null |
메타데이터 로그 위치 | 미설정 시 log.dirs의 첫 번째를 씁니다. 전용 디스크에 두는 것이 안전합니다. |
log.dirs |
null |
데이터 디렉터리 목록 | 미설정 시 log.dir(기본 /tmp/kafka-logs)이 쓰입니다. /tmp는 프로덕션 금지. |
broker.rack |
null |
랙·AZ 식별자 | 없으면 랙 인식 배치가 동작하지 않습니다. read-only. |
num.network.threads |
3 | 네트워크 스레드 수 | NetworkProcessorAvgIdlePercent가 0.3 미만이면 검토 대상입니다. |
num.io.threads |
8 | 요청 처리 스레드 수 | RequestHandlerAvgIdlePercent가 0.3 미만이면 검토 대상입니다. |
num.recovery.threads.per.data.dir |
2 | 기동 시 로그 복구 스레드 | 비정상 종료 후 기동이 오래 걸리면 올립니다. |
queued.max.requests |
500 | 요청 큐 깊이 | 큐가 차면 네트워크 스레드가 블록됩니다. |
socket.request.max.bytes |
104857600 (100 MiB) |
단일 요청 최대 크기 | — |
connections.max.idle.ms |
600000 (10분) |
유휴 연결 종료 | — |
장애 시나리오와 대응
시나리오 1 — 컨트롤러 과반을 잃었다
- 증상: 토픽 생성·삭제·설정 변경이 모두 타임아웃. 기존 파티션의 읽기·쓰기는 한동안 계속됩니다.
kafka-metadata-quorum.sh --bootstrap-controller ... describe --status로 현재 voter와 리더를 확인합니다.- 브로커도 못 붙는 상황이면
--bootstrap-controller로 컨트롤러에 직접 조회합니다. - 과반을 복구하는 것이 유일한 정답입니다. 내려간 컨트롤러를 다시 올립니다.
- 디스크가 날아간 컨트롤러를 새로 넣을 때는 포맷 전에
describe --replication으로 과반이 커밋된 데이터를 갖고 있는지 확인합니다. 빈 로그 디렉터리로 과반이 기동하면 커밋된 데이터가 빠진 리더가 뽑힐 수 있습니다.
시나리오 2 — 컨트롤러를 3대에서 5대로 늘려야 한다
이 절차가 list order 유형으로 자주 나옵니다. 순서를 바꾸면 쿼럼이 위험해집니다.
kafka-features.sh describe로 동적 쿼럼(kraft.version1 이상)인지 확인합니다. 정적이면 먼저 업그레이드해야 합니다.- 새 컨트롤러 노드를
--no-initial-controllers로 포맷합니다. - 새 컨트롤러를 기동합니다. 이 시점에는 아직 voter가 아니라 observer입니다.
describe --replication으로 새 노드가 리더를 따라잡았는지 확인합니다.- 따라잡은 뒤
add-controller로 쿼럼에 편입합니다. - 한 대씩 반복합니다. 여러 대를 동시에 넣으면 과반 계산이 흔들립니다.
제거는 순서가 반대입니다. remove-controller를 먼저 실행하고 그 다음에 노드를 내립니다.
노드를 먼저 내리면 쿼럼은 그 노드를 여전히 voter로 세면서 과반 계산에 포함합니다.
시나리오 3 — AZ 한 곳이 죽자 일부 토픽이 쓰기 불가가 되었다
- 랙 인식이 켜져 있었는지 확인합니다.
broker.rack이 없으면 배치가 랙을 고려하지 않았습니다. broker.rack이 있어도 랙 인식은 배치 시점에만 적용됩니다. 나중에broker.rack을 붙였다면 기존 파티션 배치는 바뀌지 않습니다.kafka-topics.sh --describe로 문제 토픽의 레플리카가 어느 브로커에 있는지 확인합니다.- 랙을 고려한 배치로 되돌리려면 파티션 재할당을 명시적으로 실행해야 합니다.
- 랙마다 브로커 수가 다르면 배치가 불균등해지므로, 그 기회에 랙별 브로커 수를 맞춥니다.
시나리오 4 — MM2 복제가 계속 뒤처진다
tasks.max가 기본값 1인지 확인합니다. 공식 권고는 최소 2, 가능하면 더 크게입니다.- 복제 토픽의 파티션 수를 확인합니다. 태스크는 토픽-파티션 단위로 나뉩니다.
- WAN이라면
socket.send.buffer.bytes·socket.receive.buffer.bytes를 대역폭·지연 곱에 맞춰 올립니다. - 대상 클러스터 쪽 프로듀서 압축(
{target}.producer.compression.type)으로 전송량을 줄입니다. - 새로 만든 토픽이 늦게 복제된다면
refresh.topics.interval.seconds(기본 600)를 확인합니다. 탐지 주기 문제입니다.
자주 나오는 함정
관련 케이스 스터디
- 케이스 6 · RF=3인데 브로커 1대 죽자 유실됐다 — 배치와
min.insync.replicas - 케이스 3 · 브로커 장애 후 메시지가 유실됐다 — 복제 배치와 확인 조건
KRaft 구조의 배경은 3장 KRaft와 클러스터 메타데이터, 업그레이드 경로와 레거시 대응은 부록, MM2 실습은 예제 11에 있습니다. 구성 변경 절차는 Cluster Configuration과 이어집니다.
미니 퀴즈
쿼럼 사이징 계산과 절차 배열이 중심입니다.
공식 문서 출처
- KRaft —
process.roles, 컨트롤러 3 또는 5,2N+1, combined 모드 권장 사항, 정적·동적 쿼럼, 컨트롤러 추가·제거, 메모리·디스크 5GB - Hardware and OS — 메모리 산정, 디스크·파일시스템, XFS·
noatime, 파일 디스크립터,vm.max_map_count, flush 정책, 컨트롤러 디스크 교체 절차 - Basic Kafka Operations —
broker.rack과min(랙 수, RF), 랙별 브로커 수 권고, 브로커 제거(cordon · unregister) - Datacenters — DC별 클러스터 + 미러링 권장, 단일 클러스터 확장 비권장, 소켓 버퍼 튜닝
- Geo-Replication (MirrorMaker 2) — 복제 흐름 표기,
DefaultReplicationPolicy,tasks.max권고, exactly-once 3.5.0 - MirrorMaker Configs —
replication.factor,sync.group.offsets.enabled,refresh.topics.interval.seconds기본값 - Java Version — Java 17 · 21 · 25