Spark Streaming이란?
Spark Streaming은 Apache Spark의 실시간 스트림 처리 모듈로, 대용량 데이터 스트림을 실시간으로 처리할 수 있게 해주는 프레임워크입니다.
마이크로 배치 처리 (Micro-batch Processing)
동작 원리: Spark Streaming은 연속적인 데이터 스트림을 매우 짧은 시간 간격(보통 0.5초~수 초)으로 나누어 각각을 독립적인 배치로 처리합니다. 예를 들어, 1초 간격으로 설정하면 매 1초마다 그 시간 동안 수집된 모든 데이터를 하나의 RDD로 만들어 처리합니다.
레이턴시 특성: 진정한 실시간(millisecond 단위)은 아니지만 near real-time 처리가 가능합니다. 배치 간격이 짧을수록 지연 시간은 줄어들지만, 처리 오버헤드는 증가합니다.
처리 보장: 각 마이크로 배치는 완전히 처리되거나 실패하는 all-or-nothing 방식으로 동작하여 정확히 한 번(exactly-once) 처리를 보장할 수 있습니다.
DStream (Discretized Stream)
구조: DStream은 시간순으로 정렬된 RDD들의 시퀀스입니다. 각 RDD는 특정 시간 구간의 데이터를 담고 있으며, RDD의 불변성을 그대로 상속받습니다.
변환 연산: map, filter, reduceByKey 등 RDD에서 사용하는 모든 변환을 DStream에서도 사용할 수 있습니다. 변환 시 각 시간 구간의 RDD에 동일한 변환이 적용됩니다.
상태 관리: updateStateByKey, mapWithState 등을 통해 시간에 걸쳐 상태를 유지할 수 있습니다. 예를 들어, 누적 카운트나 이동 평균 등을 계산할 수 있습니다.
Structured Streaming 핵심 개념과 동작 원리
무한 테이블 모델 (Unbounded Table Model)
Structured Streaming의 핵심 아이디어는 스트림 데이터를 끝없이 증가하는 테이블로 보는 것입니다. 새로운 데이터가 도착할 때마다 테이블에 새로운 행이 추가된다고 생각하면 됩니다.
# 스트림을 테이블처럼 처리
df = spark.readStream.format("kafka")...
word_counts = df.groupBy("word").count() # 일반 DataFrame 처리와 동일
이 모델의 장점은 배치 처리와 스트림 처리의 API가 거의 동일하다는 것입니다. 배치 처리에서 사용하던 SQL이나 DataFrame 연산을 그대로 스트림에 적용할 수 있습니다.
증분 처리 (Incremental Processing)
전체 테이블을 매번 다시 계산하는 것은 비효율적입니다. Structured Streaming은 새로 추가된 데이터만 처리하여 결과를 업데이트합니다.
# 매번 전체 계산하지 않고, 새로운 데이터만 처리
user_counts = df.groupBy("userId").count()
# 내부적으로 새로운 사용자 이벤트만 처리하여 카운트 업데이트
이를 통해 대용량 스트림에서도 일정한 성능을 유지할 수 있습니다.
핵심 특징 상세 설명
1. 이벤트 시간 처리 (Event Time Processing)
처리 시간 vs 이벤트 시간의 차이점을 이해하는 것이 중요합니다.
- 처리 시간: 데이터가 시스템에 도착한 시간
- 이벤트 시간: 실제 이벤트가 발생한 시간
# 이벤트 시간 기반 윈도우 처리
df.withWatermark("event_time", "10 minutes") \
.groupBy(window("event_time", "5 minutes")) \
.count()
Watermark는 "늦게 도착하는 데이터를 얼마나 기다릴 것인가"를 정의합니다. 위 예시에서는 10분까지 늦은 데이터를 허용합니다.
2. 상태 관리 (State Management)
집계 연산에서는 이전 결과를 기억해야 합니다. Structured Streaming은 이를 자동으로 관리합니다.
# 사용자별 누적 구매 금액 (상태 유지 필요)
user_totals = df.groupBy("user_id").agg(sum("amount"))
상태 정보는 체크포인트에 저장되어 장애 시 복구가 가능합니다.
3. 출력 모드 (Output Modes)
결과를 어떻게 출력할지 결정하는 중요한 개념입니다.
Complete Mode: 전체 결과 테이블을 매번 출력
- 집계 결과가 작을 때 사용
- 대시보드나 실시간 리포트에 적합
query = word_counts.writeStream.outputMode("complete").format("console").start()
Append Mode: 새로 추가된 행만 출력
- 필터링된 데이터나 변환된 데이터에 적합
- 파일 시스템에 저장할 때 주로 사용
filtered_data = df.filter(col("amount") > 1000)
query = filtered_data.writeStream.outputMode("append").format("parquet").start()
Update Mode: 변경된 행만 출력
- 집계 결과에서 변경된 부분만 확인하고 싶을 때
실제 활용 시나리오
실시간 추천 시스템
사용자의 클릭 스트림을 분석하여 실시간으로 관심사를 파악하는 시스템입니다.
# 사용자별 최근 관심 카테고리 분석
user_interests = click_stream \
.withWatermark("timestamp", "1 hour") \
.groupBy("user_id", window("timestamp", "30 minutes")) \
.agg(collect_list("category").alias("categories"))
설명: 30분 윈도우로 사용자의 클릭 데이터를 수집하고, 1시간까지 늦은 데이터를 허용합니다. 이 정보를 바탕으로 실시간 추천을 제공할 수 있습니다.
이상 거래 탐지
금융 거래에서 비정상적인 패턴을 실시간으로 감지하는 시스템입니다.
# 사용자별 거래 패턴 분석
transaction_patterns = transactions \
.groupBy("user_id") \
.agg(
avg("amount").alias("avg_amount"),
count("*").alias("transaction_count")
)
# 임계값을 넘는 거래 탐지
anomalies = transactions.join(transaction_patterns, "user_id") \
.filter(col("amount") > col("avg_amount") * 5)
설명: 각 사용자의 평균 거래 금액을 계산하고, 이를 크게 벗어나는 거래를 실시간으로 탐지합니다.
DStream vs Structured Streaming 비교
DStream의 특징
API 스타일:
- RDD 기반의 low-level API 사용
- 함수형 프로그래밍 스타일
- 복잡한 스트림 처리 로직을 세밀하게 제어 가능
성능:
- 각 마이크로 배치마다 작업 스케줄링 오버헤드 발생
- 배치 크기가 작을 때 비효율적일 수 있음
- 수동적인 최적화 필요
상태 관리:
DStream에서는 상태를 유지하기 위해 복잡한 함수를 작성해야 합니다.
# DStream에서 상태 관리 예시
def update_running_count(new_values, running_count):
"""상태 업데이트 함수"""
if running_count is None:
running_count = 0
current_count = sum(new_values)
return current_count + running_count
# 체크포인트 설정 (필수)
ssc.checkpoint("/tmp/checkpoint")
# 상태 유지 집계
running_counts = pairs.updateStateByKey(update_running_count)
running_counts.pprint()
ssc.start()
ssc.awaitTermination()
에러 처리: 명시적인 에러 핸들링과 복구 로직 구현 필요
Structured Streaming의 특징
API 스타일:
- DataFrame/Dataset 기반의 high-level API
- SQL과 같은 선언적 방식
- 스키마 기반의 강타입 처리
성능:
- Catalyst 옵티마이저와 Tungsten 실행 엔진 활용
- 연속 처리 모드(Continuous Processing) 지원으로 더 낮은 레이턴시 달성
- 자동 최적화 수행
상태 관리:
상태 관리가 훨씬 간단하고 직관적입니다.
# Structured Streaming에서 상태 관리 예시
running_counts = df.select(explode(split(col("value"), " ")).alias("word")) \
.groupBy("word") \
.agg(count("*").alias("total"))
# 자동 체크포인트 관리
query = running_counts.writeStream \
.outputMode("complete") \
.format("console") \
.option("checkpointLocation", "/tmp/checkpoint") \
.start()
스키마 진화: 스트림 스키마 변경에 대한 자동 처리 지원
주요 차이점 정리
1. 개발 복잡성
- DStream: 더 많은 boilerplate 코드 필요, 세밀한 제어 가능
- Structured Streaming: 간결한 코드, 선언적 접근
2. 최적화
- DStream: 수동 최적화 필요
- Structured Streaming: 자동 최적화 (predicate pushdown, column pruning 등)
3. 이벤트 시간 처리
- DStream: 복잡한 수동 구현 필요
- Structured Streaming: 내장된 watermark와 늦은 데이터 처리 지원
4. 출력 모드
- DStream: 제한적인 출력 옵션
- Structured Streaming: Complete, Append, Update 모드 지원
5. 모니터링
- DStream: 기본적인 메트릭
- Structured Streaming: 더 풍부한 모니터링과 디버깅 정보
현재 Apache Spark에서는 새로운 스트림 처리 애플리케이션 개발 시 Structured Streaming 사용을 권장하고 있으며, DStream은 레거시 지원 목적으로 유지되고 있습니다.
'Data Platform > Spark' 카테고리의 다른 글
[Apache Spark] 아키텍처 및 클러스터 (0) | 2025.06.20 |
---|---|
[Apache Spark] RDD(Resilient Distributed Dataset) (3) | 2025.06.16 |
[Apache Spark] 아파치 스파크란? (1) | 2025.06.16 |