Kafka Streams에 대한 실무 스터디 가이드

데이터 엔지니어 · 데이터 사이언티스트를 위한 핵심 요약

stream-processing kafka library

목차

  1. 1. Kafka Streams란?
  2. 2. 왜 써야 하는가?
  3. 3. 핵심 개념
  4. 4. 퀵스타트
  5. 5. 핵심 설정 레퍼런스
  6. 6. 실무 팁 & 트러블슈팅
  7. 7. 참고 자료

1. Kafka Streams란?

Kafka Streams는 Apache Kafka에 내장된 오픈소스 클라이언트 라이브러리로, Kafka 토픽을 입력/출력으로 사용하는 실시간 스트림 처리 애플리케이션을 Java/Scala/Kotlin으로 쉽게 빌드할 수 있게 해줍니다. 별도의 클러스터 관리 없이 Kafka 브로커에 의존해 가볍고 확장 가능하며, exactly-once semantics를 지원합니다.

데이터 엔지니어와 데이터 사이언티스트에게 적합한 이유는 실시간 데이터 파이프라인, ETL, ML 피처 엔지니어링 등에서 Kafka 생태계 내에서 일관되게 처리할 수 있기 때문입니다.

🔄 실시간 스트림 처리

밀리세컨드 단위 저지연으로 무한 스트림을 처리. map, filter, join 등 DSL로 간단 구현.

📊 상태 기반 처리

KTable과 State Store로 집계, 윈도잉, 세션화 지원. changelog 토픽으로 fault-tolerance.

⚖️ Exactly-Once 보장

트랜잭션과 idempotent producer로 데이터 중복 없이 처리. 생산성 극대화.

2. 왜 써야 하는가?

Kafka Streams는 기존 배치/스트림 도구 대비 가볍고 Kafka-native라 운영 부담이 적습니다. 아래 표에서 주요 대안과 비교:

기능 Kafka Streams Apache Flink Spark Streaming Storm
지연(Latency) Sub-second Sub-second 1-10초 Sub-second
운영 복잡도 낮음 (Kafka만) 높음 (별도 클러스터) 중간 (YARN) 높음
Exactly-Once 예 예 예 (Structured Streaming) 부분
상태 관리 RockDB/Changelog State Backend Checkpoint 외부
언어 Java/Kotlin/Scala Java/Scala/Python Scala/Python/Java Java
비용 최저 (라이브러리) 높음 중간 중간

실무에서 Kafka Streams는 마이크로서비스 내 스트림 처리에 최적화되어 있습니다.

3. 핵심 개념

Kafka Streams의 핵심은 Topology로, 소스(Source) → 프로세서(Processor) → 싱크(Sink)로 구성됩니다. KStream(무상태 스트림), KTable(업데이트 스트림), GlobalKTable(전역 테이블) 등을 DSL로 조합합니다.

Kafka Producer ───┐ │ ▼ ┌─────────────┐ ┌─────────────────┐ ┌─────────────┐ │ Source KStream│──▶│ Stream Processor │──▶│ Sink KStream│ │ (input-topic) │ │ (map/filter/join) │ │(output-topic)│ └─────────────┘ └─▶│ KTable Store │◄──┘ │ (stateful agg) │ └─────────────────┘ │ ▼ Kafka Consumer

위 아키텍처에서 Streams 앱은 Kafka 파티션만큼 인스턴스 스케일아웃됩니다.

4. 퀵스타트

Python 우선으로 kafka-python 라이브러리를 사용한 기본 상호작용 예제와 함께, Kafka Streams Java 예제를 병행합니다. Kafka 브로커는 Docker로 빠르게 시작.

Step 1: Kafka 설치 (CLI)

# Docker로 Kafka + Zookeeper 실행 (포트 9092)
docker run -d --name kafka -p 9092:9092 \
  -e KAFKA_ZOOKEEPER_CONNECT=localhost:2181 \
  -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
  confluentinc/cp-kafka:latest

# 토픽 생성
docker exec kafka kafka-topics --create --topic input-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
docker exec kafka kafka-topics --create --topic output-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1

Step 2: Python으로 데이터 생산/소비 테스트

# pip install kafka-python
from kafka import KafkaProducer, KafkaConsumer
import json

# Producer
producer = KafkaProducer(bootstrap_servers='localhost:9092', value_serializer=lambda v: json.dumps(v).encode('utf-8'))
for i in range(10):
    producer.send('input-topic', {'word': f'test-{i}'})
producer.flush()

# Consumer
consumer = KafkaConsumer('input-topic', bootstrap_servers='localhost:9092', value_deserializer=lambda m: json.loads(m.value.decode('utf-8')))
for msg in consumer:
    print(msg.value)

Step 3: Kafka Streams Java 프로젝트 설정 (Maven)

<!-- pom.xml -->
<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka-streams</artifactId>
  <version>3.6.0</version>
</dependency>

Step 4: 첫 Streams 앱 (WordCount)

import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
import java.util.Arrays;
import java.time.Duration;

Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");

StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> textLines = builder.stream("input-topic");

KTable<String, Long> wordCounts = textLines
  .flatMapValues(line -> Arrays.asList(line.toLowerCase().split(" ")))
  .groupBy((key, word) -> word)
  .count();

wordCounts.toStream().to("output-topic", Produced.with(Serdes.String(), Serdes.Long()));

KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
streams.cleanUp(); // Graceful shutdown

실행: mvn compile exec:java -Dexec.mainClass="WordCountApp". Python으로 input-topic에 데이터 넣으면 output-topic에서 집계 확인.

5. 핵심 설정 레퍼런스

설정 기본값 설명 권장 실무값
application.id - 앱 식별자 (State Store 그룹화) unique-per-app
cache.max.bytes.buffering 10MB 버퍼 크기 (메모리 사용 ↑) 50MB ~ 100MB
commit.interval.ms 30s (AT_LEAST_ONCE), 1s (EXACTLY_ONCE) 오프셋 커밋 주기 100ms (저지연)
num.stream.threads 1 프로세서 스레드 수 CPU 코어 수
max.in.flight.requests.per.connection 5 비동기 요청 (Throughput ↑) 8 (Exactly-Once 시 1)
rocksdb.config.setter - State Store 튜닝 block_cache=4GB
processing.guarantee at_least_once Exactly-once 활성 exactly_once

6. 실무 팁 & 트러블슈팅

💡 팁: State Store 모니터링

JMX Metrics 활성화 (kafka-streams-metrics.properties). Prometheus + Grafana로 changelog lag, restore 시간 추적. Standby tasks=1로 HA 강화.

⚡ 팁: Backpressure 처리

num.stream.threads 증가, buffer 증가. Python consumer에서 max_poll_records=500으로 조절.

🚨 자주 겪는 문제: State Store Corruption

원인: 디스크 풀/네트워크 파티션. 해결: application.server 설정 후 kafka-streams-application-reset.sh --application-id wordcount-app --input-topics input-topic 실행 후 재시작.

🚨 문제: Rebalance 지연

원인: 파티션 리밸런싱. 해결: session.timeout.ms=30s, max.poll.interval.ms=5m. Topology optimization으로 join 최소화.

🔧 Python-Kafka Streams 연동

Python으로 피처 생성 후 Kafka 토픽으로 넣고 Streams로 집계. confluent-kafka로 고성능 consumer 사용.

7. 참고 자료