실시간 인제스션(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)
- 데이터 포맷 및 스키마 (Data formats and schemas)
- 분석 엔진과의 효율성 및 호환성을 위해 컬럼형 포맷 (Columnar formats, 예: Parquet/ORC)을 사용합니다.
- 호환성을 강제하기 위해 스키마 레지스트리 (Schema registry) 또는 카탈로그 (Catalog)에 스키마를 저장합니다.
- 다음과 같은 특징을 가진 정형화되고 진화 가능한 스키마 (Canonical, evolving schema)를 선호합니다:
- 파티션 프루닝 (Partition pruning)을 위한 파티션 키 (예: event_date).
- 하위 호환성 (Backward compatibility)을 위한 선택적이고 Null 허용이 가능한 필드.
- 스키마 진화 (Schema evolution) 전략을 구현합니다:
- 중단 없는 추가 전용 변경 사항 (기본값이 있는 새 필드 추가)은 안전합니다.
- 필드 삭제 또는 이름 변경은 피해야 하며, 필요할 경우 마이그레이션 계획이 필요합니다.
- 정확히 한 번 (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 저장소)를 유지합니다.
- 데이터 품질 및 리니지 (Data quality and lineage)
- 인제스션 중에 엄격한 스키마 및 도메인 제약 조건에 따라 데이터를 검증합니다.
- 각 레코드에 리니지 메타데이터 (소스, 타임스탬프, 스키마 버전)를 포함하거나 테이블 수준의 어노테이션 (Annotations)으로 포함합니다.
- 데이터 품질 문제 (예: 누락된 필드, 지연 데이터, 스키마 드리프트 (Schema drift))에 대한 이상 탐지 및 알림을 구현합니다.
- 운영 고려 사항 (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가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기