본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 31. 10:47

실시간 인제스션(Ingestion) 및 정확히 한 번(Exactly-Once) 의미론을 갖춘 확장 가능한 이벤트 기반 데이터 레이크 설계

요약

스트리밍과 배치 데이터를 통합하여 실시간 쿼리가 가능한 확장 가능한 데이터 레이크 아키텍처 설계 방법을 다룹니다. 정확히 한 번(Exactly-once) 처리 보장과 결함 허용 전략을 포함한 실무적인 데이터 파이프라인 구축 가이드를 제공합니다.

핵심 포인트

  • 배치 및 스트리밍 소스를 지원하는 통합 데이터 레이크 구축
  • 중복 방지를 위한 Exactly-once 처리 의미론 보장
  • 인제스션, 프로세싱, 스토리지, 서빙 레이어의 계층적 설계
  • Apache Iceberg 등을 활용한 스키마 진화 및 데이터 거버넌스

실시간 인제스션(Ingestion) 및 정확히 한 번(Exactly-Once) 의미론을 갖춘 확장 가능한 이벤트 기반 데이터 레이크 설계

실시간 인제스션(Ingestion) 및 정확히 한 번(Exactly-Once) 의미론을 갖춘 확장 가능한 이벤트 기반 데이터 레이크 설계

이 튜토리얼에서는 스트리밍(Streaming) 데이터와 배치(Batch) 데이터를 인제스션(Ingestion)하고, 실시간 쿼리(Query) 기능을 제공하며, 정확히 한 번(Exactly-once) 처리 의미론(Semantics)을 보장하는 확장 가능한 데이터 레이크(Data Lake) 아키텍처를 설계하는 방법을 배웁니다. 핵심 구성 요소, 데이터 모델링 선택 사항, 결함 허용(Fault-tolerance) 전략, 그리고 실제 프로젝트에 적용할 수 있는 실무 코드 예제를 살펴보겠습니다.

개요 및 목표

  • 배치(Batch) 및 스트리밍(Streaming) 소스를 지원하는 통합 데이터 레이크(Data Lake) 구축.
  • 낮은 지연 시간(Low-latency)의 인제스션(Ingestion) 및 근실시간(Near-real-time) 분석 달성.
  • 중복 데이터를 방지하기 위한 정확히 한 번(Exactly-once) 처리 보장.
  • 인제스션(Ingestion), 처리(Processing), 저장(Storage), 카탈로그(Catalog), 서빙(Serving) 레이어의 명확한 관심사 분리 제공.
  • 스키마 진화(Schema evolution), 데이터 품질(Data quality), 관찰 가능성(Observability)을 위한 실무 패턴 포함.

아키텍처 개요

