Blog
kafkaperformanceoperating-systemjvmtuningcapacity-planning

[Kafka 성능 ③] OS·하드웨어와 종합 튜닝 프로파일 — 처리량 vs 지연 vs 내구성

Kafka가 빠른 이유(순차 I/O·페이지 캐시·zero-copy)부터 OS·디스크·JVM/GC·네트워크 튜닝, 그리고 처리량·저지연·내구성 세 가지 종합 튜닝 프로파일을 구체적인 설정 키로 정리한 Kafka 성능 시리즈 3부 완결편입니다.

Data Dynamics2026年6月22日32 min read
This post is not yet translated. The original Korean version is shown below.

Kafka를 처음 운영하는 사람들이 가장 자주 던지는 질문은 "왜 이렇게 빠른가요?"입니다. 디스크에 쓰는 메시징 시스템이 인메모리 캐시 못지않은 처리량을 내니, 마치 마법처럼 보입니다. 하지만 마법은 없습니다. Kafka의 속도는 운영체제(OS)의 기능을 거스르지 않고 그대로 빌려 쓰는 설계에서 나옵니다. 그래서 Kafka 튜닝의 절반은 브로커 설정이 아니라 OS·디스크·JVM 같은 "Kafka 바깥"에 있습니다. 시리즈 1부에서 벤치마킹 방법을, 2부에서 브로커 내부 스레드·플러시 동작을 다뤘다면, 이번 완결편은 그 모든 것을 OS·하드웨어 위에 올려두고 처리량·저지연·내구성이라는 세 가지 목표로 종합 튜닝 프로파일을 묶어냅니다.

이 글에서 배우는 것

  • Kafka가 빠른 진짜 이유: 순차 디스크 I/O, OS 페이지 캐시, zero-copy(sendfile)
  • 힙을 작게 두고 나머지 RAM을 페이지 캐시에 양보해야 하는 이유
  • vm.swappiness, 파일시스템(XFS), noatime, ulimit, TCP 버퍼 같은 OS 레벨 설정
  • JBOD vs RAID 디스크 배치의 트레이드오프
  • G1GC 기반 GC 튜닝과 긴 정지(pause)가 부르는 연쇄 장애
  • 처리량·저지연·내구성 세 가지 종합 튜닝 프로파일(프로듀서+컨슈머+브로커)

1. Kafka는 왜 빠른가 — 그리고 그것이 튜닝에 주는 함의

Kafka의 성능을 이해하려면 먼저 "디스크는 느리다"라는 통념을 분해해야 합니다. 디스크가 느린 것은 **랜덤 접근(seek)**일 때이고, **순차 접근(sequential)**일 때 현대의 디스크와 OS는 놀라운 처리량을 냅니다. Kafka는 이 사실을 설계의 뿌리로 삼습니다.

순차 디스크 I/O

Kafka의 로그는 추가 전용(append-only) 세그먼트 파일입니다. 메시지는 파티션 로그의 끝에 순서대로 덧붙여지고, 컨슈머는 오프셋 순서대로 읽어 나갑니다. 데이터베이스가 인덱스를 갱신하며 디스크 곳곳을 헤집는 것과 달리, Kafka는 디스크 헤드를 한 방향으로만 밀고 갑니다. 그 결과 HDD에서도 수백 MB/s, SSD/NVMe에서는 그 이상을 순차 쓰기로 뽑아냅니다.

OS 페이지 캐시

Kafka는 자체적으로 데이터를 캐시하지 않습니다. 대신 **OS 페이지 캐시(page cache)**에 전적으로 의존합니다. 브로커가 메시지를 쓰면 OS는 그것을 페이지 캐시에 담았다가 비동기로 디스크에 내려쓰고, 컨슈머가 최근 메시지를 읽으면 디스크가 아니라 페이지 캐시에서 곧장 서빙됩니다. 대부분의 컨슈머가 로그의 끝부분(tail)을 따라 읽으므로, 실전에서는 읽기의 상당 부분이 디스크를 건드리지 않고 RAM에서 처리됩니다.

