스트리밍 플랫폼 설계: event time, processing time, late data
스트리밍에서 "시간"이 왜 어려운가
배치 처리에서 데이터는 파일에 고정되어 있다. 어제치 로그를 오늘 읽든 내일 읽든 결과는 같다. 스트리밍에서는 다르다. 데이터가 끊임없이 흘러들어오고, 같은 쿼리를 언제 실행하느냐에 따라 결과가 달라질 수 있다.
더 복잡한 문제는 데이터가 발생한 순서대로 도착하지 않는다는 것이다. 모바일 앱이 오프라인 상태였다가 나중에 이벤트를 한꺼번에 올리거나, 네트워크 지연으로 일부 패킷이 늦게 도착하거나, 분산 서버의 클록이 제각각이거나 — 현실의 스트림에는 이런 상황이 일상적으로 발생한다.
이 문제를 다루려면 먼저 "시간"의 의미를 정확히 구분해야 한다.
1. 세 가지 시간 개념
스트리밍 시스템에는 구분해야 할 시간 개념이 세 가지 있다.
디바이스·서버가 기록한 타임스탬프
페이로드에 포함됨
event_at 필드Kafka offset의 타임스탬프
중간 버퍼링 지연 포함 가능
timestamp실행 노드의 시스템 클록
지연·재처리 시 달라짐
늦게 온 이벤트를 올바른 시간대에 집계
비즈니스 의미와 일치
늦게 도착한 데이터 처리 복잡
클록 스큐 대응 필요
워터마크 불필요
실시간 모니터링에 적합
장애·지연 시 집계 왜곡
비즈니스 의미와 불일치 가능
언제 어떤 시간을 쓰는가
- Processing Time: 실시간 알림, 시스템 모니터링, 정확도보다 지연이 중요한 대시보드
- Event Time: 비즈니스 분석, 정산, 사용자 행동 분석 — 대부분의 데이터 파이프라인은 여기에 해당
- Ingestion Time: Event time 필드가 없거나 신뢰할 수 없을 때의 차선책
2. 윈도우(Window): 무한한 스트림을 유한하게 자르는 방법
스트리밍 집계는 무한한 스트림의 일부 구간에서 계산한다. 그 구간을 윈도우라 한다.
(1분마다 슬라이드)
윈도우를 닫으려면 "이 윈도우의 이벤트가 충분히 도착했다"는 신호가 필요하다. 바로 워터마크가 이 역할을 한다.
3. 워터마크(Watermark): 시간 진행을 측정하는 신호
워터마크란
워터마크는 스트리밍 엔진이 event time의 진행을 추적하기 위해 사용하는 메타데이터다. Watermark(t)는 "event time이 시각 t에 도달했고, 이제부터 t 이하의 타임스탬프를 가진 새 이벤트는 오지 않는다(또는 늦은 것으로 간주한다)"는 선언이다.
스트리밍 엔진은 이 선언을 기준으로 윈도우를 닫고 결과를 내보낸다.
워터마크 생성 전략
고정 지연(bounded-out-of-orderness) 워터마크가 가장 일반적이다. 관측된 최대 event time에서 일정 시간을 뺀 값을 워터마크로 사용한다.
Watermark(t) = max_seen_event_time − max_out_of_orderness예를 들어 max_out_of_orderness = 2분이면, 가장 최신 이벤트가 10:10이라도 워터마크는 10:08이다. 이는 "10:08 이전의 이벤트는 모두 도착했다고 가정한다"는 의미다.
// Flink Java API
WatermarkStrategy
.<MyEvent>forBoundedOutOfOrderness(Duration.ofMinutes(2))
.withTimestampAssigner((event, ts) -> event.getEventTime())모노토닉(단조 증가) 워터마크는 이벤트가 순서대로 도착한다고 가정할 때 쓴다. 지연 없이 최대 event time을 워터마크로 사용하므로 가장 낮은 지연을 제공한다. 실제로는 거의 쓰이지 않는다.
워터마크가 처리 그래프를 따라 전파되는 방법
Flink 같은 분산 스트리밍 엔진에서 워터마크는 단순히 레코드처럼 스트림을 따라 흐른다. 여러 파티션/소스가 합쳐지는 union 연산에서는 가장 낮은 워터마크가 선택된다. 어느 한 소스가 느리면 전체 워터마크 진행이 막힌다. 이를 idle source 문제라 하며, 비활성 소스를 일시 제외하는 withIdleness(Duration) 설정으로 완화할 수 있다.
4. 늦게 도착한 데이터(Late Data) 처리
워터마크가 윈도우 종료 시점을 지나쳤더라도 해당 윈도우에 속하는 이벤트가 뒤늦게 도착할 수 있다. 이를 late data라 한다.
적합한 상황:
정확도 요건이 낮은 실시간 대시보드,
재처리 파이프라인이 따로 있을 때.
해당 윈도우를 열어두고 late event를 반영.
윈도우 결과가 갱신될 때마다 다시 방출.
적합한 상황:
수 분 이내 지연이 잦은 스트림.
별도 스트림(side output)으로 라우팅.
배치로 재처리하거나 별도 집계에 활용.
적합한 상황:
늦은 데이터도 버리지 않아야 할 때.
Flink에서 세 전략을 조합하면 다음과 같다:
DataStream<WindowedResult> result = events
.keyBy(e -> e.userId)
.window(TumblingEventTimeWindows.of(Time.minutes(10)))
.allowedLateness(Time.minutes(5)) // ② Allowed Lateness: 5분
.sideOutputLateData(lateTag) // ③ Side Output
.aggregate(new MyAggFunction());
DataStream<MyEvent> lateStream = result.getSideOutput(lateTag); // late event 스트림Apache Spark Structured Streaming은 withWatermark("event_time", "2 minutes") 뒤에 .groupBy(window(...)) 를 연결하는 방식으로 동일 개념을 표현한다. Kafka Streams는 grace(Duration.ofMinutes(2))로 허용 지연을 설정한다.
5. 완결성(Completeness) vs 지연(Latency) 트레이드오프
스트리밍 시스템 설계에서 핵심 딜레마는 "얼마나 기다릴 것인가"다.
| 전략 | 워터마크 지연 | 윈도우 결과 | 적합한 워크로드 |
|---|---|---|---|
| 공격적(낮은 지연) | 짧음 (예: 10초) | 빠르지만 late data 누락 多 | 실시간 경고, A/B 모니터링 |
| 균형 | 중간 (예: 2분) | 적당한 지연, 대부분 포함 | 일반 분석 파이프라인 |
| 보수적(높은 완결성) | 길음 (예: 30분) | 느리지만 거의 완전한 데이터 | 정산, 청구, SLA 집계 |
워터마크 지연은 늦게 도착하는 이벤트의 p99 지연 을 기준으로 설정하는 것이 좋다. Kafka consumer lag, 네트워크 지연, 배치 플러시 주기를 측정해서 결정한다.
실무 팁: 모바일 앱 이벤트는 오프라인 재연결로 최대 수 시간이 늦을 수 있다. 이 경우 event time 집계는 최소 1시간 이상의 allowed lateness가 필요하거나, side output → 배치 보정으로 설계해야 한다.
6. 소스가 여러 개일 때: 워터마크 정렬 문제
여러 Kafka 파티션, 여러 소스 시스템을 합칠 때 워터마크 진행이 "가장 느린 소스"에 묶이는 현상이 생긴다.
WM: 10:09
WM: 10:03 ⚠️
WM: 10:08
(최솟값 선택)
해결:
withIdleness(Duration.ofSeconds(30)) 로 유휴 소스를 일시 제외.이 문제를 피하려면:
- 소스가 일시적으로 비어 있는 경우를 idle로 처리하도록 설정
- 파티션 수가 많을수록 일부 파티션이 오래 비어 있을 확률이 높으므로, idleness 타임아웃을 충분히 짧게 설정
- 소스 시스템별 클록 드리프트를 모니터링
7. 설계 시 체크리스트
스트리밍 파이프라인을 설계할 때 시간 관련 결정사항을 명확히 해두어야 나중에 집계 오류로 혼란이 생기지 않는다.
- [ ] Event time 필드가 신뢰할 수 있는가? 소스 클록 동기화 상태, null/음수 값 가능성 확인
- [ ] 최대 out-of-orderness는 얼마인가? p99 지연을 측정해서 워터마크 간격 설정
- [ ] late data를 버릴 것인가, 재처리할 것인가? allowed lateness 또는 side output 결정
- [ ] 윈도우 유형이 비즈니스 로직과 맞는가? tumbling/sliding/session 선택 이유 문서화
- [ ] idle 소스가 발생할 수 있는가? withIdleness 설정 여부
- [ ] 결과 재처리 시 멱등성이 보장되는가? 같은 윈도우가 여러 번 방출될 때 싱크(sink)가 처리 가능한가
References
- Apache Flink, "Timely Stream Processing," https://nightlies.apache.org/flink/flink-docs-stable/docs/concepts/time/
- Apache Flink, "Streaming Analytics," https://nightlies.apache.org/flink/flink-docs-stable/docs/learn-flink/streaming_analytics/
- Apache Flink, "Generating Watermarks," https://nightlies.apache.org/flink/flink-docs-stable/docs/dev/datastream/event-time/generating_watermarks/
- Confluent, "Time and Watermarks in Confluent Cloud for Apache Flink," https://docs.confluent.io/cloud/current/flink/concepts/timely-stream-processing.html
- Microsoft Learn, "Understand time handling in Azure Stream Analytics," https://learn.microsoft.com/en-us/azure/stream-analytics/stream-analytics-time-handling
- Milvus, "How do watermarking techniques work in stream processing?," https://milvus.io/ai-quick-reference/how-do-watermarking-techniques-work-in-stream-processing
- Confluent Developer, "Apache Flink® 101 - Event Time and Watermarks," https://developer.confluent.io/courses/apache-flink/timely-stream-processing/