Kafka·Spark·Flink으로 설계하는 실시간 ETL 파이프라인

Apache Kafka, Spark Structured Streaming, Apache Flink를 조합해 실시간 ETL 파이프라인의 정확성, 상태 관리, 운영 기준을 정리한다.

2026-08-14 · 최초 발행 2025-10-14

이벤트가 계속 들어오는 환경에서 ETL을 설계하는 법

스트리밍 처리는 지속 유입되는 이벤트를 수집하고, 지연을 최소화하면서 처리·적재하는 방식이다. 배치 처리와 달리 연속 계산과 상태 기반 처리가 중심이 된다.

실시간 ETL에서는 CDC로 원천 데이터를 캡처한 뒤 정제·집계를 거쳐 저지연 분석 저장소인 OLAP, DWH, Feature Store에 적재한다. 이 과정에서 Schema Registry와 Avro, Protobuf, JSON 같은 직렬화 포맷을 사용해 스키마 진화를 관리한다.

시간의 기준도 설계에 영향을 준다. 이벤트 시간(Event-time)을 기준으로 처리하면 워터마크를 통해 늦게 도착한 이벤트를 다룰 수 있다. 처리 시간(Processing-time)은 운영 측면에서 단순하지만 정확성에는 제약이 있다.

정확성 보장은 At-most-once, At-least-once, Exactly-once로 구분한다. Kafka 트랜잭션과 idempotent producer, Flink 체크포인트와 2단계 커밋, Spark의 소스·싱크별 트랜잭션 지원을 조합해 필요한 수준을 구현한다.

Kafka는 이벤트를 보관하고 흘려보내는 계층이다

Apache Kafka는 로그 기반의 퍼브/섭(pub/sub) 플랫폼이다. 파티션, 리플리케이션, 오프셋을 바탕으로 고가용성과 수평 확장을 제공한다.

Kafka는 idempotent producer, 트랜잭션, consumer group rebalancing을 제공하며, 소스와 싱크의 조합에 따라 Exactly-once 처리의 기반이 될 수 있다. 스트림 처리 엔진 앞에서 버퍼링, 리플레이, Fan-out을 담당하는 계층으로 두는 구성이 일반적이다.

Spark Structured Streaming은 마이크로배치 중심이며 SQL 및 배치 처리와 통합된 API를 제공한다. 지연과 일관성 사이에서 실용적인 균형을 잡기 좋다. 반면 Apache Flink는 이벤트 구동형 아키텍처를 사용하며, 낮은 지연에서 정확한 상태 처리를 지원한다. 체크포인트와 두 단계 커밋을 바탕으로 Exactly-once 보장을 제공한다.

상태를 다루는 방식도 다르다. Flink는 RocksDB 등 외부 상태백엔드를 활용할 수 있고, Spark는 State Store를 관리한다. 체크포인트와 세이브포인트는 장애 이후에도 일관된 시점에서 복구할 수 있게 한다. 워터마크, 트리거, 윈도우를 조합하면 지연 이벤트 처리와 중복 제거를 구현할 수 있다.

이벤트는 정제·격리·적재 경로를 나눠 흐른다

출력Stream ProcessingKafka Cluster입력정제/스키마 검증 성공검증 실패/역직렬화 오류세션/슬라이딩 윈도우 집계2PC SinkApp/Service LogsDB CDC (Debezium)IoT/Telemetry(Topic: raw)(Topic: clean)(Topic: dlq)Spark Structured Streaming(micro-batch, watermark)Flink Job(native streaming, 2PC)(Data Lake/Delta/Iceberg)(OLAP: ClickHouse/Druid)(Feature Store)(Search: Elasticsearch)

원천 이벤트는 Kafka에서 버퍼링되고 파티셔닝된 뒤 스트림 엔진으로 전달된다. 처리 엔진은 데이터를 정제하거나 집계하며, 검증에 실패한 이벤트는 DLQ로 분기한다. 정상 이벤트는 OLAP, 데이터레이크, 서치 계층으로 적재할 수 있다.

역직렬화 실패, 스키마 불일치, 지연 이벤트 처리 초과는 DLQ 격리 대상으로 둘 수 있다. 재처리할 때는 오프셋을 재지정하거나 DLQ의 이벤트를 정상 흐름으로 다시 주입한다. Kafka 트랜잭션과 idempotent producer, Flink 2PC 싱크, Spark 체크포인트는 재시작 과정의 일관성을 확보하는 수단이다.

역할과 운영 특성의 차이