이 구조의 함의는 결정적입니다. 페이지 캐시는 "공짜 캐시"가 아니라 RAM을 두고 JVM 힙과 경쟁하는 자원이라는 점입니다.

Zero-copy (sendfile)

전통적인 방식으로 파일을 네트워크로 보내면, 데이터는 디스크 → 커널 버퍼 → 애플리케이션 버퍼 → 소켓 버퍼 → NIC로 네 번 복사되고 그때마다 커널/유저 모드 전환이 일어납니다. Kafka는 컨슈머에게 데이터를 보낼 때 sendfile(zero-copy)을 사용해, 페이지 캐시의 데이터를 애플리케이션 메모리를 거치지 않고 곧장 소켓으로 흘려보냅니다.

[전통적 복사 경로]
디스크 → 페이지 캐시 → 앱 버퍼 → 소켓 버퍼 → NIC   (복사 4회, 컨텍스트 스위치 다수)
 
[zero-copy: sendfile]
디스크 → 페이지 캐시 ──────────────→ NIC          (앱 메모리 우회)

zero-copy는 데이터가 페이지 캐시에 있을 때 가장 효과적입니다. 즉, 페이지 캐시가 잘 더워져 있어야(warm) Kafka가 가장 빠릅니다.

함의: 힙은 작게, RAM은 페이지 캐시에

세 가지 메커니즘을 종합하면 하나의 실전 원칙이 나옵니다. JVM 힙을 적당히(예: 5~6 GB) 유지하고, 나머지 RAM은 OS 페이지 캐시에 양보하라.

잘못된 직관실제 동작
"힙을 크게 주면 빨라진다"힙이 커지면 페이지 캐시가 줄어 디스크 적중률이 떨어지고, GC 정지도 길어진다
"Kafka가 데이터를 캐시한다"Kafka는 OS 페이지 캐시에 의존한다. 힙은 메시지 본문 캐시가 아니다
"RAM이 남으면 힙에 줘야 한다"남는 RAM은 OS가 자동으로 페이지 캐시로 쓴다. 그게 더 빠르다

32 GB 메모리의 브로커라면 힙은 5~6 GB 정도면 충분하고, 나머지 ~26 GB는 페이지 캐시로 쓰이게 둡니다. 2부에서 살펴본 브로커의 플러시 동작(log.flush.interval.messages 등을 함부로 강제하지 말고 OS의 더티 페이지 라이트백에 맡기는 편이 낫다는 논의)도 결국 같은 철학입니다. 페이지 캐시를 신뢰하라는 것.


2. OS 레벨 설정

브로커 설정을 아무리 만져도, 그 아래 OS가 페이지 캐시를 스왑 아웃하거나 파일 디스크립터가 동나면 모든 게 무너집니다. 프로덕션 브로커라면 아래 항목은 기본값으로 두지 말고 명시적으로 설정하세요.

vm.swappiness — 스왑을 거의 끄기

vm.swappiness는 커널이 얼마나 적극적으로 메모리를 스왑으로 내보낼지를 결정합니다. 기본값(보통 60)에서는 페이지 캐시를 확보하려고 프로세스 메모리를 스왑으로 밀어내는데, 이때 JVM 힙 페이지가 스왑되면 GC 한 번에 디스크에서 스왑 인을 하느라 초 단위 정지가 발생할 수 있습니다. 반대로 페이지 캐시를 스왑 아웃하면 Kafka의 강점이 사라집니다.

# 스왑을 완전히 끄지는 않되, 최후의 수단으로만 사용하도록
sudo sysctl -w vm.swappiness=1
echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf

1은 "정말 OOM 직전이 아니면 스왑하지 말라"는 의미입니다. 0은 일부 커널에서 OOM Killer를 더 공격적으로 부르므로 1이 무난합니다.

