Kafka 완전정복
Kafka를 처음부터 끝까지 실무 중심으로 배우는 기술서입니다.
이 문서는 GitBook 기반으로 구성되어 있으며, 각 장은 실제 Kafka 운영 경험을 기반으로 정리되어 있습니다.
1장. Kafka란 무엇인가
1.1 Kafka는 어떤 서비스인가요?
Kafka는 쉽게 말하면 “데이터 택배 시스템” 입니다.
여러 개의 시스템 사이에서, 어떤 시스템은 데이터를 보내고(생산자),
다른 시스템은 그 데이터를 받아서 처리합니다(소비자).
Kafka는 이 사이에서 데이터를 안전하게, 빠르게, 실시간으로 전달하는 역할을 해요.
예를 들어, 여러분이 영상을 하나 클릭했다고 해봅시다.
그 행동 정보는 “클릭 이벤트“라는 데이터가 되어 Kafka를 통해 전송되고,
분석 시스템이나 로그 저장 시스템이 그걸 받아서 처리할 수 있습니다.
1.2 Kafka는 왜 필요한가요?
문제 상황 예시
과거에는 서비스들이 서로 직접 데이터를 주고받았어요.
하지만 시스템이 많아질수록 데이터 연결이 복잡해지고, 실시간 처리도 어려워졌어요.
Kafka의 해결 방식
Kafka를 가운데에 두면 각 시스템은 Kafka와만 연결하면 됩니다.
그 결과로…
- 시스템 간 연결이 단순해지고
- 데이터 유실 없이 안정적으로 처리되고
- 실시간 반응이 가능해집니다!
1.3 Kafka는 다른 메시지 시스템보다 어떤 점이 좋을까요?
Kafka는 단순한 메시지 큐가 아니라, 대규모 실시간 데이터 처리에 특화된 플랫폼입니다.
Kafka만의 대표적인 장점은 다음과 같습니다:
✅ Kafka의 주요 장점
-
🔄 데이터를 오래 저장할 수 있어요
대부분의 메시지 큐는 메시지를 읽으면 바로 사라지지만, Kafka는 디스크에 오래 보관할 수 있어요.
같은 데이터를 여러 번 읽거나, 늦게 도착한 시스템도 처리할 수 있습니다. -
⚡ 매우 빠릅니다
Kafka는 디스크에 데이터를 쓰면서도 초당 수십만 건의 메시지를 처리할 수 있을 만큼 성능이 뛰어납니다.
내부 구조가 성능에 최적화되어 있어요. -
🧵 여러 소비자가 동시에 읽을 수 있어요
예를 들어, 같은 메시지를 “로그 저장 시스템“과 “분석 시스템“이 동시에 처리할 수 있어요. -
🧩 확장성과 내결함성이 좋아요
Kafka는 여러 대의 서버가 묶여서 하나의 클러스터를 이루기 때문에, 서버 한 대가 꺼져도 시스템이 계속 작동합니다.
수평 확장도 유연하게 가능합니다. -
💡 고가용성(High Availability) 지원
Kafka는 각 데이터(파티션)를 여러 서버에 복제(replication)해 저장함으로써, 하나의 서버가 고장 나도 데이터 손실 없이 운영이 가능합니다.
🌍 Kafka를 사용하는 유명 기업들
Kafka는 수많은 글로벌 기업에서 사용하고 있습니다. 그 예시는 다음과 같습니다:
- LinkedIn: Kafka의 창시자. 실시간 사용자 활동 로그 처리에 활용
- Netflix: 수백 개의 마이크로서비스 사이에서 메시지 전달용으로 사용
- Uber: 위치 데이터, 배차 시스템, 요금 시스템 등 실시간 흐름 관리
- Airbnb: 사용자 행동 분석과 로그 집계 처리에 Kafka 사용
- 카카오: 대규모 실시간 로그 수집 및 분산 처리에 활용
- 쿠팡: 주문, 결제, 배송 상태 이벤트 처리 및 모니터링 시스템 구성
💡 Kafka는 단순한 데이터 전달 도구를 넘어,
“데이터 흐름의 중심 허브” 로 실시간 데이터 기반 시스템을 만드는 데 핵심적인 역할을 합니다.
1.4 Kafka 간단한 예시
sequenceDiagram
participant Client as 웹 서버
participant Kafka as Kafka 브로커
participant Order as 주문 처리 시스템
participant Log as 로그 저장 시스템
Client->>Kafka: "사용자 주문 이벤트" 전송
Kafka-->>Order: 이벤트 전달
Kafka-->>Log: 이벤트 전달
1.5 Kafka를 도입하면 무엇이 단순해지나요?
Kafka가 없던 시절에는 각 서버가 필요한 다른 서버에 직접 데이터를 전송했기 때문에
통신 구조가 점점 복잡해지고 관리하기 어려워졌습니다.
예를 들어, 주문 처리 시스템이 결제 시스템, 알림 시스템, 통계 시스템에 각각 메시지를 보내야 했다면…
graph TD
A[주문 처리 시스템] --> B[결제 시스템]
A --> C[알림 시스템]
A --> D[통계 시스템]
B --> A
C --> A
D --> A
이런 구조는 다음과 같은 문제를 갖습니다:
- 새로운 시스템을 추가할 때마다 기존 시스템 코드를 수정해야 함
- 연결이 많아질수록 복잡도 증가
- 메시지 실패 시 재전송 처리 어려움
Kafka를 도입하면, 주문 처리 시스템은 Kafka에게만 메시지를 보내면 됩니다.
필요한 시스템은 Kafka로부터 알아서 메시지를 구독하면 됩니다.
graph TD
A[주문 처리 시스템] --> K[Kafka]
K --> B[결제 시스템]
K --> C[알림 시스템]
K --> D[통계 시스템]
이런 구조의 장점은:
- 메시지 생산자는 단 하나의 대상(Kafka) 에만 전송하면 됨
- 새로운 소비자 추가가 쉬움 (Kafka에서 구독만 하면 됨)
- 시스템 간 결합도가 낮아짐 → 유지보수 편해짐
- 장애가 나도 메시지는 Kafka에 저장되어, 나중에 다시 처리 가능
💡 Kafka는 시스템 간 통신을 거미줄 구조에서 중앙 집중 허브 방식으로 바꾸어 줍니다.
2장. Kafka 기본 구조 이해하기
이 장에서는 Kafka의 전체 구조를 한눈에 조망해봅니다.
각 구성 요소에 대한 상세 설명은 다음 장들에서 다룰 예정이에요.
2.1 Kafka의 구성 요소
Kafka는 다음과 같은 주요 요소들로 구성돼 있어요:
-
Producer (생산자)
Kafka로 메시지를 보내는 시스템 (예: 주문 서비스) -
Consumer (소비자)
Kafka에서 메시지를 읽고 처리하는 시스템 (예: 주문 처리기) -
Topic (토픽)
메시지를 구분하기 위한 이름 공간 (폴더 개념) -
Partition (파티션)
Topic을 나눈 물리적 단위. 병렬 처리를 위해 사용됨 -
Broker (브로커)
Kafka 서버. 메시지를 저장하고 분배함
2.2 Zookeeper는 뭐고, KRaft는 뭘까?
과거 Kafka는 Zookeeper라는 시스템을 통해 클러스터 상태를 관리했어요.
하지만 설정이 복잡하고 운영 비용도 컸죠.
그래서 Kafka 2.8부터는 KRaft (Kafka Raft Metadata Mode) 라는 구조가 도입됐어요.
✅ KRaft의 특징
- Kafka 자체가 클러스터 메타데이터를 관리 (Zookeeper 필요 없음)
- 장애 복구와 구성 단순화에 유리
- Kafka 3.3 이후로는 기본 모드로 전환되는 추세
2.3 Kafka 구조 예시
Kafka에서 메시지는 다음 흐름으로 전달돼요:
graph TD
P1[주문-서비스 - Producer] --> B1[Kafka-브로커]
B1 --> T1[주문-Topic]
T1 --> Part0[Partition-0]
T1 --> Part1[Partition-1]
Part0 --> C1[주문-처리기-1 - Consumer]
Part1 --> C2[주문-처리기-2 - Consumer]
- 주문 서비스는 Kafka에 메시지를 보냄
- Kafka는 Topic 내 파티션에 메시지를 저장
- 각 Consumer는 한 개의 Partition만 소비함 (같은 Consumer Group으로 동작)
2.4 메시지 흐름 예시
sequenceDiagram
participant App as 주문-서비스
participant Kafka as Kafka-브로커
participant Worker1 as 주문-처리기-1
participant Worker2 as 주문-처리기-2
App->>Kafka: 주문 이벤트 전송
Kafka->>Kafka: 이벤트 저장 (파티셔닝)
Kafka-->>Worker1: Partition-0 메시지 전달
Kafka-->>Worker2: Partition-1 메시지 전달
- 하나의 Topic을 여러 Consumer가 병렬로 처리함
- 메시지를 받은 후 Consumer는 Kafka에 “오프셋 커밋“을 할 수도 있음
✅ 정리
- Kafka는 Producer → Topic/Partition → Consumer 구조로 동작합니다
- 메시지를 효율적이고 병렬적으로 처리할 수 있도록 설계되어 있어요
- 이 장에서는 구조 개요만 다뤘고,
다음 장부터 각 요소를 깊이 있게 살펴볼 거예요!
3장. Kafka 프로듀서
Kafka에서 데이터를 보내는 쪽, 바로 Producer의 역할에 대해 알아봅니다.
메시지를 어디로, 어떻게, 얼마나 안전하게 보낼지 이해하는 것이 핵심입니다.
3.1 Kafka에 메시지를 보내는 역할: Producer
Kafka에서 Producer는 데이터를 Kafka로 전송하는 주체입니다.
예를 들어 쇼핑몰에서 주문이 발생하면, 주문 서비스는 Kafka에 “주문 발생” 이벤트를 보냅니다.
[주문 서비스] → Kafka
이처럼 Kafka에 데이터를 전송하는 모든 시스템은 Producer라고 불립니다.
3.2 Producer는 어디로 데이터를 보낼까? → Topic
Kafka는 데이터를 구분해서 저장할 수 있도록 Topic(토픽) 이라는 개념을 사용해요.
Topic은 메시지의 종류에 따라 분류된 폴더 같은 개념이에요.
예: 주문 →order-topic, 결제 →payment-topic, 로그인 →login-topic
Producer는 Kafka에 메시지를 보낼 때 어떤 Topic에 넣을지 명시해야 합니다.
3.3 Topic은 왜 Partition으로 나뉘나요?
하나의 Topic 안에는 실제로는 여러 개의 Partition(파티션) 으로 나뉘어 있어요.
이유는 단 하나: 성능 향상과 병렬 처리를 위해서입니다.
order-topic
├── Partition 0
├── Partition 1
└── Partition 2
- Kafka는 메시지를 여러 파티션에 분산시켜 저장합니다
- 여러 Consumer가 각각 파티션을 병렬로 읽어서 처리할 수 있어요
3.4 Partition은 어떻게 정해질까?
Producer가 메시지를 보낼 때 Kafka는 다음 중 하나로 Partition을 선택합니다:
🔸 Key가 없는 경우
- Kafka는 파티션을 무작위 또는 라운드로빈 방식으로 선택합니다
- 순서 보장이 필요 없는 데이터에 적합합니다
🔸 Key가 있는 경우 (예: userId, 주문번호 등)
- Kafka는 Key 값을 해시(Hash) 해서 항상 같은 Partition에 메시지를 넣습니다
userId=123 → Partition 0
userId=456 → Partition 2
userId=123 → Partition 0 (다시!)
이렇게 하면 같은 유저의 메시지는 같은 파티션으로 들어가서 순서를 지킬 수 있어요
👀 Key는 누가 지정하나요?
- Key는 Producer가 직접 지정하는 값이에요
- userId, 주문 ID, 장바구니 ID 등 같은 대상에 대한 메시지를 묶고 싶을 때 지정합니다
3.5 메시지를 잘 보내려면? → 설정이 중요해요
Producer는 성능과 신뢰성을 조절할 수 있는 다양한 설정 옵션이 있어요.
그중에서도 가장 중요한 acks부터 살펴볼게요.
📌 acks: 메시지를 성공으로 판단하는 기준
Kafka는 메시지를 보내놓고 끝나는 게 아니라, 브로커가 잘 받았는지 확인할 수 있어요.
그 기준을 정하는 게 acks입니다.
| 설정값 | 의미 | 장단점 |
|---|---|---|
0 | 응답을 안 기다림 | 매우 빠름 / 유실 위험 있음 |
1 | 브로커가 받았다고 하면 OK | 기본값 / 보통 안전 |
all | 확실히 저장됐다고 할 때까지 기다림 | 가장 안전 / 가장 느릴 수 있음 |
예:
- 실시간 클릭 로그 →
acks=1(속도 우선)- 결제, 주문 이벤트 →
acks=all(신뢰성 우선)
💬 Kafka 내부에 “복제 구조”가 있어서
acks=all이 완전히 저장될 때까지 기다려주는 구조인데,
이건 뒤에서 다룰 Kafka 고가용성 구조 챕터에서 자세히 설명할게요.
🔧 기타 설정 요약
| 설정 옵션 | 설명 |
|---|---|
retries | 메시지 전송 실패 시 재시도 횟수 |
linger.ms | 메시지를 얼마나 기다렸다가 한 번에 보낼지 (ms 단위) |
batch.size | 한 번에 묶어서 보낼 수 있는 메시지의 최대 크기 (byte 단위) |
compression.type | 메시지를 압축해서 전송하는 방식 (압축률/속도 트레이드오프) |
📌 처리량을 높이는 설정: linger.ms + batch.size
Kafka는 메시지를 하나씩 보내지 않고, 여러 개를 묶어서 한 번에 보내요.
이걸 제어하는 게 linger.ms와 batch.size예요.
🕒 linger.ms
- Kafka가 최대 몇 밀리초 동안 기다렸다가 메시지를 보낼지 결정
- 기다리는 동안 더 많은 메시지를 모아 전송 가능
- 처리량 ↑ / 지연 시간은 소폭 증가
📦 batch.size
- 한 번에 전송할 수 있는 묶음(batch)의 최대 크기
- 큼 → 처리량 좋음 / 작음 → 자주 전송됨
✅ 함께 사용하면 “많이 모았거나 시간이 지나면 보낸다“는 정책이 됨
🗜️ compression.type: 메시지를 압축하면 뭐가 좋을까?
Kafka에서는 메시지를 압축해서 전송할 수 있어요.
- 네트워크 전송량 줄어듦
- 디스크 저장 공간 절약
- 처리량 증가 (네트워크 병목 감소)
| 압축 방식 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
none | CPU 적음 | 전송량 많음 | 테스트 환경, 저부하 |
gzip | 압축률 최고 | 느림 / CPU 높음 | 네트워크 비용 절감 필요 |
snappy | 빠름 / 적당한 압축 | 표준 | 일반적인 로그 처리 |
lz4 | 매우 빠름 / 압축률 양호 | 고속 처리 요구 | |
zstd | 압축률+속도 최고 | 최신 Kafka 필요 | 최신 시스템 + 큰 데이터 |
3.6 메시지 전송 흐름 요약
1. Producer가 Kafka에 연결
2. 보낼 Topic을 선택
3. (필요하면 Key 지정)
4. Kafka가 Partition을 선택하고 저장
3.7 시각화로 정리
sequenceDiagram
participant App as 주문-서비스
participant Kafka as Kafka-브로커
App->>Kafka: 주문 이벤트 메시지 전송 (order-topic, userId=123)
Kafka->>Kafka: Partition 선택 및 저장
✅ 정리
- Producer는 Kafka에 메시지를 전송하는 주체입니다
- Topic은 메시지를 구분하는 주소, Partition은 병렬 처리를 위한 단위입니다
- Key를 지정하면 메시지를 고정된 Partition으로 보내 순서를 유지할 수 있어요
acks설정은 신뢰성과 속도 균형을 결정하는 중요한 포인트예요linger.ms,batch.size,compression.type등 설정을 통해 성능 최적화가 가능합니다
다음 장에서는 Kafka에서 메시지를 꺼내서 처리하는 Consumer에 대해 배워볼 거예요.
4장. Kafka 컨슈머
Kafka에서 데이터를 받아서 사용하는 쪽, 즉 Consumer의 역할을 설명합니다.
메시지를 어떻게 구독하고, 어떤 방식으로 처리하며, 여러 Consumer가 있을 때 어떤 구조로 동작하는지 알아봅니다.
4.1 Consumer란 무엇인가요?
Consumer는 Kafka에서 메시지를 꺼내서 처리하는 주체입니다.
Producer가 보낸 데이터를 실제로 사용하는 쪽이에요.
Kafka → [결제 처리 시스템]
→ [이메일 발송 시스템]
→ [DB 저장기]
Kafka는 메시지를 보관하는 중간 통신소,
Consumer는 이 메시지를 꺼내서 처리하는 작업자라고 보면 돼요.
4.2 Kafka는 왜 메시지를 push하지 않고, pull 방식일까?
Kafka는 메시지를 직접 밀어주는 push 방식이 아니라,
Consumer가 스스로 가져가는 pull 방식을 사용해요.
💡 즉, Kafka는 “여기 메시지를 쌓아둘게. 필요하면 네가 가져가!”라는 식이에요.
✅ pull 방식의 장점
| 항목 | 설명 |
|---|---|
| 소비 속도 조절 가능 | Consumer가 자기 속도에 맞춰 가져감 (과부하 방지) |
| 장애 복구 쉬움 | Consumer가 꺼졌다 켜져도 이어서 읽을 수 있음 |
| 유연한 재처리 | 필요한 경우 동일 메시지를 다시 소비 가능 (로그 기반 처리에 유리) |
4.3 Consumer Group이란?
Kafka는 여러 Consumer가 있을 때,
“같은 작업을 함께 처리하는 묶음” 으로 Consumer Group이라는 개념을 사용해요.
단순히 Consumer 여러 개가 돌아가는 게 아니라,
Kafka는 Consumer Group을 기준으로 Partition을 나누어 할당해요.
order-topic
├── Partition 0 → Consumer A
├── Partition 1 → Consumer B
위 구조는 “Consumer Group A” 안에서의 분산 처리 방식이에요.
🧩 왜 Group으로 묶을까요?
| 이유 | 설명 |
|---|---|
| 병렬 처리 | 여러 Consumer가 하나의 Topic을 나눠서 동시에 처리할 수 있어요 |
| 작업 분담 | Kafka가 자동으로 Partition을 나눠줘서 코드 수정 없이 확장 가능 |
| 장애 복구 | Offset 정보를 Kafka가 Group 단위로 저장해서 이어서 처리 가능 |
📌 초보자 궁금증! Consumer는 무조건 Group에 속해야 하나요?
Kafka에서는 Consumer가 Group에 속하지 않아도 동작은 가능하지만,
실무에서는 대부분 Group을 설정해서 사용해요.
| 조건 | 설명 |
|---|---|
| Group ID 있음 | Offset 저장, 자동 리밸런싱, 병렬 처리 가능 ✅ |
| Group ID 없음 | 매번 처음부터 읽고, 병렬성 없음 ❌ |
✅ 실무에서는 안정적인 메시지 처리와 재시작을 위해 Group ID 설정은 필수예요!
🔄 Group은 여러 Topic도 처리할 수 있어요
하나의 Consumer Group은 여러 Topic을 동시에 구독할 수 있어요.
Consumer Group A
├── 구독 Topic: order-topic, payment-topic
├── Consumer 1: order-topic Partition 0, payment-topic Partition 0
├── Consumer 2: order-topic Partition 1, payment-topic Partition 1
예: “주문/결제 통합 시스템”에서는 두 Topic을 함께 구독해서 하나의 Group으로 처리 가능해요.
4.4 한 Group 내부의 메시지 분배 구조
Kafka는 Topic의 Partition을 기준으로, Group 내부의 Consumer에게 메시지를 나눠줘요.
- 하나의 Partition은 하나의 Consumer만 읽을 수 있음 (같은 Group 안에서)
- 하나의 Consumer는 여러 Partition을 담당할 수 있음
⚖️ Consumer 수 vs Partition 수 – 몇 개가 맞을까?
| 상태 | 설명 | 결과 |
|---|---|---|
| Consumer 수 = Partition 수 | ✅ 가장 이상적. 병렬 처리 최대화 | |
| Consumer 수 < Partition 수 | ✅ 문제 없음. 한 Consumer가 여러 Partition 처리 | |
| Consumer 수 > Partition 수 | ❌ 남는 Consumer는 대기 (idle) 상태가 됨 |
Kafka는 Partition 수보다 많은 Consumer가 있어도 하나의 Partition을 두 Consumer가 동시에 소비할 수 없기 때문이에요.
4.5 메시지를 읽은 뒤에는? → Offset 개념
Kafka는 메시지를 읽을 때마다
“어디까지 읽었는지” 를 기억하는 구조를 제공합니다.
이걸 Offset(오프셋) 이라고 해요.
- Kafka는 각 Group이 Topic/Partition 별로 어디까지 읽었는지 따로 저장해줘요
- 덕분에 Consumer가 꺼졌다 켜져도 중단된 위치부터 이어서 소비할 수 있어요
4.6 자동 커밋 vs 수동 커밋
Offset을 Kafka에 저장하는 방법에는 두 가지가 있어요:
| 방식 | 설명 | 장단점 |
|---|---|---|
| 자동 커밋 | Kafka가 일정 시간마다 자동 저장 | 간편하지만 메시지가 처리되기 전에 커밋될 수도 있음 |
| 수동 커밋 | 개발자가 직접 “이제 처리 완료”라고 표시 | 정밀 제어 가능, 실무에서 선호되는 방식 |
🧨 커밋을 하지 않으면 생기는 문제들
Offset 커밋을 안 하면 Kafka는 어디까지 메시지를 읽었는지 모르게 돼요.
그 결과 다음과 같은 문제가 발생할 수 있어요:
🔁 메시지 재처리 (중복 처리)
- Consumer가 처음부터 다시 읽기 시작하면
이미 처리한 메시지를 또 처리해서 중복 결제 등 문제가 생길 수 있어요
❌ 메시지 누락
- 메시지를 처리하긴 했지만 커밋 전에 Consumer가 죽으면,
Kafka는 “읽지 않은 것”으로 간주하지 않아 누락될 가능성이 있어요
📉 장애 대응 어려움
- Kafka는 Offset을 기준으로 복구를 수행하기 때문에
커밋이 없다면 어디부터 다시 읽어야 할지 알 수 없어요
💬 Offset 커밋은 단순한 저장이 아니라, 정확한 데이터 흐름을 보장하는 핵심 요소예요.
4.7 전체 구조 요약
sequenceDiagram
participant Kafka as Kafka
participant C1 as 주문-처리기-1
participant C2 as 주문-처리기-2
Kafka-->>C1: Partition 0 메시지 전달
Kafka-->>C2: Partition 1 메시지 전달
C1->>Kafka: Offset 커밋
C2->>Kafka: Offset 커밋
✅ 정리
- Kafka는 pull 방식으로 메시지를 소비합니다 (Consumer가 직접 가져감)
- 여러 Consumer는 Group으로 묶어 하나의 작업을 나눠 병렬로 처리할 수 있어요
- 하나의 Group이 여러 Topic을 동시에 구독하는 것도 가능해요
- Kafka는 Offset을 통해 어디까지 읽었는지를 기억해주며, 복구와 재처리에 유리해요
- Consumer 수와 Partition 수의 조합에 따라 병렬성과 효율이 달라져요
- Offset 커밋을 하지 않으면 재처리, 누락, 복구 실패 등의 문제가 발생할 수 있어요
- 자동/수동 커밋을 통해 메시지 처리 성공 기준을 세밀하게 제어할 수 있어요
5장. Kafka 클러스터와 브로커
Kafka는 단일 서버가 아니라, 여러 대의 서버가 함께 구성된 클러스터 구조로 운영됩니다.
이 장에서는 Kafka 클러스터의 구조와, 핵심 구성요소인 브로커(Broker), 그리고 리더/팔로워 구조에 대해 다룹니다.
5.1 Kafka 클러스터란?
Kafka는 대량의 데이터를 안정적으로 처리하기 위해
여러 대의 서버를 하나로 묶은 클러스터 구조로 동작해요.
각 서버는 Kafka의 브로커(Broker) 역할을 합니다.
graph TD
subgraph Kafka Cluster
B1(Broker 1)
B2(Broker 2)
B3(Broker 3)
end
💡 Kafka는 1대만으로도 동작은 가능하지만, 장애 대응, 복제, 확장성을 위해 최소 3대 이상이 권장돼요.
5.2 브로커란?
Kafka의 브로커(Broker) 는 Kafka 클러스터 안에서
메시지를 받고, 저장하고, 보내주는 역할을 하는 서버입니다.
| 역할 | 설명 |
|---|---|
| 메시지 수신 | Producer가 보낸 메시지를 받음 |
| 메시지 저장 | 디스크에 Topic/Partition별로 기록 |
| 메시지 전달 | Consumer가 요청하면 메시지를 전달 |
| 클러스터 참여 | 리더 선출, 복제 등 내부 클러스터 조율 |
5.3 Partition은 브로커에 어떻게 저장될까?
Kafka는 하나의 Topic을 여러 Partition으로 쪼개고,
이 Partition을 브로커에 분산해서 저장해요.
graph LR
subgraph Topic: order-topic
P0[Partition 0]
P1[Partition 1]
P2[Partition 2]
end
P0 --> B1
P1 --> B2
P2 --> B3
5.4 Kafka의 고가용성을 위한 복제 구조
Kafka는 단순히 데이터를 저장하는 게 아니라,
서버 장애가 발생해도 메시지가 손실되지 않도록 고가용성을 보장해야 해요.
그래서 Kafka는 각 Partition을 여러 브로커에 복제(Replication) 해서 저장해요.
order-topic
├── Partition 0
│ ├── Broker 1 (복제본 1)
│ ├── Broker 2 (복제본 2)
│ └── Broker 3 (복제본 3)
✅ 이렇게 복제하면 Broker 1이 죽어도 나머지 브로커에 데이터가 살아 있어서
시스템이 계속 동작할 수 있어요!
💬 복제를 하는 이유는?
| 이유 | 설명 |
|---|---|
| 장애 대응 | 한 브로커가 죽어도 데이터를 잃지 않도록 함 |
| 고가용성 | 계속해서 메시지를 보내고 읽을 수 있도록 |
| 무중단 운영 | 리더 장애 시 자동 전환이 가능함 |
5.5 리더와 팔로워는 파티션 단위로 정해져요
Kafka는 각 Partition마다 리더와 팔로워를 지정합니다.
- Partition 단위로 리더가 존재하고,
- 리더는 쓰기/읽기 담당,
- 팔로워는 리더의 데이터를 복제만 수행해요.
order-topic
├── Partition 0 → 리더: Broker 1, 팔로워: Broker 2, 3
├── Partition 1 → 리더: Broker 2, 팔로워: Broker 1, 3
├── Partition 2 → 리더: Broker 3, 팔로워: Broker 1, 2
💡 초보자 주의! Kafka의 리더/팔로워는 브로커 기준이 아니라 Partition 기준이에요.
🔄 Producer와 Consumer는 누구와 통신할까?
Kafka에서 외부와 통신하는 주체는 항상 리더 브로커입니다.
| 역할 | 리더와 통신 여부 |
|---|---|
| Producer | ✅ 리더에게만 메시지를 전송 |
| Consumer | ✅ 리더에게만 메시지를 요청 |
| Follower | ❌ 직접 통신 불가 (복제만 수행) |
graph TD
Producer --> B1_Leader(Leader for P0)
Consumer --> B1_Leader
B1_Leader --> B2_Follower
B1_Leader --> B3_Follower
⚖️ Kafka는 Partition 리더를 브로커에 자동으로 분산시켜요
Kafka는 모든 Partition의 리더가 특정 브로커에 몰리지 않도록
리더를 자동으로 여러 브로커에 분산 배치해요.
| Partition | Leader | Follower 1 | Follower 2 |
|---|---|---|---|
| P0 | B1 | B2 | B3 |
| P1 | B2 | B3 | B1 |
| P2 | B3 | B1 | B2 |
| P3 | B1 | B2 | B3 |
| P4 | B2 | B3 | B1 |
| P5 | B3 | B1 | B2 |
✅ Kafka는 이처럼 리더 브로커를 자동으로 균형 있게 배치해서
클러스터 전체에 부하를 분산시켜요.
💬 그러면 Consumer 요청도 브로커에 분산되겠네요?
맞아! Kafka의 Consumer는 메시지를 읽을 때 각 Partition의 리더 브로커에 요청(pull) 해요.
Consumer Group A
├── Partition 0 → 리더: B1에게 요청
├── Partition 1 → 리더: B2에게 요청
├── Partition 2 → 리더: B3에게 요청
💡 이 구조 덕분에 Consumer의 메시지 요청도 자동으로 브로커에 고르게 분산됩니다!
5.6 왜 Kafka는 3대 이상 브로커 구성이 필요할까?
Kafka는 복제본이 있을 때,
과반수 이상이 살아 있어야 정상 작동할 수 있어요.
flowchart LR
B1[Broker 1] -->|리더| P0
B2[Broker 2] -->|팔로워| P0
B3[Broker 3] -->|팔로워| P0
| 브로커 수 | 살아 있어야 하는 수 | 결과 |
|---|---|---|
| 1명 죽음 (2/3) | 과반수 생존 → ✅ 정상 | |
| 2명 죽음 (1/3) | 과반수 붕괴 → ❌ 쓰기 불가 |
✅ Kafka는 과반수 합의 기반으로 쓰기를 수행하기 때문에
보통 최소 3대 이상으로 구성해요
5.7 장애 발생 시 Kafka는 어떻게 동작할까?
sequenceDiagram
participant Producer
participant B1 as Broker 1 (리더)
participant B2 as Broker 2 (팔로워)
participant B3 as Broker 3 (팔로워)
Producer->>B1: 메시지 전송
B1-->>B2: 복제
B1-->>B3: 복제
Note over B1: 장애 발생
B2-->>Kafka Controller: 리더 승격 요청
Kafka Controller-->>B2: 리더 승격
Producer->>B2: 계속 전송
✅ Kafka는 장애 발생 시 팔로워를 리더로 자동 승격해서
서비스 중단 없이 계속 동작할 수 있어요
5.8 브로커 수 설계 팁
| 항목 | 설명 |
|---|---|
| 최소 구성 | 3대 이상 (복제 기준 포함) |
| 리더 분산 고려 | Partition 리더가 몰리지 않도록 Kafka가 자동 배치 |
| 수평 확장 | 필요할 때 브로커를 추가하면 자동으로 리밸런싱 가능 |
✅ 정리
- Kafka는 브로커 여러 대를 클러스터로 구성해 고가용성과 확장성을 갖춰요
- 리더/팔로워는 브로커 기준이 아니라 파티션 기준으로 존재해요
- Kafka는 각 Partition의 리더를 브로커에 자동 분산 배치해서 부하를 나눠요
- Producer/Consumer는 항상 리더 브로커와만 통신해요
- 최소 3대 이상 구성하면 1대 장애 시에도 과반수 유지로 무중단 운영이 가능해요
6장. Kafka 설치 및 기본 사용법
이 장에서는 Kafka를 직접 설치하고, 간단한 메시지를 주고받는 실습을 해봅니다.
초보자도 따라할 수 있도록 AWS EC2(Amazon Linux 2023) 환경에서 Docker 기반 Kafka를 실행해봅니다.
6.1 Docker 설치 (Amazon Linux 2023 기준)
Kafka는 JVM 기반으로 동작하는 복잡한 구조이기 때문에, Docker를 사용하면 훨씬 쉽게 실행할 수 있어요.
우선 EC2에 Docker를 설치하고 간단한 컨테이너가 잘 실행되는지 확인해보겠습니다.
1단계: 패키지 업데이트
sudo dnf update -y
2단계: Docker 설치
sudo dnf install -y docker
docker --version
3단계: Docker 데몬 활성화 및 시작
sudo systemctl enable --now docker
4단계: 테스트 – Hello World 실행
sudo docker run hello-world
위 명령을 실행하면 아래와 같은 출력이 나오면 성공입니다:
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
이 과정은 “Docker가 정상 작동하는지“를 확인하는 간단한 테스트예요.
5단계: 현재 사용자에 docker 권한 부여 (옵션)
매번 sudo docker라고 입력하는 게 불편하다면, 현재 사용자에게 Docker 그룹 권한을 부여하세요.
sudo groupadd docker
sudo usermod -aG docker $USER
왜 필요한가요?
→ 이 작업을 하면 앞으로는sudo없이도docker명령어를 실행할 수 있어요.
단, 이 명령 후에는 터미널을 완전히 종료했다가 다시 로그인해야 적용됩니다.
docker run hello-world # 이제 sudo 없이 실행!
6.2 Kafka 브로커 실행
이제 Kafka를 실행해봅시다.
Kafka 공식 Docker Hub 페이지에 나와 있는 예제와 같이 간단하게 실행합니다.
Kafka 이미지 다운로드
docker pull apache/kafka:4.0.0
Kafka 브로커 컨테이너 실행
docker run -d --name broker apache/kafka:4.0.0
컨테이너가 실행되면 Kafka 브로커가 자동으로 시작됩니다.
6.3 토픽 생성 및 메시지 송수신 테스트 (Producer → Consumer 순)
Kafka에서는 다음과 같은 순서로 메시지를 주고받습니다:
- 토픽(topic) 을 만든 후
- Producer가 메시지를 보내고,
- Consumer가 메시지를 받아 읽는 구조입니다.
1. Kafka 명령어를 실행하기 위해 컨테이너 내부로 진입
docker exec --workdir /opt/kafka/bin/ -it broker sh
2. 토픽 생성
./kafka-topics.sh --bootstrap-server localhost:9092 --create --topic test-topic
출력 예:
Created topic test-topic.
3. 메시지 전송 (Producer 먼저 실행)
./kafka-console-producer.sh --bootstrap-server localhost:9092 --topic test-topic
아래처럼 커서가 깜빡이며 입력 대기 상태가 됩니다.
메시지를 몇 줄 입력하고 Enter 를 누르면 전송됩니다:
hello kafka
this is my first message
다 입력했다면
Ctrl + C를 눌러 종료하세요.
이제 메시지가 Kafka에 저장된 상태입니다.
4. 메시지 수신 (Consumer 실행)
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test-topic --from-beginning
이제 위에서 보낸 메시지가 출력됩니다:
hello kafka
this is my first message
👉 Consumer 실행 상태에서는 메시지를 계속 수신 대기하게 됩니다.
메시지를 다 확인했으면Ctrl + C를 눌러 종료하세요.
6.4 테스트 종료 및 정리
컨테이너에서 나가기
Kafka 명령어를 실행했던 컨테이너 내부 셸에서 나가려면 아래 명령을 입력하세요:
exit
이 명령을 입력하면 EC2 터미널(호스트) 로 돌아옵니다.
Kafka 컨테이너 삭제
docker rm -f broker
Kafka 브로커를 완전히 종료하고 삭제합니다.
마무리
이 장에서는 Kafka를 Docker로 설치하고,
Producer → 메시지 전송 → Consumer → 메시지 수신의 흐름을 실제로 체험해봤습니다.
초보자도 터미널 하나만으로 순서대로 따라 할 수 있게 구성했으며,
이제 Kafka가 어떤 구조로 동작하는지 기본 감을 잡으셨을 거예요.
다음 장에서는 Kafka를 실제 운영 환경에서 어떻게 설계하고 사이징할지를 다룹니다.
7장. Kafka 브로커 설정 이해하기 (KRaft 기반)
Kafka는 과거 Zookeeper 기반으로 동작했지만,
Kafka 4.0부터는 Zookeeper 없이 운영 가능한 KRaft 모드가 기본입니다.
이 장에서는 Kafka 브로커 설정 파일(server.properties)을 기준으로,
KRaft 환경에서 반드시 필요한 설정 항목과 그 의미를 설명합니다.
특히 3대 브로커로 구성된 Kafka 클러스터를 기준으로 실무에 가까운 설정 예제를 제공합니다.
7.1 설정 파일 위치
Kafka Docker 컨테이너를 기준으로 설정 파일은 다음 위치에 존재하며,
브로커가 어떤 역할을 하느냐에 따라 다른 설정 파일을 사용할 수 있습니다.
| 파일 경로 | 사용 조건 | 설명 |
|---|---|---|
/opt/kafka/config/server.properties | 브로커 + 컨트롤러 | 브로커와 컨트롤러 역할을 동시에 수행하는 서버에서 사용 |
/opt/kafka/config/broker.properties | 브로커 전용 | 브로커 역할만 수행하는 서버에서 사용 |
/opt/kafka/config/controller.properties | 컨트롤러 전용 | 컨트롤러 역할만 수행하는 서버에서 사용 |
Docker 공식 이미지를 사용할 경우 기본적으로 server.properties를 사용하며,
대부분의 단일 노드 또는 소규모 구성에서는 브로커와 컨트롤러를 동시에 구성하기 때문에 이 파일이 사용됩니다.
주의: 각 설정 파일에는
process.roles,node.id,controller.quorum.voters등
KRaft에 필요한 필수 항목이 반드시 포함되어 있어야 합니다.
7.2 브로커 식별자
Kafka 클러스터에서 각 브로커를 구분하기 위해 고유한 ID를 부여해야 합니다.
KRaft 모드에서는 node.id 항목으로 브로커 ID를 설정합니다.
값은 1 이상의 정수이며, 클러스터 내에서 중복되지 않아야 합니다.
node.id=1
7.3 브로커 역할 설정
Kafka는 하나의 프로세스에서 브로커와 컨트롤러 역할을 동시에 수행할 수 있습니다.
컨트롤러는 클러스터 메타데이터와 리더 선출을 담당합니다.
process.roles=broker,controller
7.4 네트워크 설정
Kafka는 클라이언트 또는 다른 브로커와 통신하기 위해 네트워크 포트를 설정합니다.
브로커 역할과 컨트롤러 역할은 별도의 포트를 사용하며,
클라이언트가 접근할 수 있도록 외부 주소(advertised.listeners)를 반드시 지정해야 합니다.
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
advertised.listeners=PLAINTEXT://kafka1:9092
PLAINTEXT는 Kafka 클라이언트와 통신할 포트CONTROLLER는 브로커 간 메타데이터 통신용advertised.listeners는 도커 환경에서는 외부 접근을 위해 필수
7.5 컨트롤러 클러스터 설정
KRaft 모드에서는 Zookeeper 대신 브로커 간 직접 통신을 통해 컨트롤러가 선출됩니다.
이를 위해 아래 세 가지 설정이 반드시 필요합니다.
controller.listener.names=CONTROLLER
controller.quorum.voters=1@kafka1:9093,2@kafka2:9094,3@kafka3:9095
controller.quorum.bootstrap.servers=kafka1:9093,kafka2:9094,kafka3:9095
controller.listener.names는 컨트롤러 통신용 리스너 이름controller.quorum.voters는 컨트롤러 노드 ID와 주소 목록controller.quorum.bootstrap.servers는 컨트롤러 간 초기 통신에 사용될 주소 목록
주의: 이 세 항목은 모두 필수이며, 누락 시 Kafka는 실행되지 않습니다.
7.6 로그 저장 및 메시지 보존 설정
Kafka는 수신한 메시지를 디스크에 로그 형태로 저장합니다.
로그 파일의 위치, 보존 기간, 파일 크기 등을 다음 항목으로 설정할 수 있습니다.
log.dirs=/var/lib/kafka/data1
log.retention.hours=168
log.retention.bytes=-1
log.segment.bytes=1073741824
log.dirs: 메시지를 저장할 디렉토리log.retention.hours: 메시지를 최대 보관할 시간 (기본 168시간 = 7일)log.retention.bytes: 보존할 전체 로그의 최대 크기 (기본 -1은 무제한). 이 값을 넘으면 오래된 로그부터 삭제됩니다.log.segment.bytes: 로그를 나눠 저장하는 세그먼트 파일의 최대 크기 (기본 1GB)
log.retention.bytes와log.retention.hours는 보존 기준이며,
둘 중 하나라도 초과하면 Kafka는 오래된 로그부터 삭제합니다.log.segment.bytes는 삭제와는 관계 없이, 파일 분할 구조에만 영향을 미칩니다.
7.7 토픽 기본 설정
Kafka는 토픽 생성 시 기본값으로 적용할 파티션 수, 복제 수, 최소 동기화 복제본 수를 아래 항목으로 설정할 수 있습니다.
num.partitions=1
default.replication.factor=3
min.insync.replicas=2
num.partitions: 새 토픽 생성 시 기본 파티션 수 (기본값: 1)default.replication.factor: 새 토픽 생성 시 기본 복제 수. 클러스터의 브로커 수보다 크면 오류가 발생합니다.min.insync.replicas: 메시지를 쓰기 위해 최소한으로 살아 있어야 하는 복제본 수
default.replication.factor 와 min.insync.replicas 관계
- 이 두 값은 반드시 함께 고려하여 설정해야 합니다.
- 예:
default.replication.factor=3일 때,min.insync.replicas=2이상이면 복제 안정성이 확보됩니다. - 만약 브로커 3대 중 2대만 살아 있는데
min.insync.replicas=3이면 메시지를 쓸 수 없어 쓰기 실패가 발생합니다. - 반대로
min.insync.replicas=1처럼 너무 낮게 설정하면, 하나의 복제본만 살아 있어도 메시지를 기록하게 되어 데이터 유실 위험이 커집니다.
운영 환경에서는 일반적으로 복제 수의 과반수를
min.insync.replicas로 설정하는 것이 안전합니다.
7.8 기타 유용한 설정
log.cleanup.policy=delete
delete.topic.enable=true
auto.create.topics.enable=true
message.max.bytes=1048576
log.cleanup.policy: 메시지를 삭제할지(delete) 압축할지(compact) 선택delete.topic.enable: 토픽을 삭제할 수 있는지 여부auto.create.topics.enable: 존재하지 않는 토픽으로 메시지를 전송했을 때 자동 생성 여부message.max.bytes: 프로듀서가 전송할 수 있는 메시지의 최대 크기 (기본값: 1MB). 큰 메시지를 보내려면 이 값을 조정해야 합니다.
운영 환경에서는 auto.create.topics.enable=false로 설정하는 것이 일반적입니다.
7.9 브로커 3대 구성 예시
아래는 Kafka를 3대 브로커로 구성할 때의 설정 예시입니다.
모든 브로커는 컨트롤러 역할도 함께 수행합니다.
| 브로커 | node.id | CONTROLLER 포트 | 데이터 포트 | Hostname |
|---|---|---|---|---|
| kafka1 | 1 | 9093 | 9092 | kafka1 |
| kafka2 | 2 | 9094 | 9092 | kafka2 |
| kafka3 | 3 | 9095 | 9092 | kafka3 |
이 Hostname 값은 Docker 컨테이너 이름이거나,
/etc/hosts또는 DNS에 등록된 이름이어야 하며,
실제 환경에서는 내부 IP나 EC2 프라이빗 DNS로 대체해야 합니다.
broker1 설정 예시
node.id=1
process.roles=broker,controller
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
advertised.listeners=PLAINTEXT://kafka1:9092
controller.listener.names=CONTROLLER
controller.quorum.voters=1@kafka1:9093,2@kafka2:9094,3@kafka3:9095
controller.quorum.bootstrap.servers=kafka1:9093,kafka2:9094,kafka3:9095
log.dirs=/var/lib/kafka/data1
log.retention.hours=168
num.partitions=1
default.replication.factor=3
min.insync.replicas=2
broker2, broker3는 node.id, CONTROLLER 포트, log.dirs, advertised.listeners 부분만 각각 다르게 설정하면 됩니다.
7.10 설정 적용 시 주의사항
- server.properties를 수정한 후에는 Kafka 브로커를 반드시 재시작해야 적용됩니다.
- controller 관련 항목은 KRaft 클러스터 기동 시 필수입니다.
- 모든 브로커는 동일한
controller.quorum.voters와bootstrap.servers값을 가지고 있어야 클러스터로 인식됩니다.
마무리
이 장에서는 Kafka를 KRaft 모드로 구성할 때 필수적으로 알아야 할 설정 항목을 정리하고,
3개의 브로커로 Kafka 클러스터를 구성하는 실전 예시도 함께 확인했습니다.
다음 장에서는 이 설정을 기반으로 Kafka KRaft 클러스터를 실행하고 테스트하는 실습을 진행합니다.
8장. Kafka KRaft 클러스터 실습
Kafka 4.0부터는 Zookeeper 없이 Kafka 클러스터를 구성할 수 있습니다.
이 장에서는 Docker Compose를 이용해 Kafka KRaft 클러스터를 손쉽게 구성하고,
토픽 생성부터 메시지 송수신까지 간단한 실습을 진행합니다.
8.1 Docker Compose 설치 (Amazon Linux 2023 기준)
Kafka 클러스터 구성을 쉽게 하기 위해 Docker Compose를 사용합니다.
1. Compose 바이너리 설치
mkdir -p ~/.docker/cli-plugins
curl -sSL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
-o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
2. 정상 설치 확인
docker compose version
8.2 클러스터 구성 파일 다운로드
Kafka 클러스터 구성을 위한 docker-compose.yml 파일을 다운로드합니다.
mkdir docker-kafka-cluster
cd docker-kafka-cluster
wget -O docker-compose.yml https://raw.githubusercontent.com/i31pc17/bitbook/main/Apache%20Kafka/docker-compose.yml
이 구성은 Kafka 브로커 + 컨트롤러 역할을 동시에 수행하는 3노드 KRaft 클러스터 예제이며,
직접 제작된 커스텀 구성 파일입니다.
각 사용자의 환경에 따라 브로커 수, 포트, 볼륨 경로, 환경 변수 등을 자유롭게 수정해서 사용하면 됩니다.
8.3 Kafka 클러스터 실행
docker compose up -d
브로커와 컨트롤러가 모두 자동으로 실행됩니다.
8.4 토픽 생성 및 메시지 송수신 실습
Kafka 컨테이너 내부로 진입해 명령어를 실행합니다.
docker exec --workdir /opt/kafka/bin/ -it broker-controller-1 sh
1. 토픽 생성
./kafka-topics.sh --bootstrap-server broker-controller-1:19092,broker-controller-2:19092,broker-controller-3:19092 --create --topic test-topic
2. 메시지 전송 (Producer)
./kafka-console-producer.sh --bootstrap-server broker-controller-1:19092,broker-controller-2:19092,broker-controller-3:19092 --topic test-topic
입력창이 뜨면 메시지를 입력하고 Enter로 전송합니다.
예:
hello kafka
this is a test
Ctrl + C로 종료합니다.
3. 메시지 수신 (Consumer)
./kafka-console-consumer.sh --bootstrap-server broker-controller-1:19092,broker-controller-2:19092,broker-controller-3:19092 --topic test-topic --from-beginning
위에서 보낸 메시지가 출력됩니다.Ctrl + C로 종료합니다.
4. 컨테이너 셸에서 나가기
exit
8.5 클러스터 종료 및 정리
docker compose down
마무리
이제 Kafka KRaft 클러스터를 직접 실행해보고,
실제로 메시지를 주고받으며 Kafka의 기본 흐름을 체험해볼 수 있습니다.
다음 장에서는 Kafka 운영 시 고려해야 할 성능 최적화 및 사이징 전략을 다룹니다.
9장. Kafka 디스크 선택 가이드
Kafka는 데이터를 디스크에 저장하는 구조이기 때문에, 디스크의 성능과 구성은 Kafka 클러스터의 안정성과 성능에 큰 영향을 줍니다. 이 장에서는 Kafka 운영 시 디스크를 어떻게 선택하고 설계할지에 대한 가이드를 제공합니다.
9.1 디스크 성능 개념 이해
Kafka는 데이터를 파일 형태로 저장합니다. 이때 어떤 디스크를 쓰느냐에 따라 처리 속도와 안정성이 크게 달라집니다. 디스크 성능을 판단할 때 주로 아래 개념을 사용합니다.
| 용어 | 설명 |
|---|---|
| IOPS | 초당 몇 번 읽고 쓰기를 할 수 있는지 나타내는 수치입니다. 작은 메시지를 많이 처리할수록 중요합니다. |
| Throughput | 초당 얼마나 많은 데이터를 처리할 수 있는지를 나타냅니다. 큰 메시지를 빠르게 처리하려면 이 값이 중요합니다. |
| 블록 크기 | 한 번에 읽거나 쓰는 데이터의 최소 단위입니다. 일반적으로 4KB입니다. |
디스크 성능 계산 공식
IOPS = ceil((M × S) ÷ B)
Throughput = (M × S) ÷ 1024 ÷ 1024
- M: 초당 메시지 수
- S: 메시지 크기 (Bytes)
- B: 블록 크기 (Bytes)
예를 들어, 초당 5,000개의 메시지를 처리하고 메시지 크기가 1KB라면, 대략 5,000KB/s = 4.88MB/s의 Throughput이 필요합니다.
9.2 디스크 종류 및 성능 비교
Kafka를 EC2 환경에서 운영하는 경우, AWS의 EBS(Elastic Block Store) 볼륨 타입에 따라 디스크 성능이 달라집니다. 아래는 일반적인 EBS 디스크 성능 비교입니다.
| EBS 볼륨 타입 | IOPS(최대) | Throughput(최대) | 특징 |
|---|---|---|---|
| gp3 | 최대 16,000 | 최대 1,000MiB/s | 범용 SSD. 성능과 비용 균형이 좋음 |
| io2 | 최대 256,000 | 최대 4,000MiB/s | 고성능 SSD. 높은 IOPS가 필요한 워크로드에 적합 |
| st1 | 최대 500 | 최대 500MiB/s | 저렴한 HDD. 대용량 순차 작업에 적합 (보관용) |
| sc1 | 최대 250 | 최대 250MiB/s | 가장 저렴한 HDD. 접근이 거의 없는 장기 보관용 |
공식 성능 수치는 AWS 문서에서 확인 가능합니다: EBS 볼륨 성능 비교 표 (AWS)
9.3 Kafka 로그 저장 용량 계산법
Kafka는 수신한 메시지를 로그(segment 파일)로 저장하고, 설정에 따라 오래된 데이터를 삭제합니다. 다음 공식으로 필요한 디스크 용량을 계산할 수 있습니다.
계산 공식
총 저장 용량 = 하루 데이터량 × 보관일수 × replication.factor
브로커당 저장량 = 총 저장 용량 ÷ 브로커 수
운영 마진 포함 = 브로커당 저장량 × 1.2 ~ 1.3
예시
- 하루 트래픽: 1TB
- 보관일수: 7일
- replication.factor: 3
- 브로커 수: 3대
총 저장 용량 = 1TB × 7 × 3 = 21TB
브로커당 저장량 = 7TB
운영 마진 포함 = 약 9TB 추천
복제를 고려하면 디스크 사용량은 메시지 양보다 훨씬 많아질 수 있으므로 주의해야 합니다.
9.4 환경별 디스크 선택 기준
| Kafka 운영 환경 | 중요 요소 | 추천 디스크 |
|---|---|---|
| 메시지를 자주 보내는 경우 | IOPS | SATA SSD / NVMe SSD |
| 큰 메시지를 빠르게 처리해야 하는 경우 | Throughput | 고속 SSD / SAS SSD |
| 보관이 목적이고 조회는 거의 없는 경우 | 용량, 가격 | HDD |
| 실시간 처리 중심 | IOPS + Throughput | NVMe SSD |
9.5 설정과 함께 고려할 점
default.replication.factor가 클수록 같은 메시지가 여러 브로커에 저장되어 디스크 사용량이 증가합니다.log.retention.bytes를 설정하지 않으면 디스크가 가득 차도 데이터가 자동으로 삭제되지 않을 수 있습니다.log.segment.bytes값이 너무 작으면 파일이 수천 개 이상 생겨, 리눅스 시스템의 inode가 고갈될 수 있습니다. 일반적으로 1GB 이상으로 설정하는 것이 좋습니다.
9.6 Kafka 디스크 구성 주의사항
- 브로커 간 디스크 성능과 용량은 되도록 비슷하게 맞춰야 분산 처리에 문제가 없습니다.
- Kafka는 자체적으로 복제를 하기 때문에 RAID보다 JBOD 구성을 추천합니다.
- Kafka는 디스크 공간이 가득 차도 자동으로 데이터 삭제를 하지 않으므로, 모니터링 설정이 꼭 필요합니다.
- HDD는 순차 입출력은 괜찮지만, 랜덤 입출력에 약하므로 실시간 처리에는 부적합합니다.
9.7 JBOD와 디스크 전략
Kafka에서 JBOD(Just a Bunch of Disks)는 RAID 없이 각 디스크를 독립적으로 사용하는 방식입니다. log.dirs에 여러 디렉터리를 지정하면, Kafka는 파티션 데이터를 디스크마다 나눠 저장합니다.
📌 중복 저장이 아닌 분산 저장이므로, 디스크 하나가 고장 나면 그 디스크에 저장된 파티션만 영향을 받습니다. 복제(replication.factor ≥ 2)가 되어 있으면 다른 브로커의 복제본으로 복구됩니다.
💡 참고해 볼만한 실전 팁:
- 디스크 1개 IOPS가 낮아도 여러 개 구성하면 합산 IOPS 증가
- 500 IOPS × 10개 ≈ 5000 IOPS, 1000 IOPS × 5개도 동일 → 이론상 쓰기 성능은 비슷
- 하지만 디스크 개수가 많으면 병렬성 ↑, 장애 허용성 ↑, 운영 복잡도도 ↑
- 고성능 디스크는 관리가 쉽고 장애 확률 낮지만 가격이 비쌈
✅ 목적에 따라 ‘저렴한 디스크 여러 개(JBOD)’ vs ‘고성능 디스크 소수’ 전략을 선택합니다.
⚠️ 주의 사항
Kafka는 파티션 단위로 디스크를 분산하므로, 특정 토픽의 파티션 하나에 데이터가 몰릴 경우 해당 디스크 하나만 과도하게 부하될 수 있습니다.
즉, 전체 데이터가 디스크에 자동으로 균등 분산되는 것이 아니라, 파티션별 분산이라는 점을 고려해야 합니다.
→ 특정 파티션의 트래픽이 디스크 IOPS 한계를 초과하는 경우, 병목이 발생할 수 있습니다. 토픽 설계 시 파티션 수를 적절히 나누고, 파티션 부하 균형도 함께 고려해야 합니다.
9.8 파일 시스템
Kafka는 디스크에 데이터를 파일 형태로 저장하는 구조이지만, 특정 파일 시스템에 대한 하드 의존성은 없습니다.
운영 환경에서는 주로 다음 파일 시스템들이 사용됩니다:
| 파일 시스템 | 특징 |
|---|---|
| EXT4 | 리눅스 표준 파일 시스템으로 폭넓게 사용됩니다. 안정성이 높고 범용성이 뛰어납니다. |
| XFS | 대용량 데이터와 고속 쓰기 작업에 최적화된 파일 시스템으로, 대규모 저장소 환경에서 성능이 우수합니다. |
📊 파일 시스템 성능 비교
Kafka 클러스터에서 상당한 메시지 부하를 주어 다양한 파일 시스템 생성 및 마운트 옵션을 테스트한 결과,
Kafka의 주요 모니터링 지표인 Request Local Time(쓰기 작업 완료까지 걸리는 시간)에서 다음과 같은 결과가 관찰되었습니다:
- XFS는 로컬 시간이 훨씬 더 우수했습니다.
- XFS: 약 160ms
- EXT4(최적 구성 시): 약 250ms 이상
- XFS는 평균 대기 시간(Wait Time)도 더 짧았으며,
- 디스크 성능의 변동성(Variance)도 EXT4에 비해 훨씬 적었습니다.
이러한 결과로 Kafka와 같이 대량의 데이터를 지속적으로 처리하는 워크로드에서는 XFS가 더 안정적이고 일관된 성능을 제공하는 경향이 있음을 알 수 있습니다.
🔧 파일 시스템 마운트 설정: noatime 적용
Kafka는 파일의 atime(파일 마지막 읽은 시간) 정보를 사용하지 않습니다.
따라서 디스크를 마운트할 때 noatime 옵션을 설정하면 디스크에 불필요한 쓰기 작업을 줄여 성능을 더욱 최적화할 수 있습니다.
10장. Kafka 메모리 선택 가이드
Kafka는 데이터를 저장하고 처리하는 시스템입니다. 이 과정에서 가장 중요한 자원 중 하나가 바로 메모리(RAM) 입니다. 이 장에서는 Kafka 운영에 필요한 메모리 용량을 계산식 기반으로 추정하고, 각 메모리 용도의 산정 근거를 수식과 함께 설명합니다.
10.1 Kafka에서 메모리가 사용되는 영역
Kafka는 크게 다음 3가지 용도로 메모리를 사용합니다:
| 용도 | 설명 |
|---|---|
| Page Cache | 데이터를 디스크에 쓰기 전 OS가 잠시 보관하는 공간 |
| JVM Heap | Kafka 내부 처리를 위한 Java 객체 저장 공간 |
| 네트워크/복제 버퍼 | 메시지를 다른 브로커 또는 클라이언트로 전송하기 전 임시 저장 공간 |
10.2 Page Cache 용량 계산
Kafka 공식 문서에서는 쓰기 처리량 기반으로 Page Cache 메모리를 산정할 것을 권장합니다.
✅ 공식:
Page Cache 메모리 = 초당 쓰기 처리량(MB/s) × 30초
- 예: 초당 쓰기 처리량이 200MB/s →
200 × 30 = 6,000MB = 6GB - 운영 경험에 따른 안정적 버퍼 확보 시간(30초)을 기준으로 합니다.
10.3 JVM Heap 용량 계산
JVM Heap은 Kafka가 내부 상태, 파티션 정보, 요청 큐 등을 관리하기 위해 사용하는 메모리입니다.
✅ 공식 (경험 기반):
JVM Heap (GB) = (브로커의 파티션 수 ÷ 1000) × 1.5~2GB
단, 최대 16GB 이내
- 예: 브로커가 5,000개 파티션 →
5,000 ÷ 1000 × 2 = 10GB
JVM Heap은 16GB 이상 설정 시 GC 효율 저하, Compressed OOPs 비활성화 등의 문제가 발생하므로 제한하는 것이 일반적입니다.
10.4 네트워크/복제 버퍼 메모리 계산
Kafka는 데이터를 전송할 때 메모리 버퍼를 사용합니다. 이때 리더는 팔로워 수만큼 데이터를 메모리에 유지하게 됩니다.
✅ 공식:
네트워크/복제 버퍼 (bytes) = 초당 메시지 수 × 평균 메시지 크기(bytes) × (1 + 복제 팔로워 수) × 평균 버퍼 유지 시간(초)
- 예:
- 메시지 수: 10,000 msg/s
- 메시지 크기: 1,024 bytes
- 복제 계수: 3 (팔로워 2명)
- 유지 시간: 1초
= 10,000 × 1024 × 3 × 1 = 30,720,000 bytes ≈ 29.3MB
- 보수적으로 2 ~ 3초 버퍼링을 고려해 약 60 ~ 90MB 정도 확보 필요
10.5 JVM 외 메모리 고려 사항
JVM은 Heap 외에도 다양한 메모리 공간을 자동으로 사용합니다:
| 영역 | 설명 | 기본 예상 |
|---|---|---|
| Metaspace | 클래스 정보 등 | 수백 MB |
| Thread Stack | 스레드당 1MB | 수백 스레드 운영 시 수백 MB |
| Direct Memory | 네이티브 I/O, Netty 등 | 수백 MB ~ 수 GB |
| Code Cache | JIT 컴파일 코드 | 수십~수백 MB |
➡️ 총합: Heap 외 JVM 메모리로 약 1~4GB 이상 예상
10.6 전체 메모리 계산 요약
Kafka 노드 하나에 필요한 메모리는 아래와 같이 계산됩니다:
✅ 공식:
전체 메모리 ≈
(Page Cache) +
(JVM Heap) +
(네트워크/복제 버퍼) +
(JVM Heap 외 메모리) +
(운영 여유 공간)
📌 각 항목의 조건 요약:
| 항목 | 계산 조건 |
|---|---|
| Page Cache | 쓰기 처리량(MB/s) × 30초 |
| JVM Heap | (파티션 수 ÷ 1000) × 2, 단 최대 16GB |
| 네트워크/복제 버퍼 | 메시지 수/s × 메시지 크기 × (1 + 팔로워 수) × 유지 시간(초) |
| JVM 외 메모리 | 고정값 아님, 일반적으로 1~4GB 예상 |
| 운영 여유 공간 | 시스템 예외 상황 대비용, 일반적으로 2~4GB 확보 권장 |
예시:
- Page Cache = 6GB (200MB/s × 30초)
- JVM Heap = 10GB (5,000파티션)
- 네트워크/복제 버퍼 = 90MB (보수적 3초 기준)
- JVM 외 메모리 = 3GB
- 여유 공간 = 4GB
→ 총 메모리: 약 23GB 권장
✅ 정리
- Kafka 메모리 구성은 모두 수치 기반 계산식으로 추정 가능합니다.
- 설정 기준:
- Page Cache: 쓰기 처리량 기반
- Heap: 파티션 수 기반
- 복제 버퍼: 메시지 수 × 크기 × 복제 수 × 유지시간
- JVM Heap 외 메모리도 시스템 수준에서 반드시 고려해야 함
- 전체 메모리는 운영 환경에 맞게 여유 있게 확보하는 것이 핵심입니다.
11장. Kafka CPU 선택 가이드 (KRaft 기반)
Kafka를 KRaft 모드(Kafka Raft Metadata Mode)로 운영하는 경우, 브로커, 컨트롤러, 또는 브로커+컨트롤러 겸용 노드 각각의 역할에 따라 CPU 성능 요구가 달라집니다. 이 장에서는 Kafka에서 CPU가 실제로 사용되는 지점부터 설명하고, CPU를 선택할 때 고려할 요소들을 정리해 설명합니다.
11.1 Kafka에서 CPU가 사용되는 영역
Kafka는 디스크 기반 시스템이지만, 다음과 같은 작업에서 CPU 리소스를 적극적으로 사용합니다:
| 사용 영역 | 설명 |
|---|---|
| 메시지 압축/해제 | gzip, snappy, lz4 등의 압축 알고리즘 처리 시 CPU 소모 |
| 메시지 인코딩/디코딩 | JSON, Avro, Protobuf 등의 포맷 처리 |
| TLS 암호화/복호화 | 암호화된 트래픽 송수신 시 연산 발생 |
| 네트워크 처리 | 브로커의 I/O 스레드 및 데이터 송수신 처리 |
| 메타데이터 처리 | 컨트롤러가 클러스터 상태 및 리더 관리 수행 |
| 응답 속도 중심 작업 | 클라이언트 요청에 빠르게 응답하기 위해 단일 스레드 처리 성능 중요 |
특히 KRaft 모드에서는 ZooKeeper가 제거되며, 컨트롤러 노드가 모든 메타데이터를 직접 처리하기 때문에 CPU 중요성이 더 높아졌습니다.
11.2 CPU 성능 구성 요소
Kafka에서 CPU의 성능은 다음 세 가지 요소로 판단할 수 있습니다:
| 항목 | 설명 |
|---|---|
| 클럭 속도 (GHz) | 초당 처리 가능한 명령 수. 클럭이 높을수록 단일 처리 속도 빠름 |
| 코어 수 | 동시에 여러 작업을 처리할 수 있는 능력. 병렬 처리에 유리함 |
| IPC (Instructions Per Cycle) | 클럭 1회 주기당 처리할 수 있는 명령어 수. CPU 내부 설계에 따라 다름 |
CPU 성능 ≈ 클럭 속도 × IPC × 코어 수
※ 실제 성능은 캐시, 메모리 구조, 소프트웨어 최적화 등에 따라 달라질 수 있습니다.
11.3 IPC란 무엇인가?
IPC(Instructions Per Cycle)는 CPU가 한 클럭 주기 동안 처리할 수 있는 명령어 수를 뜻합니다. CPU 내부 설계(파이프라인, 캐시, 분기 예측 등)에 따라 IPC가 달라지며, 같은 클럭이라도 IPC가 높으면 더 빠른 성능을 발휘합니다.
- IPC는 세대가 높을수록 개선되는 경향이 있습니다.
- 예: Intel Skylake → Alder Lake → Raptor Lake
- 예: AMD Zen → Zen 2 → Zen 3 → Zen 4
➡️ 같은 클럭과 코어 수라도 세대가 다르면 성능 차이가 큽니다.
11.4 CPU 성능 확인 방법
IPC는 CPU 제조사에서 명확하게 수치를 제공하지 않기 때문에, 실제 성능은 벤치마크 점수를 참고하는 것이 좋습니다.
벤치마크 확인 사이트
이 사이트들에서는 CPU 모델명을 입력하면 상대적인 점수와 싱글/멀티 코어 성능을 확인할 수 있습니다.
11.5 AWS 인스턴스에서의 CPU 선택 참고사항
Kafka를 AWS EC2에서 운영할 경우, 인스턴스 유형에 따라 탑재된 CPU 종류가 다르므로 주의가 필요합니다.
- 같은 vCPU 수여도, 인스턴스에 따라 실제 성능 차이가 클 수 있습니다.
- 예를 들어,
c6i와c5는 모두 8 vCPU로 구성할 수 있지만, 장착된 CPU 아키텍처나 세대가 다르면 IPC와 클럭이 달라 실제 처리 성능이 다릅니다. - 또한, 인텔과 AMD 계열, Graviton ARM 계열 간에도 성능 특성이 다릅니다.
확인 팁
- AWS 공식 EC2 인스턴스 문서에서 각 인스턴스에 어떤 CPU가 장착되어 있는지 확인할 수 있습니다.
- EC2 콘솔에서 “인스턴스 유형“을 선택하면 탑재된 CPU 모델을 직접 확인할 수 있습니다.
참고: vCPU 수가 많다고 해서 반드시 성능이 선형적으로 향상되는 것은 아니며, 클럭과 IPC, 워크로드 특성을 함께 고려해야 합니다.
11.6 노드 유형별 CPU 선택 기준
브로커 전용 노드
- 메시지 전송/수신, 압축, 인코딩, 디스크 처리 집중
- 네트워크와 병렬 처리 중요
- 권장 사양: 8코어 이상, 3.0GHz 이상, 최신 세대 CPU
컨트롤러 전용 노드 (KRaft)
- 클러스터 메타데이터 관리, 리더 선출 등 수행
- 빠른 응답과 안정적 동작이 중요
- 권장 사양: 4~8코어, 3.0GHz 이상, IPC가 높은 최신 세대 CPU
브로커 + 컨트롤러 겸용 노드
- 위 두 역할을 모두 수행함
- 병렬 처리와 단일 작업 응답 모두 필요
- 권장 사양: 8~16코어, 3.2GHz 이상, 멀티/싱글 스레드 모두 성능 우수한 모델
11.7 CPU 선택 요약
| 노드 유형 | 권장 사양 |
|---|---|
| 브로커 전용 | 8코어 이상, 3.0GHz 이상, 최신 세대 CPU |
| 컨트롤러 전용 | 4~8코어, 3.0GHz 이상, IPC 우수 모델 |
| 브로커 + 컨트롤러 겸용 | 8~16코어, 고클럭 + 고IPC CPU |
클럭 속도, IPC, 코어 수를 균형 있게 고려하고, 세대별 IPC 차이를 포함한 벤치마크 결과를 확인하면 효율적인 CPU 선택이 가능합니다.
12장. Kafka 네트워크 카드 선택 가이드 (KRaft 기반)
Kafka를 KRaft 모드(Kafka Raft Metadata Mode)로 운영할 경우, 브로커, 컨트롤러, 또는 브로커/컨트롤러 겸용 노드 각각의 역할에 따라 네트워크 카드(NIC)의 성능 요구가 달라집니다. 이 장에서는 Kafka에서 네트워크가 어떻게 사용되는지와, 역할별 적절한 NIC를 선택하는 방법을 정리합니다.
12.1 Kafka에서 네트워크가 사용되는 영역
Kafka 클러스터에서는 아래와 같은 통신이 지속적으로 발생합니다:
| 통신 대상 | 설명 |
|---|---|
| 프로듀서 → 브로커 | 데이터 수집 (Ingress) |
| 브로커 → 컨슈머 | 데이터 전송 (Egress) |
| 브로커 ↔ 브로커 | 파티션 복제 (Replication) |
| 브로커 ↔ 컨트롤러 | 메타데이터 동기화 |
| 컨트롤러 ↔ 컨트롤러 | Raft 기반 메타데이터 복제 |
12.2 브로커 기준 트래픽 예시 계산
예를 들어 다음과 같은 조건에서:
- 메시지 크기: 1KB
- 초당 메시지 수: 50,000
- replication.factor: 3
계산 결과:
- Ingress: 50 MB/s
- Replication: 100 MB/s (2개 복제 노드)
- Egress: 50 MB/s
- 총합: 200 MB/s ≒ 약 1.68 Gbps
→ 이 정도 트래픽을 안정적으로 처리하려면 최소 10Gbps NIC가 필요합니다.
12.3 단위 변환 공식 (MB/s → Gbps)
- 1 Byte = 8 bits
- 1 MB = 1,048,576 Bytes
- 1 Gbps = 1,000,000,000 bits/s
200 MB/s × 1,048,576 × 8 = 1,677,721,600 bits/s ≒ 1.68 Gbps
12.4 노드 유형별 NIC 선택 기준
브로커 전용 노드
- 권장 NIC: 10~25Gbps 이상
- 특징: 데이터 수신, 복제, 전송 모두 포함 → 트래픽 가장 높음
- 기능 추천: RSS, MTU 9000, TCP Offload
컨트롤러 전용 노드
- 권장 NIC: 1Gbps 이상 (안정성 우선)
- 특징: 메타데이터 트래픽 위주, Raft 기반 통신 → 지연(Latency)에 민감
- 권장 설정: MTU 9000
브로커 + 컨트롤러 겸용 노드
- 권장 NIC: 25Gbps 이상
- 특징: 데이터 처리와 메타데이터 처리 동시 수행 → 고성능 NIC 필요
12.5 AWS EC2 기준 네트워크 성능 예시
| 인스턴스 타입 | 최대 네트워크 대역폭 | 특징 |
|---|---|---|
| t3.medium | 최대 5Gbps | 테스트 용도 |
| m5.2xlarge | 최대 10Gbps | 소규모 운영용 |
| c6i.4xlarge | 최대 12.5Gbps | 일반 브로커 용도 |
| m6in.8xlarge | 최대 40Gbps + MTU 9000 지원 | 겸용 노드에 적합 |
| c6gn.16xlarge | 최대 100Gbps | 고성능/대형 클러스터용 |
참고: ENA(Elastic Network Adapter)와 MTU 9000 지원 여부를 반드시 확인해야 합니다.
12.6 ENA와 MTU 9000 개념 정리
ENA (Elastic Network Adapter)
AWS EC2의 고성능 네트워크 어댑터로:
- 최대 100Gbps까지 지원
- 멀티큐(RSS), 네트워크 오프로드 등 고급 기능 포함
- Kafka 같은 네트워크 중심 워크로드에서 매우 유리함
MTU 9000 (Jumbo Frame)
- 일반 MTU는 1500 Bytes
- MTU 9000은 패킷당 9000 Bytes까지 전송 가능
- 장점:
- CPU 부하 감소
- 전송 효율 향상
- Throughput 증가
12.7 네트워크 튜닝 실전 명령어
| 항목 | 명령어 |
|---|---|
| MTU 확인/설정 | ip link show / ip link set dev eth0 mtu 9000 |
| NIC 오프로드 기능 확인 | ethtool -k eth0 |
| 멀티큐(RSS) 설정 확인 | ethtool -l eth0 |
| NIC 드라이버 확인 | ethtool -i eth0 (ENA 여부 확인용) |
12.8 네트워크 선택 요약
| 역할 | 트래픽 강도 | 권장 NIC | 비고 |
|---|---|---|---|
| 브로커 | 매우 높음 | 10~25Gbps 이상 | 메시지 수신/전송 + 복제 포함 |
| 컨트롤러 | 낮음 (지연 민감) | 1Gbps 이상 | 안정적인 메타데이터 처리 중요 |
| 겸용 노드 | 매우 높음 + 지연 중요 | 25Gbps 이상 | 종합적 처리 요구 |
Kafka 클러스터에서 네트워크는 병목 현상을 가장 쉽게 일으킬 수 있는 요소입니다.
따라서 트래픽 양에 맞는 NIC 대역폭과 기능을 갖춘 구성이 중요합니다.
13장. Kafka 파일 디스크립터 설정
Kafka는 데이터를 디스크에 저장하고, 수많은 클라이언트와 연결을 유지하면서 동작합니다. 이 과정에서 운영체제가 관리할 수 있는 파일 디스크립터(File Descriptor) 수가 충분하지 않으면 성능 저하나 오류가 발생할 수 있습니다. 이 장에서는 디스크립터의 개념과 Kafka에 적절한 디스크립터 수를 계산하고 설정하는 방법을 설명합니다.
13.1 파일 디스크립터란?
파일 디스크립터는 리눅스에서 파일, 네트워크 연결(소켓), 파이프 등을 구분하기 위해 사용하는 숫자 ID입니다. Kafka는 로그 파일을 많이 열고, 클라이언트와 복제 연결도 유지하기 때문에 많은 디스크립터를 사용합니다.
13.2 Kafka에서 디스크립터가 중요한 이유
Kafka 브로커는 다음과 같은 작업에 디스크립터를 사용합니다:
| 항목 | 설명 |
|---|---|
| 로그 세그먼트 파일 | 파티션 데이터를 저장하는 .log, .index, .timeindex 파일 |
| 클라이언트 연결 | 프로듀서와 컨슈머의 TCP 연결 |
| 복제 연결 | 브로커 간 파티션 복제를 위한 연결 |
| 컨트롤러 연결 | KRaft 모드에서는 컨트롤러와 브로커 간 메타데이터 동기화 |
13.3 디스크립터가 부족할 때 발생하는 문제
Too many open files오류- 로그 파일을 열지 못해 데이터 유실 가능성
- 클라이언트 접속 실패 → 서비스 중단
OutOfMemoryError (Map failed)발생 가능
13.4 디스크립터 수 계산 방법
Kafka 운영 전에 필요한 디스크립터 수를 계산해보는 것이 중요합니다.
총 디스크립터 수 =
예상 연결 수
+ (파티션 수 × (파티션 크기 ÷ 세그먼트 크기))
- 예상 연결 수: 프로듀서 수 + 컨슈머 수 + 복제 연결 + 컨트롤러 연결
(파티션 크기 ÷ 세그먼트 크기)= 파티션당 세그먼트 수- 세그먼트당 실제 파일은 3개이지만, 보수적으로 1개로 간주
예시
- 연결 수: 6,000
- 파티션 수: 1,000
- 파티션 크기: 10GB
- 세그먼트 크기: 1GB
총 디스크립터 수 = 6,000 + (1,000 × 10) = 6,000 + 10,000 = 16,000개 이상 필요
Kafka는 버퍼 포함해서 100,000개 이상을 권장합니다.
13.5 관련 설정 확인 방법
세그먼트 크기 설정 (server.properties)
log.segment.bytes=1073741824 # 1GB
파티션 크기 추정 기준
- 하루 1GB 데이터 × 30일 보관 = 파티션당 30GB
- 이 기준으로 전체 파티션 크기 계산
13.6 디스크립터 수 설정 방법 (Linux)
임시 적용 (쉘 세션 한정)
ulimit -n 100000
영구 적용 방법
limits.conf
# /etc/security/limits.conf
* soft nofile 100000
* hard nofile 100000
systemd 설정 (kafka.service)
[Service]
LimitNOFILE=100000
설정 반영:
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl restart kafka
14장. Kafka 가상 메모리 튜닝
Kafka는 운영체제의 페이지 캐시(Page Cache) 를 적극 활용해 디스크 I/O 성능을 높이는 구조입니다. 따라서 가상 메모리와 스와핑(Swapping) 설정은 Kafka 성능에 매우 큰 영향을 미칩니다.
14.1 스와핑(Swapping)이란?
운영체제에서 메모리가 부족하면, 자주 사용하지 않는 데이터를 디스크의 스왑 공간(swap area) 으로 옮겨 RAM 공간을 확보합니다. 이 과정을 스와핑(swapping) 이라고 합니다.
마치 방이 좁아서 자주 안 쓰는 짐을 창고(디스크)로 보내는 것과 비슷한 개념입니다.
14.2 Kafka에서 스와핑이 문제가 되는 이유
Kafka는 실시간 메시지 처리 시스템입니다. 빠른 응답과 낮은 지연 시간이 핵심인데, 스와핑이 발생하면 다음과 같은 문제를 일으킬 수 있습니다:
| 문제 상황 | 영향 |
|---|---|
| 페이지 캐시가 줄어듦 | 디스크 쓰기/읽기 성능 저하, 처리량 감소 |
| Kafka 스레드가 스왑됨 | 메시지 처리 지연, 응답 시간 불안정 |
| 스와핑이 반복됨 | 전체 시스템 성능 급락 (Thrashing 현상 발생) |
14.3 vm.swappiness란?
vm.swappiness는 Linux 커널 파라미터로, 운영체제가 메모리가 부족할 때 스왑 공간을 얼마나 적극적으로 사용할지를 결정합니다.
값이 낮을수록 실제 RAM을 더 많이 활용하고, 스왑 사용을 줄입니다.
설정 방법 (임시 적용)
sudo sysctl -w vm.swappiness=1
설정 방법 (영구 적용)
echo "vm.swappiness = 1" | sudo tee -a /etc/sysctl.conf
14.4 설정값의 의미와 Kafka 권장값
| 설정값 | 의미 |
|---|---|
| 0 | 절대 스왑 사용 안 함. RAM 부족 시 OOM 위험 높음 |
| 1 | 거의 스왑을 사용하지 않음. Kafka 권장 설정 |
| 60 | 기본값. 일반적인 Linux 서버 운영 환경 |
| 100 | 가능한 많이 스왑을 사용함 |
Kafka에서는?
vm.swappiness = 0은 OOM(Out of Memory) 가능성이 높아 위험할 수 있음vm.swappiness = 1은 스왑을 거의 사용하지 않으면서, 시스템이 완전히 RAM을 소진했을 때만 최소한으로 스왑을 활용함
✅ 따라서 Kafka 운영 환경에서는 다음과 같은 설정이 권장됩니다:
vm.swappiness = 1
안정적인 처리 속도와 디스크 I/O 성능 유지를 위해 최소 스왑 정책이 가장 바람직합니다.
15장. Kafka 소켓 버퍼 크기 설정
Kafka는 네트워크를 통해 데이터를 주고받는 시스템입니다. 이때 Kafka 브로커는 보내거나 받는 데이터를 일시적으로 담아두는 소켓 버퍼(Socket Buffer) 를 사용합니다. 이 버퍼가 충분하지 않으면 빠른 네트워크에서도 데이터를 처리하지 못해 성능 저하나 데이터 유실이 발생할 수 있습니다.
15.1 소켓 버퍼란?
소켓 버퍼는 TCP 통신에서 데이터를 임시로 저장하는 공간입니다. Kafka는 데이터를 주고받을 때 이 공간을 거쳐 송수신을 하며, 버퍼가 작으면 처리 속도가 줄어들고, 버퍼가 충분하면 성능을 안정적으로 유지할 수 있습니다.
15.2 최소, 기본, 최대 버퍼 크기란?
TCP에서는 버퍼 크기를 다음 세 단계로 나눠서 관리합니다.
| 항목 | 설명 |
|---|---|
| 최소 (min) | 절대 보장해야 하는 가장 작은 버퍼 크기 (일반적으로 4KB) |
| 기본 (default) | 연결이 시작할 때 사용하는 기본 버퍼 크기 (Kafka에서는 512KB 권장) |
| 최대 (max) | 네트워크 상황이 좋을 때 확장 가능한 최대 버퍼 크기 (안정적인 값은 2MB이지만, 실제 작업 부하에 근거하여 BDP 계산 기반으로 더 크게 설정 가능) |
15.3 Kafka에서 기본 버퍼를 512KB로 설정하는 이유
- Kafka는 1MB 이상의 데이터를 한 번에 주고받는 경우가 많습니다.
- 기본 버퍼가 너무 작으면 TCP Slow Start 구간이 길어져 Throughput이 제대로 나오지 않습니다.
- 512KB로 설정하면 1Gbps~10Gbps 환경에서 빠른 네트워크 부하에도 초기 성능이 안정적으로 유지됩니다.
- 실무에서는 안정성을 중시해서 128KB로도 기본값을 줄일 수 있지만, Kafka의 대량 메시지 처리 특성상 512KB 이상을 기본으로 설정하는 것이 권장됩니다.
15.4 버퍼 크기는 어떻게 정할까? (BDP 기반)
버퍼 크기를 정할 때 사용하는 기준이 바로 BDP(Bandwidth Delay Product) 입니다.
📘 BDP란?
BDP는 “네트워크 대역폭과 지연 시간(RTT)을 고려하여, 버퍼에 저장할 수 있는 최적의 데이터 양“입니다.
공식:
BDP = (네트워크 속도(Gbps) × 왕복 지연 시간(RTT, 초)) ÷ 8
bit 단위 대역폭을 byte로 바꾸기 위해 ÷ 8을 합니다.
네트워크를 충분히 활용하려면 최소 BDP만큼의 버퍼가 필요하고, 실무에서는 BDP × 1.5~2배 여유를 주는 것이 일반적입니다.
15.5 버퍼 크기 결정 흐름
TCP 소켓 버퍼 크기는 다음 흐름으로 결정됩니다.
-
소켓 생성 시 기본값 적용
net.core.rmem_default(수신 버퍼 기본값)net.core.wmem_default(송신 버퍼 기본값)
-
TCP 연결 후 네트워크 상황에 따라 자동 확장
net.ipv4.tcp_rmem(수신 버퍼 최소/기본/최대 범위)net.ipv4.tcp_wmem(송신 버퍼 최소/기본/최대 범위)
-
소켓 버퍼 크기 확장 시 최대 한도 제한
net.core.rmem_max,net.core.wmem_max값을 넘지 못함
-
서버 전체 TCP 메모리 총량 제한
net.ipv4.tcp_mem에 설정된 값 이상으로는 전체 TCP 소켓 메모리를 사용할 수 없음tcp_mem값은 페이지 단위(보통 4KB)로 설정되며, 경고선(soft limit)과 절대 한계(hard limit)를 초과할 경우 새 소켓 생성이나 버퍼 확장이 거부될 수 있음- TCP 소켓 하나는 송신(wmem) 버퍼와 수신(rmem) 버퍼를 모두 사용합니다.
- 서버 전체의 모든 소켓의 송신/수신 버퍼 사용량 합계를 기준으로
net.ipv4.tcp_mem리밋이 적용됩니다.
net.ipv4.tcp_mem 세 값의 의미 및 설정 기준
| 값 | 의미 | 설정 기준 |
|---|---|---|
| 첫 번째 값 (경고 없이 허용) | 이 범위까지는 정상 운영 | 예상 TCP 메모리 사용량 |
| 두 번째 값 (소프트 리밋) | 초과 시 커널이 버퍼 회수(cleanup) 시작 | 경고 없이 허용 × 1.5배 |
| 세 번째 값 (하드 리밋) | 초과 시 소켓 생성 실패, 버퍼 확장 거부 | 소프트 리밋 × 2배 |
📘 Kafka 환경에서는 어떻게 설정할까?
Kafka는 송신과 수신 트래픽이 대체로 비슷하게 발생하는 구조입니다.
따라서 전체 TCP 메모리 소모량을 다음 기준으로 예상하고 설정합니다.
- NIC 속도와 RTT를 기반으로 BDP를 계산한다.
- 활성 소켓 수를 고려하여 예상 TCP 메모리 소비량을 구한다.
- 송수신 합산을 고려해 예상량 × 2배로 계산한다.
- 여유를 주기 위해 예상 사용량의 1.5배 이상으로 소프트 리밋을 설정한다.
- 소프트 리밋의 약 2배를 하드 리밋으로 설정한다.
이렇게 설정하면 Kafka 브로커가 다수의 프로듀서와 컨슈머를 동시에 처리할 때에도 안정적인 네트워크 성능을 유지할 수 있습니다.
15.6 다수의 소켓 연결 시 버퍼 크기 조정
Kafka는 여러 프로듀서 및 컨슈머와 동시에 TCP 연결을 유지합니다. 따라서 단일 소켓이 NIC의 전체 대역폭을 모두 사용할 수 없으므로, 연결된 소켓 수를 고려하여 버퍼를 조정해야 합니다.
- NIC 속도가 10Gbps라면 송신과 수신 각각 10Gbps이며, 동시에 사용 가능합니다.
- 총 예상되는 프로듀서 및 컨슈머 소켓 수로 NIC 속도를 나누어 단일 소켓당 버퍼 크기를 산정해야 합니다.
예시 (RTT 50ms, NIC 10Gbps):
- BDP = 1250MB/s × 0.05초 = 62.5MB
- 프로듀서 소켓 100개: 단일 소켓당 수신 버퍼 최소 크기 ≈ 62.5MB ÷ 100 ≈ 0.625MB
- 컨슈머 소켓 200개: 단일 소켓당 송신 버퍼 최소 크기 ≈ 62.5MB ÷ 200 ≈ 0.3125MB
15.7 설정 방법
Linux 커널 설정 (/etc/sysctl.conf)
# 소켓 기본 버퍼 크기 (512KB)
net.core.rmem_default = 524288
net.core.wmem_default = 524288
# 소켓 최대 버퍼 크기 (2MB)
net.core.rmem_max = 2097152
net.core.wmem_max = 2097152
# TCP 동적 버퍼 크기 (min, default, max)
net.ipv4.tcp_rmem = 4096 524288 2097152
net.ipv4.tcp_wmem = 4096 524288 2097152
# TCP 메모리 총량 제한 (페이지 단위)
net.ipv4.tcp_mem = 65536 131072 262144
tcp_mem설정은 순서대로 (경고 없이 허용되는 값, 소프트 리밋, 하드 리밋)이며, 각 값은 페이지 수입니다. 예를 들어65536페이지는 약 256MB(65536 × 4KB)입니다.
적용 명령어:
sudo sysctl -p
Kafka 설정 (server.properties)
# Kafka 소켓 버퍼 크기 설정
socket.receive.buffer.bytes=2097152
socket.send.buffer.bytes=2097152
BDP에 따라 더 큰 값을 적용할 수도 있습니다.
※ 참고: Kafka 브로커 설정(
socket.receive.buffer.bytes,socket.send.buffer.bytes)은 OS 커널 설정(net.core.rmem_max,net.core.wmem_max)의 최대값을 초과할 수 없습니다. Kafka가 요청한 값이 커널 최대값을 넘으면 커널이 강제로 잘라 적용합니다. 따라서 OS와 Kafka 설정을 함께 조정해야 합니다.
15.8 확인 및 측정 팁
- RTT 측정:
ping [IP주소] - NIC 속도 확인:
ethtool [인터페이스명] - 현재 소켓 버퍼 확인:
ss -tnm명령어로 소켓별 버퍼 크기 확인 가능
※ 인터페이스명은 ip addr 또는 ip link show 명령어로 실제 서버에 설정된 이름을 확인한 후 사용해야 합니다. (예: eth0, ens5 등)
15.9 추가적으로 고려해야 할 TCP 관련 설정
net.ipv4.tcp_window_scaling
TCP 윈도우 스케일링 기능을 활성화하는 옵션입니다.
net.ipv4.tcp_window_scaling = 1
이 값을 1로 설정하면 클라이언트가 데이터를 더 효율적으로 전송할 수 있으며, 브로커도 수신 데이터를 더 잘 버퍼링할 수 있습니다. 빠른 네트워크 환경에서는 필수로 켜야 하는 옵션입니다.
net.ipv4.tcp_max_sync_backlog
브로커가 동시에 처리할 수 있는 TCP 연결 요청 대기열 크기를 설정하는 옵션입니다.
net.ipv4.tcp_max_sync_backlog = 4096
기본값은 1024로 작기 때문에, Kafka 브로커처럼 많은 클라이언트 연결을 받을 경우 4096 이상으로 설정하는 것이 좋습니다.
net.core.netdev_max_backlog
네트워크 인터페이스가 처리하지 못한 패킷을 임시로 커널 큐에 쌓아두는 최대 수를 정하는 옵션입니다.
net.core.netdev_max_backlog = 5000
기본값은 1000인데, 멀티 기가비트 NIC 환경에서는 큐 길이를 크게 늘려야 네트워크 트래픽 급증 시에도 패킷 손실을 방지할 수 있습니다.
16장. Kafka 메모리 맵(vm.max_map_count)
Kafka는 데이터를 디스크에 저장할 때, 파일을 메모리에 매핑(mmap)하는 방식을 사용합니다. 이 장에서는 Kafka에서 메모리 맵이 왜 중요한지, 설정 방법과 주의사항을 정리합니다.
16.1 메모리 맵(Memory Map)이란?
- 디스크 파일을 직접 접근하지 않고, 메모리처럼 빠르게 접근할 수 있게 해주는 운영체제 기능입니다.
- 기존
read()/write()방식보다 빠른 데이터 접근이 가능합니다.
📦 비유: 책장을 열어 책을 꺼내는 대신, 책상 위에 펼쳐놓고 바로 읽는 것과 같습니다.
16.2 Kafka에서 메모리 맵이 중요한 이유
- Kafka는 각 파티션마다 여러 개의 로그 세그먼트 파일을 관리합니다.
- 세그먼트 파일은 인덱스 파일과 타임 인덱스 파일로 구성되고, 각각 메모리에 매핑됩니다.
- 세그먼트 수가 많아질수록 필요한 메모리 맵 수도 급격히 증가합니다.
16.3 메모리 맵 부족 시 문제
vm.max_map_count(한 프로세스당 메모리 맵 최대 개수)를 초과하면:- 파일을 메모리에 매핑하지 못함
OutOfMemoryError (Map failed)오류 발생- Kafka 브로커 다운 위험
특히 파티션 수가 많거나, 세그먼트 크기가 작을 경우 더욱 치명적입니다.
16.4 현재 메모리 맵 설정 확인 및 변경 방법
현재 설정 확인
cat /proc/sys/vm/max_map_count
임시 설정 (재부팅 시 초기화)
sudo sysctl -w vm.max_map_count=262144
영구 설정 (/etc/sysctl.conf)
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
16.5 메모리 맵 수에 따른 메모리 소모량
| 메모리 맵 수 | 64B 기준 | 128B 기준 | 256B 기준 |
|---|---|---|---|
| 2¹⁸ (262,144) | 16MB | 32MB | 64MB |
| 2¹⁹ (524,288) | 32MB | 64MB | 128MB |
| 2²⁰ (1,048,576) | 64MB | 128MB | 256MB |
현대 서버 메모리 용량을 고려하면, 메모리 맵 수를 충분히 늘려도 부담이 거의 없습니다.
16.6 필요한 메모리 맵 수 계산 방법
기본 공식
필요 메모리 맵 수 = (파티션 수) × (파티션당 세그먼트 수) × 2
여유를 고려한 공식 (30% 추가)
필요 메모리 맵 수 = (파티션 수) × (세그먼트 수) × 2 × 1.3
예시 계산
- 파티션 수: 10,000개
- 평균 세그먼트 수: 5개
10,000 × 5 × 2 × 1.3 = 130,000
→ 최소 262,144개(2¹⁸)로 설정하는 것이 안전합니다.
16.7 왜 262,144(2¹⁸)를 권장하는가?
- 262,144는 2¹⁸로, 시스템 자원 관리를 최적화할 수 있는 크기입니다.
- Kafka 운영 특성상 파티션, 세그먼트 수가 증가하기 때문에 여유 있게 설정하는 것이 안전합니다.
- Kafka 공식 가이드와 커뮤니티에서도 기본적으로 262,144 이상을 권장합니다.
16.8 결론
- 메모리 맵은 Kafka 브로커의 데이터 처리 성능과 안정성에 직접적인 영향을 줍니다.
- 파티션 수, 세그먼트 수를 고려하여 필요한 메모리 맵 수를 계산하고 충분히 여유를 둬야 합니다.
- 기본적으로
vm.max_map_count=262144이상으로 설정하고, 필요한 경우 더 높게 조정하는 것이 좋습니다.
Kafka 브로커를 안정적으로 운영하기 위해, 메모리 맵 설정은 필수적인 튜닝 항목입니다.