아키텍처 개요

  • 인제스션 레이어 (Ingestion layer)
    • 스트리밍 (Streaming): 실시간 데이터 스트림을 위한 Apache Kafka 또는 그에 상응하는 메시지 버스 (Message bus).
    • 배치 (Batch): 오브젝트 스토리지(예: S3 호환 스토리지, GCS, Azure Blob)로의 주기적인 파일 드롭.
  • 프로세싱 레이어 (Processing layer)
    • 스트림 프로세싱 (Stream processing): 변환(Transformations), 풍부화(Enrichments) 및 집계(Aggregation)를 적용하기 위한 결함 허용 (Fault-tolerant) 스트림 프로세서 (예: Apache Flink, ksqlDB 또는 Spark Structured Streaming).
    • 배치 프로세싱 (Batch processing): 대규모 과거 데이터셋을 처리하고 업데이트된 로직으로 재처리하기 위한 Spark 작업 또는 유사 작업.
  • 스토리지 레이어 (Storage layer)
    • Raw 존 (Raw zone): 파티션된 구조 내의 불변(Immutable) 및 추가 전용(Append-only) 데이터 레이크 파일 (예: Parquet/ORC).
    • Cleansed 존 (Cleansed zone): 표준화된 메타데이터와 함께 정제되고 규격화된 스키마 (Conformed schema).
    • Curated 존 (Curated zone): 분석 및 머신러닝 (ML)을 위해 준비된 집계된 기능 중심 (Feature-oriented) 데이터셋.
  • 메타데이터 및 거버넌스 (Metadata and governance)
    • 데이터 카탈로그 (Data catalog): 스키마, 파티션 및 데이터 리니지 (Data lineage)를 추적하기 위한 Hive Metastore, Apache Iceberg 또는 AWS Glue Data Catalog.
    • 스키마 레지스트리 (Schema registry): Avro/Schema Registry 또는 Iceberg의 스키마 진화 (Schema evolution) 기능.
  • 서빙/쿼리 레이어 (Serving/query layer)
    • 데이터 가상화 (Data virtualization) 또는 쿼리 엔진 (예: Presto/Trino, Apache Spark SQL) 또는 최적화된 BI 커넥터.
    • 빠른 대시보드를 위한 구체화된 뷰 (Materialized views) 또는 증분 집계 (Incremental aggregates).
  • 오케스트레이션 및 신뢰성 (Orchestration and reliability)
    • 오케스트레이터 (Orchestrator): 적절한 의존성 그래프와 함께 배치 작업을 관리하기 위한 Airflow, Dagster 또는 Prefect.
    • 정확히 한 번 (Exactly-once) 보장: 멱등적 싱크 (Idempotent sinks), 지원되는 경우 트랜잭션 쓰기 (예: 트랜잭션 쓰기가 가능한 Iceberg 테이블), 그리고 스트리밍에서의 세심한 오프셋 (Offset) 관리.
  • 관찰 가능성 (Observability)
    • 메트릭 (Metrics): 프로세서로부터의 Prometheus 호환 메트릭, 인제스션 지연 (Ingestion lag), 실패율.
    • 트레이스 (Traces): 엔드 투 엔드 가시성을 위한 분산 트레이싱 (Distributed tracing) (예: Jaeger/Tempo를 사용하는 OpenTelemetry).
    • 로깅 (Logging): 구조화된 로그, 에러 대시보드 및 알림을 포함한 중앙 집중식 로깅.

주요 설계 결정 (Key design decisions)

  1. 데이터 포맷 및 스키마 (Data formats and schemas)
  • 분석 엔진과의 효율성 및 호환성을 위해 컬럼형 포맷 (Columnar formats, 예: Parquet/ORC)을 사용합니다.
  • 호환성을 강제하기 위해 스키마 레지스트리 (Schema registry) 또는 카탈로그 (Catalog)에 스키마를 저장합니다.
  • 다음과 같은 특징을 가진 정형화되고 진화 가능한 스키마 (Canonical, evolving schema)를 선호합니다:
    • 파티션 프루닝 (Partition pruning)을 위한 파티션 키 (예: event_date).
    • 하위 호환성 (Backward compatibility)을 위한 선택적이고 Null 허용이 가능한 필드.
  • 스키마 진화 (Schema evolution) 전략을 구현합니다:
    • 중단 없는 추가 전용 변경 사항 (기본값이 있는 새 필드 추가)은 안전합니다.
    • 필드 삭제 또는 이름 변경은 피해야 하며, 필요할 경우 마이그레이션 계획이 필요합니다.
  1. 정확히 한 번 (Exactly-once) 의미론
  • 스트리밍 인제스션 (Streaming ingestion) 시, 가능한 경우 멱등성 프로듀서 (Idempotent producers)와 트랜잭션 싱크 (Transactional sinks)를 사용합니다.
  • Flink/Spark에서 오퍼레이터 (Operators) 및 싱크 (Sinks) 전반에 걸쳐 체크포인팅 (Checkpointing)과 정확히 한 번 (Exactly-once) 보장을 활용합니다.
  • 파일 기반 싱크 (File-based sinks)의 경우:
    • 먼저 스테이징 영역 (Staging area)에 기록한 다음, 대상 데이터셋에 원자적 (Atomically)으로 커밋합니다 (예: 트랜잭션 테이블 또는 Apache Iceberg의 테이블 스냅샷과 같은 커밋 프로토콜을 통해).
  • 다운스트림 시스템 (Downstream systems)의 경우:
    • 적절한 경우 중복 제거 키 (Deduplication keys)와 업서트 (Upsert) 의미론을 사용합니다.
    • 중복 데이터의 재처리를 방지하기 위해 압축된 내구성이 있는 중복 제거 저장소 (예: 압축된 key+timestamp 저장소)를 유지합니다.
  1. 데이터 품질 및 리니지 (Data quality and lineage)
  • 인제스션 중에 엄격한 스키마 및 도메인 제약 조건에 따라 데이터를 검증합니다.
  • 각 레코드에 리니지 메타데이터 (소스, 타임스탬프, 스키마 버전)를 포함하거나 테이블 수준의 어노테이션 (Annotations)으로 포함합니다.
  • 데이터 품질 문제 (예: 누락된 필드, 지연 데이터, 스키마 드리프트 (Schema drift))에 대한 이상 탐지 및 알림을 구현합니다.
  1. 운영 고려 사항 (Operational considerations)
  • 파티셔닝 전략: 일관된 명명 규칙을 가진 시간 기반 파티션 (예: 일별/시간별).
  • 백프레셔 (Backpressure) 처리: 프로듀서 및 프로세서에서 버퍼링 (Buffering) 및 백오프 (Backoff) 전략을 사용합니다.
  • 보안 및 액세스 제어: 프로젝트별 범위에 따른 최소 권한 원칙을 적용하며, 저장 데이터 (Data at rest) 및 전송 데이터 (Data in transit)를 암호화합니다.
  • 관측 가능성 (Observability): 인제스션 지연 (Ingestion lag), 처리 지연 시간 (Processing latency) 및 에러율을 위한 대시보드를 유지합니다.