파일시스템 선택과 마운트 옵션

항목권장이유
파일시스템XFS대용량 순차 I/O와 병렬 쓰기에 강하고, Kafka 운영 사례가 풍부
마운트 옵션noatime읽을 때마다 접근 시각 메타데이터를 갱신하지 않아 불필요한 쓰기 제거
마운트 옵션nodiratime디렉터리 접근 시각 갱신도 생략
# /etc/fstab 예시 — Kafka 로그 디렉터리 마운트
/dev/nvme1n1  /data/kafka-logs  xfs  defaults,noatime,nodiratime  0  0

ext4도 동작하지만, Confluent를 비롯한 다수의 운영 가이드는 대규모 환경에서 XFS를 권장합니다. 파일시스템 레벨에서 fsync를 강제하는 옵션(data=ordered 등)은 내구성과 성능의 트레이드오프가 있으니, 내구성은 가급적 Kafka 복제(RF)로 확보하는 편이 좋습니다.

파일 디스크립터 ulimit

브로커는 파티션마다 여러 개의 세그먼트 파일과 인덱스 파일을 동시에 열어 둡니다. 토픽·파티션·세그먼트가 많아지면 열린 파일 수가 수만~수십만 개에 이르기도 합니다. 기본 ulimit -n(흔히 1024)은 금세 한계에 부딪혀 Too many open files 오류로 브로커가 죽습니다.

# /etc/security/limits.conf
kafka  soft  nofile  100000
kafka  hard  nofile  100000
# 현재 프로세스의 열린 파일 수 확인
ls /proc/$(pgrep -f kafka.Kafka)/fd | wc -l

소켓 연결도 파일 디스크립터를 소비하므로, 클라이언트 수가 많은 클러스터일수록 넉넉히 잡아야 합니다.

기본 TCP/네트워크 튜닝

고대역폭(10 GbE 이상) 링크나 지연이 있는 구간(다른 AZ·리전 간 복제)에서는 TCP 소켓 버퍼가 병목이 됩니다. 대역폭-지연 곱(BDP)을 채우려면 버퍼를 키워야 합니다.

# /etc/sysctl.conf — 고대역폭 링크용 TCP 버퍼 상향
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.core.somaxconn=4096

브로커 쪽에서는 socket.send.buffer.bytes·socket.receive.buffer.bytes-1(OS 기본 사용)로 두거나, 장거리 복제 구간에 맞춰 명시적으로 키울 수 있습니다.


3. 디스크 배치 — JBOD vs RAID

Kafka는 log.dirs에 **여러 디렉터리(여러 디스크)**를 지정할 수 있습니다. 파티션은 이 디렉터리들에 분산 배치됩니다. 디스크를 어떻게 묶느냐가 처리량과 내구성을 동시에 좌우합니다.

JBOD (Just a Bunch Of Disks)

각 디스크를 독립적으로 log.dirs에 나열하는 방식입니다.

# server.properties — JBOD: 독립 디스크 4개
log.dirs=/data/disk1/kafka,/data/disk2/kafka,/data/disk3/kafka,/data/disk4/kafka
  • 장점: 디스크 4개의 I/O 대역폭을 모두 합산해 쓸 수 있어 총 처리량이 가장 높습니다. RAID 패리티 오버헤드도 없습니다.
  • 단점: 디스크 하나가 고장 나면 그 디스크에 있던 파티션들이 오프라인이 됩니다. 다행히 RF≥2이면 다른 브로커의 복제본이 리더를 넘겨받아 가용성은 유지되지만, 해당 브로커는 디스크 교체와 재복제가 필요합니다.

RAID

방식특성Kafka 적합성
RAID 0스트라이핑, 내결함성 없음비권장 — 디스크 1개 고장 = 전체 손실
RAID 10스트라이핑+미러링권장 — 디스크 고장에도 브로커가 계속 동작, 대신 용량 50% 손실
RAID 5/6패리티쓰기 패리티 계산 오버헤드로 쓰기 집약적 워크로드에 불리