항목 Kafka Spark Structured Streaming Apache Flink
역할 분산 로그/메시징 마이크로배치 중심 스트림 처리 네이티브 이벤트 스트림 처리
지연 수 ms 단위 브로커 레벨 보통 수백 ms~수 초 (배치 간격에 비례) 수십 ms~수백 ms (연속 처리)
처리량/확장성 파티션 수 비례 선형 확장 스케일아웃 우수, 배치·SQL 생태계 연계 스케일아웃 우수, 낮은 지연에서 고처리량
상태 관리 비상태(컨슈머 상태 외부 관리) State Store, 체크포인트 RocksDB/메모리 상태백엔드, 체크포인트/저장점
정확성 보장 Idempotent/Transactional Producer 지원 소스/싱크 조합에 따라 Exactly-once 달성 가능 Exactly-once(체크포인트+2PC) 일급 지원
운영 편의 토픽/브로커 운영 난이도 중 Spark 생태계 친화, SQL 중심 편의성 러닝 커브 존재, 강력한 연산자/상태 모델
주 사용 시나리오 버퍼링, 리플레이, Fan-out SQL 기반 정제/집계/ETL 실시간 CEP, 세밀한 윈도우, 저지연 파이프라인

최신 릴리스별 기능·성능 차이는 지속 변화한다. 프로젝트를 구성할 때는 버전 호환성과 폐지·변경된 API를 확인해야 한다.

데이터가 늦게 도착하거나 잘못 들어올 때의 운영 기준

스키마 계층에서는 Confluent 또는 Apicurio Schema Registry를 통해 호환성 레벨을 강제할 수 있다. Avro와 Protobuf는 바이너리 효율을 높이는 선택지이며, Data Catalog와 Lineage를 연결하면 규정 준수와 충돌을 줄이는 데 도움이 된다.

운영 지표는 Kafka JMX, Flink/Spark Metrics, Lag Exporter, OpenTelemetry 기반 트레이싱으로 수집한다. Kafka lag, 처리 지연, 드롭·오류율, 체크포인트 시간과 크기, 백프레셔 신호가 핵심 관측 대상이다.

TLS 암호화, SASL/OAuth 인증, 토픽 ACL, Vault/KMS 기반 비밀정보 관리를 보안 구성에 포함한다. Kubernetes와 cgroup을 통한 리소스 격리는 노이즈 간섭을 낮춘다.

토픽의 키는 사용자, 세션, 디바이스처럼 집계 단위를 기준으로 정한다. 용량은 목표 처리량, 메시지 크기, retention/compaction 정책을 바탕으로 산정한다. 프로듀서에서는 acks=all, enable.idempotence=true, linger.ms/batch.size를 조정하고, 컨슈머에서는 max.poll.interval.ms/records, fetch.min.bytes, 재시도 정책을 설정한다. read_committed 격리 수준은 트랜잭션 메시지의 일관성을 확보한다.

Spark에서는 HDFS 또는 S3 같은 고가용성 체크포인트 디렉터리와 Watermark+DropDuplicates를 사용해 중복을 방지할 수 있다. Flink에서는 체크포인트 주기와 모드(Exactly-once), RocksDB 상태백엔드, 2PC 싱크를 선택한다. Kafka나 JDBC TwoPhaseCommitSinkFunction은 2PC 싱크의 예다.

Schema Registry를 필수화하고 Backward 또는 Full 호환성 모드를 지정할 수 있다. 역직렬화 실패는 DLQ로 보내고, 필수 필드·범위·참조 무결성 같은 데이터 품질 규칙 위반은 메타데이터에 기록한다.

압축(lz4/zstd), 배치 크기, 상태 TTL, 온디맨드 스케일링은 비용 제어의 대상이다. 전송·저장 암호화, 최소권한 ACL, 네트워크 세분화, 비밀정보 외부화도 함께 설계한다. PII 마스킹·토큰화와 삭제권 대응을 위한 소프트/하드 삭제 절차 역시 운영 범위에 포함된다.

서비스별로 달라지는 처리 경로

전자상거래에서는 장바구니 이벤트를 바탕으로 세션을 집계하고 추천 피드를 갱신할 수 있다. Kafka에서 Flink 윈도우 집계를 거쳐 Druid 또는 ClickHouse에 적재하면 대시보드에 1초대 반영이 가능하다.

금융 이상거래 탐지에서는 CDC로 거래 스트림을 수집한 뒤 피처 엔지니어링과 온라인 모델 추론을 연결한다. 저지연 경보 발송과 트랜잭션 블로킹 플로우를 연계하는 경로도 구성할 수 있다.

IoT 환경은 수백만 디바이스 이벤트를 수집하고, 스키마 검증·압축 저장·SLA 기반 알람을 처리한다. 지연 데이터를 보정하고 위치 기반 CEP 규칙 엔진을 적용하는 방식도 사용된다.

로그 및 보안 분석에서는 애플리케이션·네트워크 로그를 정규화해 SIEM 또는 ES에 적재한다. DLQ를 노이즈 분리 경로로 두고 규칙 기반 재처리 파이프라인을 운영한다.

Spark Structured Streaming으로 Kafka 이벤트를 Delta Lake에 적재하기