단계별 설계 및 구현

1단계: 데이터 제품(Data products) 및 소스 정의

  • 이벤트 도메인 식별: 사용자 활동(User activity), 트랜잭션(Transactions), 센서 측정값(Sensor measurements) 등
  • 각 도메인별 목록 작성:
    • 소스 유형 (스트리밍 (Streaming), 배치 (Batch))
    • 주요 식별자 (user_id, order_id)
    • 이벤트 시간 (Event time) vs 처리 시간 (Processing time)
    • 필수 필드 및 선택적 필드

2단계: 인제스션 (Ingestion) 청사진

  • 스트리밍 경로 (실시간용): 도메인별 Kafka 토픽 (Kafka topics)
    • 프로듀서 (Producers)는 안정적인 키(예: user_id 또는 event_id)와 함께 이벤트를 발행합니다.
    • 이벤트 페이로드 (Event payload): 단조 증가하는 event_time을 포함하여 정의된 스키마를 가진 Avro 또는 JSON 사용
  • 배치 경로: 데이터를 파티션된 파일 형태로 오브젝트 스토리지 (Object storage)에 드롭
    • 일관된 폴더 구조 사용: /domain/event_date=YYYY-MM-DD/...
  • 처리 접착제 (Processing glue): Kafka에서 소비하여 로우 존 (Raw zone)에 쓰는 스트림 프로세서 (Stream processor)
    • 싱크 레이어 (Sink layer)에서 정확히 한 번 (Exactly-once) 처리를 보장합니다 (예: Iceberg으로 관리되는 Parquet 파일에 쓰기)
    • 필요한 경우 실시간으로 이벤트를 풍부화 (Enrich) 합니다 (참조 데이터 조인, 룩업 (Lookups))

코드 예시: 정확히 한 번 (Exactly-once) 의미론을 갖춘 최소 기능의 Flink 스트리밍 작업

  • 언어: Java 또는 Scala (아래는 명확성을 위해 Java 스타일의 의사코드(Pseudocode)를 사용합니다)

  • 의사코드 하이라이트:

    • 정확히 한 번 (Exactly-once) 의미론을 가진 Kafka 소스 생성
    • Avro/JSON을 POJO로 역직렬화 (Deserialize)
    • 차원 테이블 (Dimension table)을 사용하여 풍부화 (Broadcast state 또는 외부 저장소 경유)
    • 체크포인팅 (Checkpointing) 및 테이블 수준 트랜잭션을 사용하여 Iceberg 기반 Parquet 싱크 (Sink)에 쓰기