RAID 10은 단일 디스크 고장이 브로커 전체를 멈추지 않게 해 주지만, 용량의 절반을 미러링에 쓰는 비용을 치릅니다.

어느 쪽을 고를까

JBOD   →  최대 처리량, 비용 효율. 디스크 고장 시 일부 파티션 오프라인.
          RF≥3 + min.insync.replicas=2 로 가용성을 복제 레이어에서 확보.
 
RAID10 →  브로커 단위 내결함성. 디스크 1개 고장에도 무중단.
          용량 50% 손실을 감수. 복제 + RAID 이중 안전.

핵심은 내구성을 디스크에서 확보할지, Kafka 복제(RF)에서 확보할지의 선택입니다. 시리즈 ⑦ "디스크 풀(disk full) 장애 대응"에서 다뤘듯, 디스크가 가득 차면 해당 로그 디렉터리의 모든 파티션이 막히고 브로커가 위험해집니다. JBOD는 디스크별로 용량을 독립 관리해야 하므로, 디스크별 사용량 모니터링과 log.retention.bytes/리텐션 정책이 RAID보다 더 중요합니다.


4. JVM / GC 튜닝

브로커는 JVM 위에서 돕니다. GC가 길게 멈추면 그 브로커는 그 순간 ZooKeeper/KRaft 컨트롤러와의 하트비트, 팔로워의 fetch 응답, 컨슈머 그룹 멤버십까지 한꺼번에 늦어집니다. GC 정지는 단순한 지연이 아니라 연쇄 장애의 방아쇠입니다.

G1GC 기본값을 쓰되, 목표는 "짧고 예측 가능한 정지"

최신 Kafka/JDK 조합의 권장 컬렉터는 G1GC입니다. 거대한 단일 정지 대신, 작은 단위로 나눠 짧은 정지를 자주 갖는 방식이라 Kafka처럼 지연에 민감한 워크로드에 잘 맞습니다.

# KAFKA_HEAP_OPTS — 힙은 작게 (페이지 캐시에 RAM 양보)
export KAFKA_HEAP_OPTS="-Xms6g -Xmx6g"
 
# KAFKA_JVM_PERFORMANCE_OPTS — G1GC, 목표 정지 시간 20ms
export KAFKA_JVM_PERFORMANCE_OPTS="\
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=20 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:+ExplicitGCInvokesConcurrent \
-XX:G1HeapRegionSize=16M"

-Xms-Xmx를 같게 두어 힙 리사이징으로 인한 정지를 없애고, MaxGCPauseMillis로 G1이 정지 목표를 향해 자동 조율하게 합니다.

긴 GC 정지가 부르는 연쇄 장애

긴 Full GC 정지 (예: 수 초)

   ├─ 컨슈머 하트비트 누락 → 세션 타임아웃 → 리밸런스 (시리즈 ⑥ 리밸런스 폭풍)
   ├─ 팔로워 fetch 지연 → replica.lag.time.max.ms 초과 → ISR 축소 (시리즈 ⑨ ISR shrink)
   └─ acks=all 프로듀서가 ISR 부족으로 NotEnoughReplicas 오류

즉, 힙을 과도하게 키우는 것은 직관과 반대로 위험합니다. 힙이 크면 한 번의 GC가 훑어야 할 객체가 많아져 정지가 길어지고, 위 연쇄가 터질 확률이 높아집니다. "RAM이 많으니 힙도 키우자"는 유혹을 이겨내고, 힙은 작게, GC 로그로 정지 분포를 모니터링하는 편이 안전합니다.

# GC 로그 활성화 (정지 시간 분포 관찰)
export KAFKA_GC_LOG_OPTS="-Xlog:gc*:file=/var/log/kafka/gc.log:time,uptime:filecount=10,filesize=100M"

5. 네트워크와 배치(placement)

NIC 대역폭이라는 진짜 천장

