Skip to content

Commit 20693c2

Browse files
committed
Write Kafka Post " [카프카 핵심 가이드] CHAPTER 2. 카프카 설치하기 "
1 parent 80165a6 commit 20693c2

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
layout: post
3+
title: " [카프카 핵심 가이드] CHAPTER 2. 카프카 설치하기 (설치부터 파티션과 브로커 규모 산정까지) "
4+
categories: Kafka
5+
author: devFancy
6+
---
7+
* content
8+
{:toc}
9+
10+
> 이 글은 [카프카 핵심 가이드](https://product.kyobobook.co.kr/detail/S000201464167?utm_source=google&utm_medium=cpc&utm_campaign=googleSearch&gad_source=1) 책을 읽고 정리한 글입니다.
11+
>
12+
> 이 글에서 다루는 모든 코드는 [깃허브](https://github.com/devFancy/springboot-coupon-system)에서 확인하실 수 있습니다.
13+
>
14+
> `참고`: 본 글에서 소개되는 코드 예시는 현재 시점의 구현을 바탕으로 작성되었으며, 프로젝트가 발전함에 따라 내용이 변경되거나 개선될 수 있음을 미리 알려드립니다.
15+
16+
## Prologue
17+
18+
"카프카 핵심 가이드" 2장 내용을 바탕으로 카프카의 기본 구성 요소와 설치, 그리고 운영에 필수적인 설정 값들 정리해보았다.
19+
20+
특히 쿠폰 시스템 개발 환경에서 `docker-compose.yml`을 사용하는 상황을 고려하여 실제 설정과 연결해 이해를 돕고자 한다.
21+
22+
자세한 내용은 책의 2장을 참고하자.
23+
24+
25+
---
26+
27+
## 카프카 브로커와 주키퍼 (개념 및 설치)
28+
29+
카프카를 운영하려면 브로커와 주키퍼, 두 가지 핵심 개념을 이해해야 한다.
30+
31+
> 주키퍼 (Zookeeper)
32+
33+
`주키퍼`는 카프카 클러스터의 메타데이터를 관리하는 중앙화된 서비스다.
34+
35+
* 역할: 클러스터의 설정 정보, 컨트롤러 정보, 컨슈머 클라이언트 정보 등을 저장하고 동기화한다.
36+
37+
* 주키퍼는 고가용성을 보장하기 위해 `앙상블`이라 불리는 클러스터 단위로 작동하도록 설계되었다. (`앙상블`이란 여러 대의 주키퍼 서버들이 클러스터 형태로 구성되어 고가용성을 확보한 시스템을 말한다.)
38+
39+
* 앙상블은 과반수 이상의 노드가 정상 작동하면 서비스를 유지할 수 있어 **홀수 개**(3개, 5개 등)의 서버로 구성한다.
40+
41+
* 운영 환경에서는 2대의 노드 장애를 허용하는 **5개** 노드 구성을 고려하는 것을 권장한다.
42+
43+
![](/assets/img/kafka/Kafka-Installation-Sizing-Guide-1.png)
44+
45+
> 카프카 브로커 (Kafka broker)
46+
47+
`카프카 브로커`는 카프카 클러스터를 구성하는 개별 서버이다.
48+
49+
* 역할: 프로듀서로부터 메시지를 받아 토픽 내 파티션에 저장하고, 컨슈머의 요청에 따라 메시지를 전달한다.
50+
51+
### 예제: Docker Compose로 카프카 실행하기
52+
53+
이 글의 예제에서 사용하는 docker-compose.yml 파일 중 카프카와 관련된 부분은 아래와 같다.
54+
55+
이 설정은 하나의 주키퍼와 하나의 카프카 브로커를 실행하는 가장 기본적인 구성이다.
56+
57+
```yaml
58+
# docker-compose.yml
59+
60+
services:
61+
# ... (다른 서비스 생략)
62+
63+
zookeeper:
64+
image: wurstmeister/zookeeper
65+
container_name: zookeeper
66+
ports:
67+
- "2181:2181"
68+
69+
kafka:
70+
image: wurstmeister/kafka:2.12-2.5.0
71+
container_name: kafka
72+
ports:
73+
- "9092:9092"
74+
environment:
75+
KAFKA_BROKER_ID: 1
76+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
77+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
78+
KAFKA_DELETE_TOPIC_ENABLE: "true"
79+
volumes:
80+
- /var/run/docker.sock:/var/run/docker.sock
81+
depends_on:
82+
- zookeeper
83+
```
84+
85+
이제 위 `environment` 에 설정된 값들이 어떤 의미인지, 다음 섹션에서 하나씩 자세히 알아보자.
86+
87+
88+
## 핵심 브로커 매개변수 설정
89+
90+
카프카 브로커를 설정할 때, 클러스터 환경에서 반드시 검토하고 수정해야 하는 핵심 매개변수들이다.
91+
92+
* `broker.id`
93+
* 클러스터 내에서 각 브로커를 식별하는 **고유한 정수 ID**다.
94+
* 모든 브로커는 서로 다른 ID를 가져야 한다.
95+
96+
* `listeners`
97+
* 프로듀서와 컨슈머가 브로커에 접속하기 위한 **네트워크 주소와 포트**를 정의한다.
98+
* `{프로토콜}://{호스트이름}:{포트}` 형식으로 설정한다. (e.g. `PLAINTEXT://localhost:9092`)
99+
100+
* `zookeeper.connect`
101+
* 브로커가 메타데이터를 저장하고 동기화할 **주키퍼 앙상블의 주소**를 지정한다.
102+
* 필자의 `docker-compose.yml`에서는 `KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181`로 설정되어, `zookeeper`라는 서비스 이름으로 주키퍼를 찾는다.
103+
104+
* `log.dirs`
105+
* 메시지 로그 세그먼트가 저장될 **디스크 디렉토리 경로**다.
106+
* 쉼표로 여러 경로를 지정하면, 카프카는 파티션을 분산 저장하여 I/O 부하를 분산시킨다.
107+
108+
* `auto.create.topics.enable`
109+
* 존재하지 않는 토픽에 접근 시 자동으로 토픽을 생성할지 여부를 결정한다.
110+
* 운영 환경에서는 **`false`로 설정** 하여 의도치 않은 토픽 생성을 방지하고 명시적으로 관리하는 것이 좋다.
111+
112+
* `delete.topic.enable`
113+
* 토픽 삭제 기능을 활성화한다.
114+
* 필자의 `docker-compose.yml`에서는 `KAFKA_DELETE_TOPIC_ENABLE: "true"`로 설정되어 있어 토픽 삭제가 가능하다.
115+
* 데이터 보존 정책에 따라 신중하게 설정해야 한다.
116+
117+
118+
## 토픽의 파티션 수 결정 방법
119+
120+
파티션은 카프카의 병렬 처리와 확장성의 핵심이다. 파티션 수를 정하는 것은 전체 시스템 성능에 큰 영향을 미치므로 신중하게 접근해야 한다.
121+
122+
* `num.partitions` 매개변수
123+
124+
* 먼저 브로커 설정에 있는 `num.partitions` 매개변수를 이해해야 한다.
125+
126+
* 이 매개변수는 `auto.create.topics.enable`이 `true`일 때, 토픽이 자동으로 생성될 경우의 **기본 파티션 수**를 결정한다. 기본값은 `1`이다.
127+
128+
* (중요) 카프카 토픽의 파티션 수는 **한번 정해지면 늘릴 수는 있지만, 절대로 줄일 수는 없다.** 따라서 초기에 적절한 파티션 수를 산정하는 것이 매우 중요하다.
129+
130+
* 명확한 기준이 없을 때, 많은 사용자는 부하를 고르게 분산시키기 위해 토픽의 파티션 수를 **클러스터의 브로커 수와 맞추거나 그 배수**로 설정한다.
131+
예를 들어 브로커가 10대라면 파티션을 10개로 생성하여 각 브로커가 파티션 리더를 하나씩 맡게 함으로써 처리량을 최적화할 수 있다.
132+
133+
* 실제로 필자의 docker-compose.yml 파일에는 파티션 수를 설정하는 부분이 없으므로, 이 환경에서 토픽이 자동으로 생성된다면 책의 설명대로 **기본값인 파티션 `1개`** 로 생성된다.
134+
135+
### (중요) 파티션 수는 어떻게 결정해야 하는가?
136+
137+
책에서는 **"파티션은 많아야 하지만, 너무 많아서는 안 된다"** 는 핵심 원칙을 제시한다.
138+
139+
이는 확장성과 시스템 자원 사용량 사이의 균형을 찾아야 함을 의미한다.
140+
141+
> 왜 컨슈머 처리량이 핵심인가?
142+
143+
파티션 수를 결정하는 계산법을 이해하기 전에, 카프카의 아키텍처 특성을 알아야 한다.
144+
145+
일반적으로 메시지를 **쓰는(Produce) 속도는 읽어서 처리하는(Consume) 속도보다 훨씬 빠르다.**
146+
데이터를 쓰는 작업은 비교적 단순하지만, 컨슈머는 메시지를 읽어 데이터베이스에 저장하거나 외부 API를 호출하는 등 복잡한 비즈니스 로직을 수행하기 때문이다.
147+
148+
이 때문에 시스템 전체 처리량의 병목은 프로듀서가 아닌 **컨슈머**에서 발생하는 경우가 대부분이다.
149+
150+
하나의 컨슈머 그룹 내에서, **하나의 파티션은 오직 하나의 컨슈머에 의해서만 처리**될 수 있다.
151+
즉, 특정 파티션에 대한 처리 속도는 해당 파티션에 할당된 단일 컨슈머의 성능에 의해 제한된다.
152+
153+
이것이 바로 **컨슈머의 처리량** 이 전체 시스템의 성능을 결정하는 핵심 요소가 되는 이유다.
154+
155+
따라서 토픽의 전체 목표 처리량을 달성하려면, 여러 컨슈머가 **동시에** 메시지를 처리해야 한다.
156+
이를 위해서는 컨슈머들이 각자 다른 파티션에 연결되어 병렬로 작업할 수 있는 환경, 즉 **충분한 수의 파티션**이 필요하게 된다.
157+
158+
> 처리량 기반 계산법
159+
160+
위 배경을 바탕으로 책에서는 다음과 같은 실용적인 계산법을 제시한다.
161+
162+
**계산법**: `필요 파티션 수 = 토픽의 목표 처리량 (MB/s) / 컨슈머 하나의 예상 처리량 (MB/s)`
163+
164+
* 예시: 토픽에 대해 초당 1GB(1000MB)의 처리량을 목표로 하고, 컨슈머 애플리케이션 하나가 초당 50MB의 데이터를 처리할 수 있다고 가정하자.
165+
166+
* 계산: 1000MB/s / 50MB/s = 20
167+
168+
* 목표 처리량을 달성하기 위해서는 최소 **20개**의 파티션이 필요하다. 이렇게 하면 20개의 컨슈머가 각 파티션에 하나씩 붙어 병렬로 작업함으로써 초당 1GB의 데이터를 소비할 수 있다.
169+
170+
계산법 외에도 다음과 같은 요소들을 종합적으로 고려해야 한다.
171+
172+
* 미래 사용량 예측: 현재가 아닌 **미래의 예측 사용량**을 기준으로 처리량을 계산해야 한다. 특히 메시지 키를 사용해 파티션을 결정하는 경우, 나중에 파티션을 추가하면 키-파티션 매핑이 변경되어 복잡해질 수 있다.
173+
174+
* 브로커 자원: 각 브로커에 할당될 파티션 수와 그에 따른 디스크 공간, 네트워크 대역폭을 고려해야 한다.
175+
176+
* 오버헤드: 파티션은 브로커의 메모리와 CPU 자원을 사용한다. 또한 파티션 수가 너무 많으면 장애 발생 시 리더 선출에 걸리는 시간이 길어지는 등 관리 오버헤드가 증가한다.
177+
178+
* 만약 상세한 처리량 추정이 어렵다면, 경험적으로 파티션의 일일 데이터 증가량이 **6GB 미만**으로 유지하는 것이 좋다. 일단 작은 크기로 시작해서 나중에 필요할 때 확장하는 것이 처음부터 크게 시작하는 것보다 쉽다.
179+
180+
181+
## 카프카 클러스터 설정 및 브로커 개수 결정
182+
183+
단일 브로커는 개발용으로 적합하지만, 실제 서비스에서는 여러 브로커를 묶어 클러스터로 구성해야 부하 분산과 데이터 안정성을 확보할 수 있다.
184+
185+
아래 그림2-2와 같이 여러 대의 브로커를 하나의 클러스터로 구성하면 부하를 다수의 서버로 확장하는 이점이 있다.
186+
187+
또한, 복제를 사용함으로써 단일 시스템 장애에서 발생할 수 있는 데이터 유실을 방지할 수 있다.
188+
189+
![](/assets/img/kafka/Kafka-Installation-Sizing-Guide-2.png)
190+
191+
(책에 따르면, 여기서는 기본적인 카프카 클러스터를 설정하는 단계에 초점을 맞추고, 데이터의 복제와 지속성은 7장에서 다룬다고 나와있다.)
192+
193+
### 브로커 개수를 결정하는 기준
194+
195+
카프카 클러스터의 적절한 크기는 다음 요소들을 종합적으로 고려해 결정한다.
196+
197+
* 디스크 용량
198+
199+
* 클러스터에 저장해야 할 총 데이터량과 `복제 팩터`(Replication Factor)를 고려해야 한다. (`복제 팩터`란 하나의 파티션 데이터를 몇 개의 다른 브로커에 복제하여 저장할지를 나타내는 값으로, 데이터의 안정성과 가용성을 높인다)
200+
201+
* 최소 브로커 수 = `(총 데이터 저장량 * 복제 팩터)` / `브로커당 사용 가능 디스크 용량`
202+
203+
* 예시: 10TB의 데이터를 저장해야 하고 복제 팩터가 3이라면 총 30TB의 공간이 필요하다. 브로커 하나가 5TB를 저장할 수 있다면, 최소 `6대`의 브로커가 필요하다.
204+
205+
* CPU 및 네트워크 용량
206+
207+
* **피크 타임**의 트래픽을 감당할 수 있는지 확인해야 한다.
208+
209+
* 단일 브로커의 네트워크 사용량이 80%에 육박한다면, 컨슈머 증가나 데이터 복제 트래픽을 감당하기 위해 브로커를 추가해야 한다.
210+
211+
* 파티션 및 레플리카 수
212+
213+
* 브로커가 관리하는 파티션(레플리카 포함) 수가 너무 많아지면 성능이 저하된다.
214+
215+
* 권장 사항: 브로커당 파티션 레플리카 개수를 **14,000개 이하**, 클러스터당 **100만 개 이하**로 유지하는 것을 권장한다.
216+
217+
### 브로커 설정
218+
219+
다수의 카프카 브로커가 하나의 클러스터를 이루려면 두 가지를 설정해야 한다.
220+
221+
1. 모든 브로커가 동일한 `zookeeper.connect` 설정에 **동일한 주키퍼 앙상블 주소**를 지정한다.
222+
223+
2. 모든 브로커의 `broker.id`가 **서로 다른 고유한 값**을 갖도록 설정한다.
224+
225+
226+
## References
227+
228+
* [카프카 핵심 가이드](https://product.kyobobook.co.kr/detail/S000201464167?utm_source=google&utm_medium=cpc&utm_campaign=googleSearch&gad_source=1)
207 KB
Loading
433 KB
Loading

0 commit comments

Comments
 (0)