// 의사코드 개요
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(60000); // 1분

Properties kafkaProps = new Properties();
// bootstrap servers, group.id 등을 설정합니다.

DataStream events = env
.addSource(new FlinkKafkaConsumer<>("domain-events", new EventDeserializationSchema(), kafkaProps))
.assignTimestampsAndWatermarks(new EventTimeAssigner());

DataStream enriched = events
.keyBy(Event::getKey)
.process(new EnrichmentFunction(dimensionsBroadcastState));

enriched.addSink(new IcebergParquetSink("s3://lake/raw/domain-events"))
.setSemantic(Semantic.EXACTLY_ONCE);

env.execute("Domain Event Ingestion - Exactly-Once");

참고 사항:

  • EventDeserializationSchema는 필드 검증을 포함한 Avro/JSON 파싱을 처리합니다.
  • EventTimeAssigner는 워터마킹 (Watermarking) 및 지연 데이터 (Late data) 처리를 위해 event_time을 추출합니다.
  • EnrichmentFunction은 차원 조회 (Dimension lookup) 또는 외부 저장소를 위해 브로드캐스트 상태 (Broadcast state)를 사용합니다.
  • IcebergParquetSink는 트랜잭션 커밋 (Transactional commits)을 통해 raw 존 (Raw zone)의 Iceberg 테이블에 데이터를 기록합니다.

3단계: 스토리지 레이아웃 (Storage layout) 및 카탈로그 (Catalog)

  • Raw 존: /lake/raw/domain/date=YYYY-MM-DD/part-*.parquet

  • Cleansed 존: /lake/cleansed/domain/date=YYYY-MM-DD/...

  • Curated 존: /lake/curated/domain/aggregates/date=...

  • Iceberg 사용:

    • 스키마 (Schema) 및 파티션 사양 (Partition specs)을 포함한 raw 도메인 데이터용 Iceberg 테이블 생성
    • 원자적 커밋 (Atomic commits)을 지원하기 위해 테이블 수준의 트랜잭션 (Table-level transactions) 활성화
    • 새로운 필드를 안전하게 추가하기 위해 스키마 진화 (Schema evolution) 기능 사용

코드 스니펫: 코드에서 Iceberg 테이블 정의 (개념적)

  • 예시 (개념적): CREATE TABLE lake.raw.domain_events ( event_id STRING, domain STRING, user_id STRING, event_time TIMESTAMP, payload STRUCT<...> ) PARTITIONED BY (date(event_time));

4단계: 배치 (Batch) 및 스트리밍 (Streaming) 처리를 위한 프로세싱

  • 실시간 인리치먼트 (Real-time enrichment): 빠른 캐시 (Cache) 또는 작은 차원 저장소 (Dimension store)를 사용하여 참조 데이터 (예: 사용자 프로필)와 조인 (Join)
  • 윈도우 집계 (Windowed aggregations): 대시보드를 위해 롤링 메트릭 (Rolling metrics, 예: 1시간, 24시간) 계산
  • 배치 재처리 (Batch reprocessing): 정기적으로 Spark 작업을 실행하여 정제된/큐레이션된 뷰 (Cleaned/curated views)를 구체화하고, 스키마가 진화할 때 과거 데이터를 재처리

5단계: 서빙 레이어 (Serving layer) 및 데이터 액세스

  • 쿼리 엔진 (Query engines): 애드혹 분석 (ad-hoc analytics)을 실행하기 위한 Presto/Trino 또는 Spark SQL 사용
  • 구체화된 뷰 (Materialized views): 대시보드를 위한 요약 테이블 유지 (예: 시간당 도메인별 총 이벤트 수 (total_events_by_domain per hour))
  • 데이터 액세스 제어 (Data access controls): 카탈로그 (catalog) 수준에서 사용자별 또는 프로젝트별 액세스 정책 적용
  • 데이터 카탈로그 (Data catalogs): 스키마 (schema), 파티션 (partition), 리니지 메타데이터 (lineage metadata)를 포함한 모든 테이블을 데이터 카탈로그에 등록