벤치마크에서 처리량이 어느 선에서 더 이상 안 오른다면, 범인은 종종 디스크가 아니라 NIC 대역폭입니다. 복제까지 고려하면 네트워크 부하는 생각보다 큽니다.

예) RF=3, 프로듀서 인입 300 MB/s 일 때 한 브로커의 네트워크
  - 인입(프로듀서 → 리더):              ~100 MB/s
  - 복제 송신(리더 → 팔로워 2):          ~200 MB/s
  - 컨슈머 송출(팔로워/리더 → 컨슈머):    가변 (컨슈머 그룹 수에 비례)
  → 1 GbE(~125 MB/s)면 즉시 한계. 10 GbE 이상을 권장.

복제는 프로듀서 인입의 (RF-1)배만큼 네트워크를 더 먹고, 여기에 컨슈머 팬아웃까지 겹칩니다. 컨슈머 그룹이 많을수록 송출 대역폭이 곱으로 늘어난다는 점을 용량 산정에서 빠뜨리면 안 됩니다.

교차 AZ/리전 배치 비용

내구성을 위해 브로커를 여러 가용영역(AZ)에 분산하면, 복제 트래픽이 AZ 경계를 넘나들며 데이터 전송 비용과 추가 지연을 만듭니다. 리전 간이면 비용·지연이 더 큽니다.

  • AZ 간: 지연은 보통 수 ms로 작지만, 클라우드에서는 AZ 간 전송에 과금됩니다. acks=all이면 팔로워 ACK 왕복이 지연에 직접 반영됩니다.
  • 리전 간 동기 복제: 권장하지 않습니다. 수십 ms 이상의 RTT가 모든 쓰기에 더해져 처리량이 급락합니다. 리전 간은 MirrorMaker 2 등 비동기 복제로 가는 게 정석이며, 이는 별도 DR 시리즈(① DR 설계 RPO/RTO, ⑮ MirrorMaker 2 구성)에서 다룹니다.

broker.rack 설정과 rack-aware 복제본 배치를 쓰면, 같은 파티션의 복제본이 서로 다른 AZ에 놓이도록 해 AZ 장애에도 데이터를 보존할 수 있습니다.


6. 결실 — 세 가지 종합 튜닝 프로파일

여기까지의 모든 논의(페이지 캐시, OS, 디스크, JVM, 네트워크, 그리고 1·2부의 프로듀서/브로커 설정)를 하나로 묶을 차례입니다. 실전에서 튜닝은 결국 **"우리 워크로드는 처리량·저지연·내구성 중 무엇을 가장 원하는가?"**라는 질문으로 수렴합니다. 세 가지 대표 프로파일을 구체적인 설정 키로 제시합니다.

아래 표의 값은 **출발점(starting point)**입니다. 절대 정답이 아니라, 1부에서 다룬 벤치마킹 방법(kafka-producer-perf-test·kafka-consumer-perf-test로 한 번에 한 변수만 바꿔 측정)으로 여러분의 환경에서 검증해야 하는 기준선입니다.

6.1 처리량 최적화 프로파일

대량 로그 수집, 분석 파이프라인처럼 "초당 최대한 많은 메시지"가 목표이고 수십~수백 ms의 지연은 허용되는 경우입니다.

계층설정의도
Producerbatch.size256000~512000큰 배치로 요청당 메시지 수 극대화
Producerlinger.ms50~100배치가 더 차길 기다림
Producercompression.typezstd 또는 lz4네트워크·디스크 전송량 절감
Producerbuffer.memory134217728 (128MB)큰 배치를 담을 충분한 버퍼
Produceracks1처리량 우선(내구성 손해는 감수, 아래 주의)
Consumerfetch.min.bytes1048576 (1MB)한 번에 크게 가져와 요청 수 감소
Consumerfetch.max.wait.ms500min.bytes가 찰 때까지 대기 허용
Consumermax.partition.fetch.bytes5242880 (5MB)파티션당 큰 페치 허용
Brokernum.io.threads디스크 수 × 2~3디스크 I/O 병렬도 확대
Brokernum.network.threads코어 수에 비례네트워크 처리 확장
Brokernum.replica.fetchers4~8팔로워 복제 병렬도 향상