전제: Spark 3.4+, Python 3.9+, Kafka 3.x, Delta 2.x, S3/HDFS 체크포인트 디렉토리

# pyspark-shell 또는 spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.4.0,io.delta:delta-core_2.12:2.4.0
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, from_json, to_timestamp, window
from pyspark.sql.types import StructType, StructField, StringType, LongType

spark = (SparkSession.builder
         .appName("kafka-to-delta-realtime-etl")
         .getOrCreate())

schema = StructType([
    StructField("user_id", StringType()),
    StructField("event", StringType()),
    StructField("ts", LongType())
])

raw = (spark.readStream
       .format("kafka")
       .option("kafka.bootstrap.servers", "kafka:9092")
       .option("subscribe", "raw")
       .option("startingOffsets", "latest")
       .load())

parsed = (raw.selectExpr("CAST(value AS STRING) AS v")
          .select(from_json(col("v"), schema).alias("j"))
          .select("j.*")
          .withColumn("event_time", to_timestamp((col("ts")/1000).cast("timestamp"))))

# 지연 데이터 허용 10분, 사용자별 1분 윈도우 집계
agg = (parsed
       .withWatermark("event_time", "10 minutes")
       .groupBy(window(col("event_time"), "1 minute"), col("event"))
       .count())

query = (agg.writeStream
         .format("delta")
         .option("checkpointLocation", "s3://bucket/chk/kafka-to-delta")
         .option("path", "s3://bucket/data/events_agg")
         .outputMode("append")
         .trigger(processingTime="10 seconds")  # 마이크로배치 간격
         .start())

query.awaitTermination()

체크포인트 경로는 고가용성 저장소를 사용한다. 스키마 진화가 필요하면 Delta의 mergeSchema 옵션을 검토한다. Kafka 싱크로 재전송할 때 트랜잭션과 정확성 보장 범위는 버전·커넥터에 따라 다르므로 최신 문서를 확인해야 한다.

전제: Flink 1.17+, Kafka 3.x, Avro/Registry 사용 가능

-- Flink SQL Client
CREATE TABLE src_raw (
  user_id STRING,
  event STRING,
  ts BIGINT,
  `event_time` AS TO_TIMESTAMP_LTZ(ts, 3),
  WATERMARK FOR event_time AS event_time - INTERVAL '10' MINUTE
) WITH (
  'connector'='kafka',
  'topic'='raw',
  'properties.bootstrap.servers'='kafka:9092',
  'format'='json',
  'scan.startup.mode'='latest-offset'
);

CREATE TABLE agg_minute (
  window_start TIMESTAMP_LTZ(3),
  window_end   TIMESTAMP_LTZ(3),
  event        STRING,
  cnt          BIGINT,
  PRIMARY KEY (window_start, event) NOT ENFORCED
) WITH (
  'connector'='upsert-kafka',
  'topic'='agg_minute',
  'properties.bootstrap.servers'='kafka:9092',
  'key.format'='json',
  'value.format'='json'
);

INSERT INTO agg_minute
SELECT
  WINDOW_START(w) as window_start,
  WINDOW_END(w) as window_end,
  event,
  COUNT(*) as cnt
FROM TABLE(
  TUMBLE(TABLE src_raw, DESCRIPTOR(event_time), INTERVAL '1' MINUTE)
) GROUP BY event, w;

Upsert-Kafka 싱크를 사용하면 키 기준의 최신 집계를 유지할 수 있다. 체크포인트를 활성화하면 Exactly-once 보장을 제공한다.

SLA와 상태 규모가 엔진 선택을 결정한다

Spark의 마이크로배치 모델은 단순성과 생태계 측면에서 강점이 있고, Flink는 저지연과 정밀한 처리에 강점이 있다. 어느 쪽이 적합한지는 SLA와 팀 역량을 기준으로 판단한다.

대규모 상태를 관리할 때 Flink와 RocksDB는 안정성이 우수하지만 스토리지와 IO 비용이 증가한다. 상태 TTL과 컴팩션 전략이 필요하다. 스키마 검증을 엄격하게 적용하면 데이터 품질은 높아지지만 DLQ가 증가할 수 있으므로 단계적 롤아웃과 샘플링 검증을 병행한다.

기능이 풍부한 플랫폼일수록 러닝 커브도 올라간다. 표준화된 템플릿, IaC, 관측성 기본 세트를 운영에 포함해야 한다.

체크포인트 기반 처리에서는 대시보드 신선도를 T+분에서 T+초로 전환하고, 이상탐지 평균 응답을 90% 단축하며, 재처리 시간을 50% 절감할 수 있다. 데이터 신뢰도 향상, 부서 간 데이터 공유 촉진, 개발·운영 사이클의 민첩성 증대도 함께 기대할 수 있다.

스트리밍 처리Apache KafkaApache FlinkSpark Structured Streaming실시간 ETL