6단계: 관찰 가능성 (Observability) 및 신뢰성 (reliability)

  • 모니터링 (Monitoring):
    • 토픽 (topic)별 인제스션 지연 (Ingest lag)
    • 프로세서 (processor)의 처리량 (Throughput) 및 CPU/메모리 사용량
    • 체크포인트 (Checkpoint) 성공/실패율
  • 트레이싱 (Tracing):
    • 인제스션 및 처리 과정을 통한 트레이스 컨텍스트 (trace contexts) 전파
    • 중앙 집중식 백엔드와 함께 OpenTelemetry 사용
  • 알림 (Alerting):
    • 지연 증가, 실패 또는 스키마 드리프트 (schema drift) 발생 시 통지
    • 비정상적인 데이터 패턴에 대한 거버넌스 알림 생성

7단계: 스키마 진화 (Schema evolution) 및 데이터 품질 (data quality)

  • 스키마 진화 (Schema evolution):
    • 가산적 스키마 변경 (additive schema changes, 기본값이 있는 새 필드 추가) 사용
    • 필드 삭제 시, 폐기 기간 (deprecation period) 및 데이터 마이그레이션 계획 고려
  • 데이터 품질 검사 (Data quality checks):
    • 중요 필드에 대해 Non-null 제약 조건 적용
    • 허용 가능한 윈도우 (window) 내의 event_time 검증
    • 이상치 (anomaly) 횟수를 추적하고 임계값(threshold) 도달 시 알림 트리거

8단계: 운영 플레이북 (Operational playbooks)

  • 신규 도메인 온보딩 (New domain onboarding):
    • 표준 이벤트 스키마, 토픽 명명 규칙 및 파티셔닝 체계 정의
    • 스키마 호환성 및 엔드 투 엔드 (end-to-end) 인제스션 테스트 추가
  • 장애 대응 (Incident response):
    • 인제스션 실패, 백프레셔 (backpressure) 및 데이터 드리프트 (data drift)에 대한 런북 (Runbooks) 작성
  • 롤백 전략 (Rollback strategy):
    • Raw 존 (raw zone)의 불변 데이터 (immutable data) 사용; 롤백은 문제가 되는 파티션을 무시하거나 Cleansed 레이어에 수정 작업을 적용하는 것을 의미함

모범 사례 및 일반적인 실수

  • 함정: 높은 카디널리티(High cardinality) 키로 인한 핫 파티션(Hot partitions) 발생
    • 키를 해싱(Hashing)하고, 키로만 파티셔닝하는 대신 시간(Time) 단위로 파티셔닝하여 완화
  • 함정: 지연 도착 데이터(Late-arriving data)로 인한 컨슈머(Consumer) 장애
    • 워터마킹(Watermarking) 및 지연 데이터 처리(Late data handling)를 사용하고, 지연된 이벤트에 대한 유예 기간(Grace period)을 유지
  • 함정: 스키마 드리프트(Schema drift)로 인한 다운스트림 조인(Downstream joins) 오류
    • 하위 호환성(Backward compatibility)을 유지하고, 파괴적 변경(Breaking changes)을 피하며, 스키마 버전(Schema versions)을 게시
  • 함정: 지나치게 낙관적인 정확히 한 번(Exactly-once) 보장
    • 모든 싱크(Sink)가 멱등적 쓰기(Idempotent writes)를 지원하는지 확인하고, 체크포인트(Checkpoints)가 일부가 아닌 전체 핵심 흐름을 커버하는지 확인

예시: 엔드 투 엔드(End-to-end) 데이터 제품

- 도메인: 이커머스 트랜잭션(Ecommerce transactions)

Rizwan Saleem | https://rizwansaleem.co

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0