|
| 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 | + |
| 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 | + |
| 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) |
0 commit comments