티스토리 뷰
광고 이벤트 레이크하우스 구축기 (1) — 프로젝트 설계 및 기술 스택
코딩하는 제리코 2026. 6. 10. 14:18프로젝트 개요
데이터 엔지니어링 최종 프로젝트로 광고 이벤트 레이크하우스를 구축한다.
광고 도메인을 선택한 이유는 데이터 특성이 명확하기 때문이다. (보충 추가 예정)
- 이벤트가 퍼널 구조(request → impression → click → conversion)로 흐른다
- 실시간성이 중요하다 (클릭 직후 전환 여부 추적)
- 집계 단위가 다양하다 (캠페인별, 배너별, 디바이스별, 시간별)
이 세 가지 특성이 있으면 파이프라인의 모든 계층(수집 → 저장 → 처리 → 집계)을 의미 있게 설계할 수 있다.
전체 아키텍처(수정 될 수 있음)
[원천 데이터]
Criteo Attribution Dataset (16.5M건)
더미 이벤트 생성기 (Python 스크립트)
↓ JSON
┌─────────────────────┐
│ Apache Kafka │ EKS (K8s)
│ ad-requests │
│ ad-impressions │
│ ad-clicks │
│ ad-conversions │
└─────────────────────┘
↓ Spark Structured Streaming
┌─────────────────────────────────────┐
│ AWS S3 (Apache Iceberg) │
│ Bronze → Silver → Gold │
│ (raw) (정제) (집계) │
└─────────────────────────────────────┘
↓
AWS Athena → 대시보드
Medallion Architecture
데이터를 3계층으로 나눠서 관리하는 구조다.
| 계층 | 역할 | 데이터 상태 |
| Bronze | 원본 보존 | Kafka에서 온 raw JSON 그대로 |
| Silver | 정제 | 타입 변환, 중복 제거, 퍼널 조인 |
| Gold | 집계 | 캠페인별 일별 성과 지표 |
Bronze는 절대 원본을 변환하지 않는다.
파이프라인에 버그가 생겼을 때 Bronze에서 다시 처리(reprocessing)할 수 있어야 하기 때문이다.
Kafka를 쓰는 이유 (보충 예정)
처음에 Kafka를 사용하지 않고 Redis로도 충분히 가능하지 않나라는 의문이 있었다.
| Kafka | Redis | |
| 저장 방식 | 디스크 (영구 보존) | 메모리 (재시작 시 손실) |
| offset 관리 | 내장 (어디까지 읽었는지 자동 추적) | 직접 구현 필요 |
| Spark 연동 | 네이티브 커넥터 제공 | 별도 구현 필요 |
| 재처리 | 보존 기간 내 언제든 재읽기 가능 | 소비하면 사라짐 |
Spark Structured Streaming은 Kafka 오프셋을 기반으로 체크포인트를 관리한다.
장애가 나도 마지막으로 읽은 오프셋 이후부터 다시 읽으면 된다.
Redis는 이 구조를 지원하지 않는다.
Avro 대신 JSON을 선택한 이유
처음에는 Kafka 실무 표준인 Avro + Schema Registry를 도입하려 했다.
하지만 이 프로젝트 구조에서는 맞지 않는다.
이유: Bronze = raw 원칙
메달리온 아키텍처에서 Bronze는 원본 데이터를 변환 없이 보존하는 계층이다.
Avro + Schema Registry를 쓰면 스키마에 맞지 않는 이벤트가 Kafka 진입 전에 거부된다.
이는 Bronze에 담겨야 할 원본 데이터가 유실되는 것이다.
나쁜 흐름:
Producer → [스키마 불일치] → Schema Registry 거부 → Bronze에 없음 → 재처리 불가
스키마 관리는 Glue Catalog가 담당한다.
S3에 저장된 Silver/Gold 테이블의 컬럼, 타입, 파티션 정보는 Glue Catalog에서 관리한다.
Schema Registry가 진짜 필요한 상황은 따로 있다.
회사 A팀 Producer ─┐
회사 B팀 Producer ─┤→ Kafka → C팀 Consumer
D팀 Consumer _|
서로 다른 팀이 독립적으로 Producer/Consumer를 운영할 때, 한 팀이 스키마를 바꾸면 다른 팀 Consumer가 터진다.
Schema Registry는 이 계약을 강제한다.
이 프로젝트는 Producer 2개를 직접 만든다. Schema Registry가 필요한 조건 자체가 없다.
EKS vs 로컬 Docker
처음에는 EKS(Elastic Kubernetes Service) 위에 Kafka를 올리는 것부터 시작하려 했다.
그런데 개발 초기에 EKS 클러스터부터 구성하면 시간이 너무 많이 걸린다.
먼저 로컬 Docker로 개발하고, 나중에 EKS로 전환하는 것이 실무에서도 일반적인 순서다.
전환할 때 바뀌는 건 딱 하나다.
# config.py
# 로컬 Docker 개발 단계
KAFKA_BOOTSTRAP_SERVERS = "localhost:9092"
# EKS 전환 후
KAFKA_BOOTSTRAP_SERVERS = "a1b2c3d4.ap-northeast-2.elb.amazonaws.com:9092"
Producer 코드는 전혀 바꾸지 않아도 된다. config.py에서 주소를 읽어가기 때문이다.
디렉토리 구조
ad-event-lakehouse/
├── README.md
├── .gitignore
├── code/
│ ├── producers/ ← 이번 시리즈 (1), (2) 구현 영역
│ │ ├── common/
│ │ │ ├── __init__.py
│ │ │ └── schema.py
│ │ ├── config.py
│ │ ├── dummy_generator.py
│ │ ├── criteo_producer.py
│ │ └── requirements.txt
│ ├── ddl/ ← Bronze/Silver/Gold DDL (이후)
│ ├── pipelines/ ← Glue ETL 로직 (이후)
│ └── health-queries/ ← 운영 헬스 쿼리 (이후)
├── infra/ ← EKS, Kafka K8s 설정 (이후)
├── orchestration/ ← Airflow DAG (이후)
└── dashboard/ ← BI 대시보드 (이후)
다음 글
다음 글에서는 실제 원천 데이터 생성 코드를 다룬다.
- 공통 이벤트 스키마 설계(schema.py)
- 더미 이벤트 생성시기 퍼널 구조 (dummy_generator.py)
- Criteo 데이터 매핑 및 버그 수정 (criteo_producer.py)
'Projects > 광고 플랫폼 Lakehouse 실전 설계 with Iceberg' 카테고리의 다른 글
| 광고 이벤트 레이크하우스 구축기 (4) — Producer 컨테이너화 와 토픽 자동 생성 (0) | 2026.06.14 |
|---|---|
| [개념] Spark Structured Streaming vs AWS Glue Streaming (0) | 2026.06.11 |
| 광고 이벤트 레이크하우스 구축기 (3) — 로컬 Kafka 환경 구성 및 Producer 연결 테스트 (0) | 2026.06.11 |
| [개념] Iceberg MERGE INTO — 늦게 오는 전환 데이터를 어떻게 처리할까 (0) | 2026.06.11 |
| 광고 이벤트 레이크하우스 구축기 (2) — 원천 데이터 생성 코드 구현 (0) | 2026.06.10 |
- Total
- Today
- Yesterday
- iceberg
- Spark structured streaming
- Glue
- Daynamic Task
- elasticip
- de
- airflow
- AWS
- s3
- Glue ETL
- Unity Catalog
- Data Engineerring
- catchup
- docker
- AWS Glue Catalog
- Databricks
- Backfill
- 데이터파이프라인
- Data Dngineering
- lake house
- Data Pipeline
- Consumer DAG
- DataSet
- spark
- kafka
- RDD
- Data engineering
- DAG
- Prodcuder DAG
- lakehouse
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
