본문으로 건너뛰기
yutils

data streaming 은 어떻게 동작할까?

batch vs streaming, Kafka / Kinesis / Pub-Sub, windowing (tumbling / sliding / session), late event 의 watermark, exactly-once semantics — 왜 어려운가, Flink / Kafka Streams 의 stream-table duality.

약 10분 읽기

매 transaction 이 즉시 처리되어 실시간 분석. 사용자 행동 → 5초 후 대시보드. 이게 streaming. batch 의 hourly / nightly 와 다른 세계. Kafka / Kinesis / Flink — 이 가이드는 streaming 의 핵심 개념 (windowing, watermark, exactly-once) 을 정리한다.

Batch vs Streaming

Batch:
  - 큰 chunk 데이터를 한꺼번에 처리
  - "오늘 자정의 매출 합산" 매일 1회
  - 도구: cron + Spark / Hive / dbt

Streaming:
  - 매 record 가 도착하면 즉시 처리
  - "지난 5분의 활성 user 수" 매 초 갱신
  - 도구: Kafka / Kinesis (transport) + Flink / Kafka Streams / Spark Streaming (compute)

Trade-off:
  - Batch: 단순, 정확한 결과, 큰 dataset 효율
  - Streaming: 빠른 응답, 운영 복잡, watermark / late event 문제

→ 두 가지 다 필요 (Lambda Architecture). modern 은 Kappa (streaming
   만으로 둘 다 처리) 로 단순화 추세.

Streaming 의 핵심 component

Transport (메시지 broker):
- Kafka — partition + consumer group + persistent log
- Kinesis (AWS) — Kafka 의 managed 변형
- Google Pub/Sub — push 모델
- RabbitMQ — queue 중심 (AMQP)
- Pulsar — multi-tenant, geo-replication

Compute (stream processing):
- Kafka Streams — Java/Scala 라이브러리, Kafka 내장
- Flink — 가장 powerful, exactly-once, low-latency
- Spark Streaming — Spark 의 micro-batch
- Beam — 추상 layer (Flink / Dataflow / Spark 위)

Sink (저장 / external):
- Warehouse (BigQuery, Snowflake)
- OLAP store (ClickHouse, Druid)
- 다른 Kafka topic (chain)
- DB (CDC 의 역방향)

Windowing — 시간 단위 grouping

stream 의 "지난 5분 합계" 같은 query 가 어떻게?

3 windowing 패턴:

1. Tumbling (고정 크기, 겹침 없음)
   |---1min---|---1min---|---1min---|
   각 window 가 1 분, 겹침 0.
   "매 분의 요청 수" — 단순.

2. Sliding (겹침 있음)
   |--5min--|
       |--5min--|
           |--5min--|
   slide=1min, window=5min → 매 분 갱신, 지난 5분 평균.

3. Session (사용자 활동 기반)
   user 의 마지막 event 후 30 초 idle 면 session 종료.
   user 별 동적 window 크기.

선택:
- Tumbling: 단순 metric (per-minute counter)
- Sliding: smooth metric (rolling avg)
- Session: user behavior (web session, game session)

Watermark — Late Event 처리

문제: event 가 late 도착하면?

09:00:00 — A
09:00:01 — B
09:00:02 — C
09:00:30 — Z (실은 09:00:01 의 event, network 지연)

"09:00:00 ~ 09:00:10 window" 의 합계 계산:
- 09:00:10 시점에 close? Z 가 빠짐
- 무한 wait? close 불가

Watermark 의 해결:
- "현재 시점에 이 시간 이전의 모든 event 는 도착했다고 가정"
- watermark = max(event_time) - allowedLateness
- watermark 가 window 끝을 넘으면 → window close + emit

예: allowedLateness = 30s
   09:00:10 의 watermark = max(09:00:02) - 30s = 08:59:32
   → window 09:00:00 ~ 09:00:10 아직 close 안 함 (watermark < 09:00:10)
   09:00:40 의 watermark = 09:00:10
   → window close, Z 도 포함

Late event 처리:
- discard (default)
- side output (별도 sink, late report)
- accumulate + retract (이전 결과 정정)

Exactly-Once Semantics — 왜 어려운가

distributed system 에서 "정확히 한 번" 처리 = 가장 어려운 문제.

가능한 보장:
- at-most-once: 보내고 잊기. event 손실 가능.
- at-least-once: ack 까지 retry. 중복 가능.
- exactly-once: 손실 X + 중복 X.

문제: producer / broker / consumer / sink 4 layer 의 모든 fail 점을 cover.

Kafka 의 exactly-once (Kafka 0.11+):
- Idempotent producer (producer ID + sequence number)
- Transactional API (multiple partition write atomic)
- Consumer 가 transaction commit log 기반 read

Flink 의 exactly-once:
- Checkpoint (분산 snapshot) + state recovery
- 2-phase commit sink (Kafka, file system, JDBC)

함정:
- side effect (외부 API 호출) 의 exactly-once 는 보장 어려움
  → idempotent API 또는 dedup key 사용
- exactly-once 의 throughput 대가 — 매 batch 에 transaction overhead

Stream-Table Duality

Kafka Streams / Flink 의 핵심 통찰:

Stream = sequence of immutable events
Table = current state (key → value)

서로 변환:
- Stream → Table: aggregate events into state
  (예: user_signed_up events → user_table)
- Table → Stream: emit changes as events
  (예: user_table UPDATE → user_changed events)

→ 같은 데이터를 두 view 로:
  table view = "현재 상태"
  stream view = "변경 history"

응용:
- KTable + KStream의 join (last value 와 새 event)
- materialized view (table) + change log (stream)
- event sourcing 의 자연스러운 확장

실제 use case

  • real-time analytics — 사용자 행동 → 5초 후 대시보드
  • fraud detection — 거래 발생 즉시 anomaly 검사
  • recommendation — 사용자 click → 다음 추천에 반영
  • IoT — sensor 수만 개의 실시간 monitoring
  • microservice 간 event-driven — service A 의 이벤트가 B, C, D 의 trigger
  • CDC downstream — DB 변경 → 검색 인덱스 / 캐시 자동 update

흔한 함정

  • watermark 없이 window — late event 처리 불완전. allowedLateness 명시.
  • infinite state — group-by user_id 의 state 가 모든 user 보유 → memory 폭발. TTL 또는 state backend (RocksDB).
  • exactly-once 가정 + side effect — sink 가 외부 API 면 보장 깨질 수 있음. idempotent.
  • partition skew — 한 user 의 event 가 90% → 한 consumer 만 부하. 더 fine-grained key.
  • streaming 으로 batch job 대체 — 단순 nightly aggregate 면 batch 가 더 단순. streaming 은 latency 가 핵심.

마무리

Streaming = batch 의 generalization — record-by-record 처리. windowing 으로 시간 단위 query, watermark 로 late event 처리, exactly-once 의 어려움. Kafka + Flink 가 modern 표준.

실용 — real-time 이 진짜 필요할 때만. nightly batch 로 충분하면 그게 단순. "실시간" 요구가 명확하면 → Kafka (transport) + Kafka Streams (소규모) 또는 Flink (큰 규모) + 결과는 ClickHouse / Druid / OLAP.

가이드 목록으로