데이터 엔지니어 · AI 엔지니어를 위한 핵심 요약
Apache Kafka는 분산 이벤트 스트리밍 플랫폼이다. LinkedIn에서 처음 개발되었고, 현재 Apache Software Foundation이 관리한다. 단순 메시지 큐가 아니라 실시간 데이터 파이프라인의 중추 신경 역할을 한다.
이벤트 스트림을 발행(publish)하고 구독(subscribe). 외부 시스템에서 연속적으로 데이터를 import/export할 수 있다.
이벤트 스트림을 내구성 있게(durably) 원하는 기간만큼 저장. 기본 7일이지만 무제한 보관도 가능.
이벤트가 발생하는 즉시 또는 소급하여(retrospectively) 스트림을 처리할 수 있다.
| 항목 | 전통 MQ (RabbitMQ 등) | Apache Kafka |
|---|---|---|
| 메시지 수명 | 소비 후 삭제 | 보존 기간 동안 영구 보관 (재소비 가능) |
| 처리량 | 수천 msg/s | 수백만 msg/s (초당 수 GB) |
| 통신 모델 | Point-to-Point 중심 | Pub/Sub + Consumer Group (둘 다) |
| 확장성 | 수직 확장 중심 | 파티션 기반 수평 확장 |
| 내구성 | 메모리 중심 | 디스크 기반 + 복제 (fault-tolerant) |
| 순서 보장 | 큐 단위 | 파티션 단위 순서 보장 |
Kafka는 "메시지를 보내고 끝"이 아니라, 이벤트 로그를 중심으로 시스템 전체를 연결하는 데이터 허브로 쓴다. 데이터 엔지니어에게 Kafka는 ETL/ELT 파이프라인의 중추이고, AI 엔지니어에게는 실시간 피처 전달 및 모델 서빙의 백본이다.
"무언가 일어났다"는 사실의 기록. key, value, timestamp, 그리고 선택적 헤더로 구성된다.
이벤트가 흐르는 논리적 채널. 파일시스템의 폴더, DB의 테이블과 유사한 개념. 토픽 수에 제한 없음.
토픽을 물리적으로 분할한 단위. 병렬 처리의 기본 단위이자 저장의 기본 단위. 각 이벤트는 파티션 내에서 고유한 offset을 갖는다.
Kafka 서버 1대 = 1 Broker. 클러스터는 보통 최소 3대로 구성 (1대 유지보수 + 1대 장애 허용).
이벤트를 토픽에 쓰는 클라이언트. 어떤 파티션에 보낼지 결정. Consumer와 완전히 디커플링되어 있다.
토픽을 구독하여 이벤트를 읽는 클라이언트. Consumer Group 내에서 각 파티션은 1개의 Consumer에만 할당되어 병렬 소비한다.
각 파티션은 여러 브로커에 복제된다. 하나가 Leader(읽기/쓰기 담당), 나머지는 Follower(복제만). Leader 장애 시 Follower 중 하나가 새 Leader로 승격된다.
replication.factor=3 → 데이터 3벌 보관 (운영 권장값)| API | 역할 | 사용 시나리오 |
|---|---|---|
| Producer API | 이벤트를 토픽에 발행 | 앱 → Kafka 데이터 전송 |
| Consumer API | 토픽에서 이벤트 구독 | Kafka → 앱 데이터 소비 |
| Streams API | 스트림 처리 애플리케이션 | 실시간 변환·집계·윈도잉 |
| Connect API | 외부 시스템 연동 커넥터 | DB↔Kafka, S3↔Kafka 등 |
| Admin API | 토픽·브로커 관리 | 토픽 생성/삭제, 설정 변경 |
Kafka 4.0부터 ZooKeeper 지원이 완전히 제거되었다. 3.9가 ZooKeeper를 지원하는 마지막 버전이며, 2025년 11월부터 ZooKeeper 기반 클러스터는 공식 지원이 종료된다. 지금 새로 시작한다면 무조건 KRaft 모드를 사용해야 한다.
KRaft(Kafka Raft)는 ZooKeeper를 대체하는 Kafka 자체 내장 메타데이터 관리 프로토콜이다. Raft 합의 알고리즘의 이벤트 기반 변형을 사용하여, 별도 클러스터(ZooKeeper) 없이 Kafka 브로커만으로 메타데이터를 관리한다.
# 1. 클러스터 UUID 생성
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
# 2. 로그 디렉토리 포맷
bin/kafka-storage.sh format \
-t $KAFKA_CLUSTER_ID \
-c config/kraft/server.properties
# 3. Kafka 서버 시작 (KRaft 모드)
bin/kafka-server-start.sh config/kraft/server.properties
# config/kraft/server.properties 주요 항목
# 노드 역할: controller, broker, 또는 둘 다 (개발용: controller,broker)
process.roles=broker,controller
# 노드 ID (클러스터 내 고유)
node.id=1
# 컨트롤러 쿼럼 투표자 목록
controller.quorum.voters=1@localhost:9093
# 리스너 설정
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
controller.listener.names=CONTROLLER
# 로그 디렉토리
log.dirs=/tmp/kraft-combined-logs
Java 17 이상이 설치되어 있어야 한다. java -version으로 확인.
# Kafka 최신 버전 다운로드 (https://kafka.apache.org/downloads)
wget https://downloads.apache.org/kafka/4.0.0/kafka_2.13-4.0.0.tgz
tar -xzf kafka_2.13-4.0.0.tgz
cd kafka_2.13-4.0.0
# 클러스터 ID 생성 & 스토리지 포맷
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID \
-c config/kraft/server.properties
# Kafka 서버 시작
bin/kafka-server-start.sh config/kraft/server.properties
# 토픽 생성 (파티션 3개)
bin/kafka-topics.sh --create \
--topic my-first-topic \
--bootstrap-server localhost:9092 \
--partitions 3 \
--replication-factor 1
# 토픽 목록 확인
bin/kafka-topics.sh --list --bootstrap-server localhost:9092
# 토픽 상세 정보
bin/kafka-topics.sh --describe \
--topic my-first-topic \
--bootstrap-server localhost:9092
# 터미널 1: Producer (메시지 입력)
bin/kafka-console-producer.sh \
--topic my-first-topic \
--bootstrap-server localhost:9092
> Hello Kafka!
> This is my first event.
# 터미널 2: Consumer (메시지 읽기)
bin/kafka-console-consumer.sh \
--topic my-first-topic \
--from-beginning \
--bootstrap-server localhost:9092
--from-beginning 플래그는 토픽의 처음부터 모든 메시지를 읽는다. 빼면 Consumer 시작 이후의 메시지만 읽는다.
| 라이브러리 | 성능 | 적합한 상황 |
|---|---|---|
confluent-kafka | 최고 (librdkafka 기반 C 래퍼) | 고부하 프로덕션 환경 |
kafka-python | 보통 (순수 Python) | 중소규모 워크로드, 빠른 프로토타이핑 |
quix-streams | 좋음 | pandas 스타일의 Pythonic 스트림 처리 |
# confluent-kafka (권장)
pip install confluent-kafka
# 또는 kafka-python
pip install kafka-python
from confluent_kafka import Producer
import json
# 설정
conf = {
'bootstrap.servers': 'localhost:9092',
'client.id': 'my-python-producer',
# 성능 튜닝
'linger.ms': 10, # 배치 대기 시간
'batch.size': 16384, # 배치 크기 (bytes)
'compression.type': 'lz4' # 압축
}
producer = Producer(conf)
# 전송 콜백
def delivery_callback(err, msg):
if err:
print(f'전송 실패: {err}')
else:
print(f'전송 성공: {msg.topic()} [{msg.partition()}] @ {msg.offset()}')
# 메시지 전송
for i in range(10):
event = {'user_id': i, 'action': 'page_view', 'page': f'/products/{i}'}
producer.produce(
topic='user-events',
key=str(i), # 같은 key → 같은 파티션
value=json.dumps(event),
callback=delivery_callback
)
# 버퍼에 남은 메시지 전송 완료 대기
producer.flush()
from confluent_kafka import Consumer, KafkaError
import json
conf = {
'bootstrap.servers': 'localhost:9092',
'group.id': 'my-consumer-group',
'auto.offset.reset': 'earliest', # 처음부터 읽기
'enable.auto.commit': True, # 자동 오프셋 커밋
'auto.commit.interval.ms': 5000
}
consumer = Consumer(conf)
consumer.subscribe(['user-events'])
try:
while True:
msg = consumer.poll(timeout=1.0) # 1초 대기
if msg is None:
continue
if msg.error():
if msg.error().code() == KafkaError._PARTITION_EOF:
print(f'파티션 끝 도달: {msg.topic()} [{msg.partition()}]')
else:
print(f'에러: {msg.error()}')
continue
# 메시지 처리
event = json.loads(msg.value().decode('utf-8'))
print(f'수신: partition={msg.partition()}, '
f'offset={msg.offset()}, event={event}')
except KeyboardInterrupt:
pass
finally:
consumer.close() # Consumer Group 탈퇴 & 오프셋 커밋
KafkaProducer는 스레드 안전(thread-safe)하지만, KafkaConsumer는 그렇지 않다. Consumer를 멀티스레드로 사용하려면 thread-local로 만들거나, multiprocessing을 권장한다.
| 설정 | 기본값 | 설명 |
|---|---|---|
num.partitions | 1 | 토픽 자동 생성 시 기본 파티션 수 |
default.replication.factor | 1 | 기본 복제 팩터 (운영: 3 권장) |
log.retention.hours | 168 (7일) | 메시지 보존 기간 |
log.segment.bytes | 1073741824 | 로그 세그먼트 파일 크기 (1GB) |
min.insync.replicas | 1 | 최소 ISR 수 (운영: 2 권장) |
auto.create.topics.enable | true | 운영에서는 false 권장 |
| 설정 | 기본값 | 설명 |
|---|---|---|
acks | all | 0=무확인, 1=리더만, all=ISR 전체 |
retries | 2147483647 | 재시도 횟수 (Kafka 3.x+ 기본값 무한) |
linger.ms | 0 | 배치 대기 시간 (높이면 처리량↑, 지연↑) |
batch.size | 16384 | 배치 크기 (bytes) |
compression.type | none | lz4 또는 zstd 권장 |
enable.idempotence | true | 중복 방지 (exactly-once 기반) |
| 설정 | 기본값 | 설명 |
|---|---|---|
group.id | - | Consumer Group 식별자 (필수) |
auto.offset.reset | latest | earliest=처음부터, latest=최신부터 |
enable.auto.commit | true | 오프셋 자동 커밋 여부 |
max.poll.records | 500 | poll() 당 최대 레코드 수 |
session.timeout.ms | 45000 | Consumer 생존 감지 타임아웃 |
fetch.min.bytes | 1 | fetch 최소 바이트 (높이면 처리량↑) |
| 수준 | 설정 | 특징 |
|---|---|---|
| At-Most-Once | acks=0 |
메시지 손실 가능. 처리량 최대. 로그/메트릭 등 손실 허용 가능한 경우. |
| At-Least-Once | acks=all + 재시도 |
메시지 중복 가능. 대부분의 운영 환경 기본값. Consumer 측 멱등성 필요. |
| Exactly-Once | enable.idempotence=true + 트랜잭션 API |
중복·손실 없음. 결제 등 정확성 필수 시나리오. 약간의 성능 오버헤드. |
# Producer: 안전한 기본값
acks=all
enable.idempotence=true
max.in.flight.requests.per.connection=5
retries=2147483647
# Broker: 데이터 안전성
min.insync.replicas=2
unclean.leader.election.enable=false
# Consumer: 수동 커밋으로 정확한 처리
enable.auto.commit=false
# → 메시지 처리 완료 후 consumer.commit() 호출
대부분의 데이터 파이프라인은 At-Least-Once + Consumer 측 멱등성(Idempotency)이 현실적 최선이다. Exactly-Once는 Kafka Streams 내부 처리에서 가장 쉽게 달성할 수 있다.
대부분의 AI 실패는 모델 정확도 문제가 아니라 데이터 파이프라인 병목에서 온다. 배치 파이프라인으로 실시간 예측을 서빙하려는 것이 근본 원인이다. Kafka는 이 문제를 해결하는 실시간 데이터 백본이다.
| 시나리오 | Kafka 역할 | ML 모델 |
|---|---|---|
| 실시간 사기 탐지 | 거래 이벤트 스트림 수집, 사용자 프로필 데이터와 enrichment | 트랜잭션 스코어링 모델이 실시간으로 승인/차단 결정 |
| 고객 이탈 예측 | 로그인/활동/취소 이벤트 스트림 | 세션 데이터 집계 → 이탈 확률 예측 → CRM 자동 리텐션 트리거 |
| 동적 가격 책정 | 경쟁사 가격, 브라우징 행동, 재고 데이터 스트림 | 강화학습 모델이 실시간 가격 조정 추천 |
| 실시간 추천 | 사용자 클릭/조회/구매 이벤트 | 실시간 피처 업데이트 → 추천 모델 서빙 → 결과를 Kafka로 전달 |
Kafka-ML은 Kafka의 고처리량·저지연 특성을 활용하여 ML/AI 파이프라인을 관리하는 오픈소스 프레임워크다. TensorFlow와 PyTorch를 지원하며, 연속적인 데이터 스트림에서 동작하는 ML 시스템 구축에 특화되어 있다.
Kafka + Flink 조합으로 온라인 ML 학습과 모델 드리프트 감지를 구현할 수 있다. 연속적인 모델 훈련과 실시간 추론을 통해 항상 최신 상태의 고정확도 예측을 유지할 수 있다.
from confluent_kafka import Consumer, Producer
import json, pickle
# 학습된 모델 로드
model = pickle.load(open('fraud_model.pkl', 'rb'))
consumer = Consumer({
'bootstrap.servers': 'localhost:9092',
'group.id': 'ml-inference',
'auto.offset.reset': 'latest'
})
consumer.subscribe(['transactions'])
producer = Producer({'bootstrap.servers': 'localhost:9092'})
while True:
msg = consumer.poll(1.0)
if msg is None or msg.error():
continue
# 1. 이벤트 파싱
event = json.loads(msg.value().decode('utf-8'))
# 2. 피처 추출
features = extract_features(event) # 사용자 정의 함수
# 3. 모델 추론
prediction = model.predict([features])[0]
score = model.predict_proba([features])[0][1]
# 4. 결과를 다른 토픽에 발행
result = {
'transaction_id': event['id'],
'is_fraud': bool(prediction),
'fraud_score': float(score)
}
producer.produce(
'fraud-results',
key=str(event['id']),
value=json.dumps(result)
)
producer.flush()
| 항목 | 권장값 | 이유 |
|---|---|---|
| Broker 수 | 최소 3대 | 1대 유지보수 + 1대 장애 허용 |
| replication.factor | 3 | 데이터 안전성 |
| min.insync.replicas | 2 | 쓰기 확인 최소 수 |
| acks | all | 데이터 손실 방지 |
| 파티션 수 | Consumer 수 × 2~3배 | 병렬성 확보 |
| auto.create.topics | false | 의도치 않은 토픽 생성 방지 |
| unclean.leader.election | false | ISR 밖의 리더 선출 방지 |
| compression.type | lz4 또는 zstd | 네트워크/디스크 절약 |
max.poll.records 조정, 처리 로직 최적화session.timeout.ms가 너무 짧거나, max.poll.interval.ms 초과session.timeout.ms 늘리기 (45s → 60s), 무거운 처리는 별도 스레드로 분리enable.idempotence=true + max.in.flight.requests.per.connection=5log.retention.hours 줄이기, log.retention.bytes 설정, 불필요한 토픽 정리| 메트릭 | 위치 | 알람 기준 |
|---|---|---|
| Consumer Lag | Consumer Group | 지속적으로 증가하면 위험 |
| Under-Replicated Partitions | Broker | 0이 아니면 즉시 확인 |
| ISR Shrink/Expand Rate | Broker | 빈번하면 네트워크/디스크 문제 |
| Request Latency (p99) | Broker | 급증 시 브로커 부하 확인 |
| Disk Usage | Broker | 80% 초과 시 경고 |
Kafka 메트릭은 JMX로 노출된다. Prometheus + Grafana 조합이 가장 널리 사용되며, Confluent Control Center(유료) 또는 AKHQ(오픈소스 UI)도 좋은 선택이다.