주의(내구성 비용): acks=1은 리더만 받으면 성공으로 처리하므로, 리더가 복제 전에 죽으면 메시지가 유실될 수 있습니다(시리즈 ④ acks/min.insync.replicas, ③ 메시지 유실 시나리오 참고). 유실을 절대 허용 못 하면 acks=all로 두고 배치·압축으로 처리량을 만회하세요.

6.2 저지연 최적화 프로파일

결제 알림, 실시간 트레이딩 신호처럼 "한 건이라도 빨리"가 목표인 경우입니다. 처리량은 다소 양보합니다.

계층설정의도
Producerlinger.ms0모이길 기다리지 않고 즉시 전송
Producerbatch.size16384 (기본) 또는 작게배치 대기 최소화
Producercompression.typelz4 또는 none압축 CPU 지연 최소화(lz4는 빠름)
Producermax.in.flight.requests.per.connection5 (+ enable.idempotence=true)멱등성으로 순서 보장하며 파이프라이닝 유지
Produceracks1 (지연 우선 시) / all (내구성 병행)트레이드오프 선택
Consumerfetch.min.bytes1데이터가 생기면 즉시 반환
Consumerfetch.max.wait.ms10~50페치 대기를 최소화
Consumermax.poll.records100~500poll당 적은 레코드로 처리 지연 단축
Brokerreplica.fetch.wait.max.ms작게복제 지연 단축
Topicpartition충분히 분산컨슈머 병렬도로 큐잉 지연 완화

핵심은 "기다림"을 모두 제거하는 것입니다. linger.ms=0, fetch.min.bytes=1, 작은 fetch.max.wait.ms로 어디에서도 배치가 차길 기다리지 않게 합니다. 단, enable.idempotence=true를 켜야 max.in.flight=5에서도 재시도 시 순서가 보장됩니다(시리즈 ⑩ 순서 보장).

6.3 내구성 최적화 프로파일

금융 원장, 감사 로그처럼 "절대 유실 불가"가 최우선인 경우입니다. 처리량·지연을 일부 양보하더라도 데이터 안전을 택합니다.

계층설정의도
Produceracksall모든 ISR이 받아야 성공 처리
Producerenable.idempotencetrue재시도 시 중복·순서 깨짐 방지
ProducerretriesInteger.MAX_VALUE일시 장애에 끈질기게 재시도
Producermax.in.flight.requests.per.connection5 (멱등성과 함께)순서 보장 유지
Producerdelivery.timeout.ms충분히 크게재시도가 끝까지 시도되도록
Topicreplication.factor (RF)3브로커 2대 동시 고장에도 데이터 보존
Topicmin.insync.replicas2ISR 2개 미만이면 쓰기 거부(반쪽 쓰기 방지)
Broker/Topicunclean.leader.election.enablefalse뒤처진 복제본이 리더가 되어 유실되는 일 차단

이 조합은 시리즈 ③(메시지 유실 시나리오)·④(acks·min.insync.replicas)·⑤(unclean leader election)에서 다룬 안전 장치를 한 자리에 모은 것입니다. 특히 min.insync.replicas=2acks=all은 짝으로 동작해야 의미가 있습니다. RF=3·MISR=2이면 브로커 1대가 죽어도 쓰기는 계속되고, 2대가 죽으면 쓰기를 거부해 무방비 유실 대신 명시적 실패를 택합니다.

프로파일 한눈에 비교

목표처리량지연내구성대표 워크로드
처리량 최적화○(acks=all이면 ◎)로그 수집, 배치 분석
저지연 최적화실시간 알림, 트레이딩
내구성 최적화금융 원장, 감사 로그

7. 세 목표와 핵심 다이얼의 매핑

세 프로파일이 결국 어떤 "다이얼"을 어느 방향으로 돌리는지를 한 장으로 정리하면 다음과 같습니다.

Loading diagram…

세 갈래가 모두 같은 공통 토대(OS·하드웨어) 위에 놓인다는 점이 핵심입니다. 어떤 프로파일을 고르든 페이지 캐시·GC·디스크·NIC라는 바닥이 부실하면 윗단 설정은 의도대로 동작하지 않습니다.


8. 시리즈 마무리 — 성능 1~3부 정리

Kafka 성능 미니 시리즈는 "측정 → 한 변수씩 변경 → OS를 존중"이라는 한 줄로 요약됩니다. 세 편을 한눈에 정리합니다.

주제핵심 메시지
① 벤치마킹 방법론kafka-*-perf-test로 처리량·지연 측정측정 없는 튜닝은 추측이다. 한 번에 한 변수만 바꿔라
② 브로커 내부·스레드·플러시I/O·네트워크 스레드, 페이지 캐시 플러시플러시를 강제하지 말고 OS 라이트백을 믿어라
③ OS·하드웨어·종합 프로파일페이지 캐시·zero-copy·GC·디스크·세 프로파일튜닝의 절반은 Kafka 바깥(OS·하드웨어)에 있다

세 편을 관통하는 원칙은 변하지 않습니다.

  • 측정하라(Measure): 직관이 아니라 벤치마크가 진실이다. 프로덕션과 닮은 부하로 검증하라.
  • 한 변수만 바꿔라(Change one variable): batch.sizeacks를 동시에 바꾸면 무엇이 효과였는지 알 수 없다.
  • OS를 존중하라(Respect the OS): 힙은 작게, 페이지 캐시는 넉넉히, GC는 짧게, 디스크·NIC를 천장으로 인식하라.

성능 튜닝은 더 넓은 운영의 일부입니다. 처리량을 쫓다가 acks=1로 내려 유실을 자초하거나(시리즈 ③·④), 긴 GC가 리밸런스 폭풍(⑥)과 ISR 축소(⑨)를 부르거나, 디스크 풀(⑦)로 파티션이 멈추는 일은 모두 "성능"과 "안정성"이 한 몸이라는 증거입니다. 또한 내구성·복제 설계는 DR 시리즈(① DR 설계 RPO/RTO, ⑮ MirrorMaker 2 구성, ⑯ 오프셋 변환, ⑰ Failover/Failback 런북)와 직접 맞닿아 있습니다. 성능·운영·DR을 따로 보지 말고, 하나의 워크로드 요구사항에서 출발해 함께 조율하세요.


마치며

  • Kafka의 속도는 **순차 I/O + OS 페이지 캐시 + zero-copy(sendfile)**라는 OS 기능을 그대로 빌려 쓰는 데서 나옵니다. 그래서 힙은 작게, RAM은 페이지 캐시에 양보하는 것이 첫 번째 원칙입니다.
  • OS 레벨에서 vm.swappiness=1, XFS + noatime, 넉넉한 파일 디스크립터 ulimit, 고대역폭용 TCP 버퍼는 기본값으로 두면 안 되는 항목입니다.
  • 디스크는 **JBOD(최대 처리량)와 RAID10(브로커 단위 내결함성)**의 트레이드오프를 워크로드와 복제 전략에 맞춰 고르세요.
  • GC 정지는 연쇄 장애의 방아쇠입니다. G1GC로 짧고 예측 가능한 정지를 목표하고, 힙을 과도하게 키우지 마세요.
  • 처리량·저지연·내구성 세 프로파일은 출발점입니다. 1부의 벤치마킹 방법으로 여러분 환경에서 반드시 검증하세요.
  • 시리즈를 관통하는 한 줄: 측정하고, 한 변수씩 바꾸고, OS를 존중하라.

참고 자료


— Data Dynamics 엔지니어링 팀