모든 데이터 스택에서 실행되는 12가지 패턴. 시니어리티란 각 패턴이 언제 실수가 되는지 아는 것이다.
요약
데이터 스택의 12가지 패턴 중 ETL과 ELT의 실무적 차이와 주의점을 다룹니다. 각 아키텍처의 용도뿐만 아니라, 비용 효율성과 컴플라이언스 측면에서 어떤 상황이 실수가 되는지 시니어의 관점에서 분석합니다.
핵심 포인트
- ETL은 PII 마스킹 등 컴플라이언스 준수가 필수적인 상황에 적합함
- ELT는 현대 데이터 웨어하우스의 기본값이지만 중복 변환 시 비용이 급증함
- 데이터 이동 패턴 선택 시 단순 선호도가 아닌 제약 조건과 비용을 고려해야 함
- ELT 환경에서는 원시 데이터 스캔 비용을 줄이기 위한 공유 스테이징 전략이 필요함
권장 태그: dataengineering, architecture, bigdata, dataops
여러분은 그 차트를 보셨을 겁니다 — 12개의 박스, 12개의 패턴, 지금까지 구축된 모든 데이터 스택이 그 안에 담겨 있습니다. Darshil Parmar의 영역 지도는 진정으로 훌륭합니다. 하지만 지도는 어떤 도로가 폐쇄되었는지, 어떤 도로가 우기에 침수되는지, 그리고 고속도로처럼 보이지만 통행료를 유료 도로처럼 청구하는 곳은 어디인지 알려주지 않습니다.
17년 동안 이를 프로덕션(production) 환경에 배포해 온 경험을 바탕으로, 12가지 패턴 모두에 대한 제 견해를 정리했습니다. 각 패턴에 대해: 용도가 무엇인지, 그리고 — 차트가 보여줄 수 없는 부분인 — 언제 그것이 실수가 되는지를 다룹니다.
데이터 이동: ETL, ELT, CDC
1. ETL — 향수 때문이 아니라, 여전히 살아있다
PII(개인정보)를 데이터가 어디에 착륙하기 전에 반드시 마스킹(masking)해야 하는 경우, 변환 우선(transform-first) 방식은 단순한 선호의 문제가 아닙니다. 이는 제약 조건을 충족하는 유일한 아키텍처(architecture)입니다. 컴플라이언스(Compliance)가 ETL을 계속 고용하게 만듭니다.
이 주장은 실행 가능합니다. 그 형태는 다음과 같습니다 — 데이터가 이동하는 중에 마스킹하여, 원본 값이 저장소에 절대 닿지 않도록 합니다:
import hashlib
import json
...
{
"user_id": 42,
"email": "0a1f2b8c9d3e4f5a",
...
ELT에서 이를 시도해 보십시오. 불가능합니다 — 정의상, 변환되지 않은 값이 이미 착륙했기 때문입니다. 만약 규제 기관이 단순히 누가 쿼리(query)하는지가 아니라, 원본 PII가 어디에 존재하는지를 중요하게 여긴다면, L(Load)이 T(Transform)보다 앞서는 순서는 부적격입니다.
실수가 되는 경우: 착륙(landing)에 대한 제약 조건이 없는데, 웨어하우스(warehouse)가 대체할 수 있는 변환 클러스터(transform cluster) 비용을 지불하고 있을 때.
2. ELT — 숨겨진 승수를 가진 현대의 기본값
Snowflake, BigQuery, Redshift는 웨어하우스를 컴퓨팅 엔진(compute engine)으로 탈바꿈시켰습니다. 저렴한 스토리지(storage)가 글자의 순서를 뒤집었습니다. 원본을 로드(Load)하고, SQL에서 변환(transform)하며, dbt에서 버전을 관리합니다. 이는 올바른 기본값입니다.
하지만 청구서를 주의 깊게 보십시오. 왜냐하면 "나중에 변환한다"는 말이 조용히 "다섯 번 변환한다"로 변하기 때문입니다. 마케팅 팀은 원본 이벤트에서 sessions_clean을 도출합니다. 제품 팀은 거의 동일한 테이블을 도출합니다. 재무 팀은 필터 하나를 추가하여 다시 도출합니다. 아무도 모릅니다. 왜냐하면 원본 데이터가 바로 거기에 있고, 도출하는 것은 CREATE TABLE AS 한 번이면 충분하기 때문입니다.
승수는 단순히 산술적인 문제입니다:
raw_scan_tb = 2.0 # 원시 이벤트(raw events) 전체 변환 시 스캔되는 TB 수
price_per_tb = 6.25 # 온디맨드(on-demand) 스캔 가격, 사용 중인 웨어하우스에 맞춰 조정
runs_per_day = 4 # 예정된 새로고침(scheduled refreshes) 횟수
...
한 팀, 한 달: $1,500
다섯 팀, 동일 데이터: $7,500
동일한 원시 데이터(raw data). 동일한 비즈니스 질문. 하지만 다섯 개의 청구서가 발생합니다. 해결책은 ELT를 포기하는 것이 아닙니다. 원시 데이터의 값비싼 스캔이 한 번만 발생하고, 모두가 _그것_으로부터 파생될 수 있도록 공유 스테이징 레이어(shared staging layer)를 구축하는 것입니다.
실수가 되는 경우: 패턴 자체가 아니라, 소유권이 없는(unowned) 패턴으로 운영될 때입니다. 공유 모델 레이어(shared model layer)가 없는 ELT는 중복된 컴퓨팅 비용을 지불하겠다는 구독과 같습니다.
3. CDC — 스택에서 가장 오진된 패턴
모두가 실시간 복제(real-time replication)를 요구합니다. 하지만 대부분은 시간 단위의 배치(hourly batch)만으로 충분합니다. 진단 질문은 "데이터가 얼마나 최신인가?"가 아닙니다. "_의사결정_이 얼마나 최신이어야 하는가?"입니다.
def cdc_or_batch(decision_latency_min: float, change_rate_per_hr: int) -> str:
"""배치 윈도우(batch window)보다 빠르게 변하는 의사결정이 없다면, CDC는 기능이 아니라 비용입니다."""
if decision_latency_min < 15:
...
거래에 대한 사기 방지 홀딩(Fraud hold) -> CDC — 배치 윈도우 내에서 실제로 의사결정이 변경됨
경영진 판매 대시보드(Executive sales dashboard) -> 시간 단위 배치 — 이 정도면 충분합니다, 퇴근하세요
앱으로의 재고 동기화(Inventory sync to the app) -> CDC — 배치 윈도우 내에서 실제로 의사결정이 변경됨
그리고 만약 정답이 정말로 CDC라면, 이를 존중하십시오. Kafka에 데이터를 공급하는 Debezium 에이전트는 운영 인프라입니다. 스키마 진화(schema evolution) 처리, 스냅샷 전략(snapshot strategy), 오프셋 관리(offset management), 데드 레터 큐(dead-letter queues), 온콜(on-call) 대응이 필요합니다. 주말에 가볍게 해보는 프로젝트가 아닙니다. 저는 이 목록에 있는 그 어떤 패턴보다도, 무심하게 배포된 CDC로 인해 더 많은 장애가 발생하는 것을 보았습니다.
실수가 되는 경우: 시간 중간에 변하는 의사결정에 근거한 것이 아니라, 슬라이드에 적힌 "실시간(real-time)"이라는 단어 때문에 강요되었을 때입니다.
데이터 저장: 레이크하우스 (Lakehouse)
4. 레이크하우스 (Lakehouse) — 10년 전쟁 끝에 맺은 휴전
데이터 저장: 레이크하우스 (Lakehouse)
4. 레이크하우스 (Lakehouse) — 10년 전쟁 끝에 맺은 휴전
레이크와 웨어하우스는 결코 공정한 싸움이 아니었습니다. 그것은 동일한 데이터의 두 복사본, 서로 동의하는 척하는 두 파이프라인, 그리고 그 사이에서 사과하는 조정 작업에 불과했습니다. 레이크하우스(Delta, Iceberg, Hudi — 객체 스토리지 위 ACID를 제공하는 테이블 형식)는 이 휴전 협정입니다. 하나의 복사본, 단일 진실 공급원(single source of truth), 그리고 레이크 경제학 위에 웨어하우스의 의미론을 얹은 것입니다.
저는 이 패턴을 통해 6개의 데이터 레이크를 1개로 통합했습니다. 이전과 이후 비교는 다음과 같습니다:
| 이전 (6개 레이크) | 이후 (1개 레이크하우스) | |
|---|---|---|
| 스토리지 | baseline | −40% |
| ... |
컴퓨팅 비용 절감분은 사람들이 예측하지 못하는 부분입니다. 그 지출의 대부분은 분석에 쓰이지 않았습니다. 그것은 6개의 복사본을 느슨하게 동기화시키려는 여섯 개의 파이프라인이었고, 이는 통찰력을 제공하지 않고 오직 '동의'만을 만들어내는 작업이었습니다. 중복성을 제거하면 컴퓨팅 비용도 스스로 사라집니다.
실수하는 경우: 데이터가 하나의 웨어하우스에 편안하게 들어맞는데 '레이크하우스 마이그레이션'이 이력서(résumé)를 위한 것이거나, 혹은 필요하지 않은 Delta 레이크보다 단일 Postgres 레플리카와 dbt가 더 나은 경우입니다.
데이터 처리 주기: 스트리밍, 배치, Lambda, Kappa
5. 실시간 스트리밍 (Real-time streaming) — 강력하지만 과도하게 규정된 방식
Flink와 Spark Structured Streaming이 복잡성을 얻는 순간은 결정이 분 단위로 바뀔 때입니다: 사기 거래 차단(fraud holds), 동적 가격 책정, 운영 알림 등이 있습니다. 그 복잡성은 실제이며, 워터마크(watermarks), 지연 데이터(late data), 정확히 한 번의 의미론(exactly-once semantics), 그리고 세심한 관리가 필요한 상태 저장소(state stores)가 존재합니다.
한편, 5분마다 새로고침되는 대시보드는 코스튬을 입은 배치 작업일 뿐입니다. 만약 데이터 소비자가 회의 사이에 화면을 흘긋 보는 인간이라면, 5분짜리 마이크로 배치도 운영상의 복잡도는 훨씬 낮으면서 동일한 경험을 제공합니다. 스트리밍 작업이 사용자를 위한 것이 아니라 아키텍처 다이어그램을 위해 봉사하고 있는 것입니다.
실수하는 경우: 가장 신선한 데이터와 실제로 '필요한' 데이터가 배치 간격보다 더 많이 차이가 날 때마다 발생합니다.
6. 배치 (Batch) — 화려하진 않지만 세상을 움직이는 방식
지구상의 대부분의 데이터는 여전히 밤사이에 배치 (Batch) 방식으로 이동하며, 이는 괜찮습니다. 제가 경험한 가장 좋아하는 최적화 성공 사례는 순수하게 배치 방식이었습니다. 8시간이 걸리던 Spark 파이프라인을 47분으로 줄였습니다. 새로운 도구도, 마이그레이션도 없었습니다. 해결책은 지루하지만 복리 효과를 내는 종류였습니다.
# 8시간을 47분으로 단축시킨 변경 사항들 — 그 중 특이한 것은 하나도 없었습니다.
spark.conf.set("spark.sql.shuffle.partitions", "auto") # 2018년 블로그 포스트에서 가져온 하드코딩된 200을 변경
...
지루한 일을 잘 해내는 것이 흥미로운 일을 하는 것보다 낫습니다. 8시간 걸리던 작업이 47분으로 줄어들면, 작업이 실패하더라도 업무 시간 내에 다시 실행할 수 있습니다. 이는 이 페이지에 언급된 그 어떤 아키텍처 선택보다 여러분의 온콜 (on-call) 생활을 더 크게 변화시킵니다.
실수가 되는 경우: 결정 사항이 배치 윈도우 (window)를 진정으로 기다릴 수 없는 경우에만 해당합니다. 이는 벤더의 발표 자료(vendor deck)에서 말하는 것보다 훨씬 드문 일입니다.
9. Lambda — 긴장 상태를 솔직하게 인정하지만, 실행 시에는 잔혹함
(9번과 10번은 하나의 논점이기 때문에 함께 다룹니다.) Lambda 아키텍처는 실제적인 긴장 상태를 인정합니다. 즉, 어떤 답변은 빨라야 하고, 모든 답변은 완전해야 한다는 것입니다. 그 해결책인 — 동일한 지표를 계산하는 스피드 레이어 (speed layer)와 배치 레이어 (batch layer) — 는 영원히 일치해야만 하는 두 개의 코드베이스를 의미합니다.
하지만 두 코드는 일치하지 않을 것입니다. 다음은 두 레이어에서의 동일한 "일일 매출 (daily revenue)" 예시입니다. 두 번 배포되는 버그를 찾아보세요:
-- 배치 레이어 (Batch layer, SQL): 시간대(timezone)를 인식하며, 환불(refunds)은 제외됨
SELECT DATE(created_at AT TIME ZONE 'America/Sao_Paulo') AS day,
SUM(amount) AS revenue
...
# 스피드 레이어 (Speed layer, streaming): UTC 날짜, 환불... 누락됨
def on_event(order, state):
day = order["created_at"][:10] # UTC 슬라이스 — 이미 배치 레이어와 불일치
...
이제 모든 정의 변경 사항은 두 개의 언어, 두 개의 배포 파이프라인, 그리고 두 팀의 머릿속에 반영되어야 합니다. 모든 버그는 두 번 배포되며, 데이터 정합성 확인 회의 (reconciliation meeting)는 경력이 늙어가는 장소가 됩니다.
실수가 되는 경우: 오늘날에는 거의 항상 그렇습니다. Lambda는 테이블 포맷 (table formats)과 현대적인 스트림 프로세서 (stream processors)가 등장하기 전에는 타당했습니다. 하지만 지금은 대부분 그리스식 이름을 가진 역사적 유물에 불과합니다.
10. Kappa — Lambda에 대한 사과
하나의 스트림, 하나의 코드베이스, 재처리(reprocess)가 필요할 때 로그를 다시 재생(replay)합니다. 만약 Kafka의 보관 기간(retention)과 재처리 시나리오를 감당할 수 있다면 매우 우아합니다:
daily_gb = 400 # 수집량 (ingest volume)
retention_days = 90 # 로직 버그 발견 후 재생하기에 충분한 기간
replication = 3
...
Kafka 보관량: 108.0 TB
보관 비용: $10,800/월
500 MB/s 속도로 전체 재생 시 소요 시간: 20시간
많은 팀이 이를 보고 대신 약간의 스트리밍 이점이 있는 배치(batch) 방식을 선택합니다. 그것은 실패가 아니라 산술적인 결정입니다.
실수가 되는 경우: 보관 비용과 재생 기간(replay window)이 Lambda의 이중 코드베이스가 초래하던 문제보다 더 커질 때입니다.
데이터 파악하기: 카탈로그, 리니지 (catalog, lineage)
7. 데이터 카탈로깅 (Data cataloging) — 살아있는 시스템으로만 작동한다
아무도 업데이트하지 않는 카탈로그는 박물관과 같습니다. 방문하기에는 즐겁지만, 현재의 모든 것에 대해 틀린 정보를 담고 있죠. 제가 본 생존하는 카탈로그는 오직 파이프라인 자체에 의해 채워지는 것들뿐이었습니다. 즉, 6주 뒤에 데이터 관리자(data steward)가 직접 입력하는 것이 아니라, 실행 시간(run time)에 방출되는 메타데이터(metadata)를 사용하는 방식입니다. 만약 카탈로그를 업데이트하는 것이 인간의 작업이라면, 그 카탈로그는 이미 오래된 것입니다. 단지 당신이 아직 알아차리지 못했을 뿐입니다.
실수가 되는 경우: 독립적인 문서화 프로젝트로 접근할 때입니다. 오케스트레이션(orchestration)의 자동화된 부수 효과(side effect)로 구현한다면, 이 목록에서 가장 레버리지가 높은 작업 중 하나가 될 것입니다.
11. 리니지 (Lineage) — 이사회 보고서가 틀리기 전까지는 아무도 예산을 배정하지 않는다
그러다 갑자기 "이 숫자가 어디서 왔는가"라는 질문이 그것을 생성한 파이프라인보다 더 가치 있게 느껴지는 순간이 옵니다. 리니지는 보험과 같습니다. 사고가 발생하여 그것이 유일하게 중요한 것이 되기 직전까지는 순수한 비용처럼 보입니다. 요령은 카탈로깅과 같습니다. 문서로부터 유도하지 말고, 실행(쿼리 로그, dbt 매니페스트, OpenLineage 이벤트)으로부터 유도하십시오.
실수가 되는 경우: 수동으로 관리할 때입니다. 자동화되거나, 혹은 아예 없거나 — 이 두 가지만이 유일하게 정직한 상태입니다.
사람 조직하기: 메쉬 (mesh)
8. 데이터 메쉬 (Data mesh) — 아키텍처의 탈을 쓴 조직적 결정
Mesh는 기술이 아닙니다. 그것은 당신의 회사에 대한 주장입니다. 즉, 도메인 팀이 데이터 제품 (data as a product)으로서 데이터를 소유할 준비가 되어 있다는 주장입니다. 여기에는 SLA, 계약으로서의 스키마 (schemas as contracts), 그리고 자신들의 데이터셋이 소비자에게 문제를 일으켰을 때 호출(on call)에 응답할 사람이 포함됩니다. 만약 그 주장이 사실이라면, 메쉬 (mesh)는 해방을 가져다줍니다. 만약 그것이 거짓이라면, 메쉬는 이름만 더 그럴싸하게 바꾼 분산된 혼돈 (decentralized chaos)일 뿐이며, 중앙 데이터 팀은 아무것도 소유하지 않으면서 여전히 모든 문제에 대해 호출을 받게 됩니다.
실수가 되는 경우: 조직도 (org chart) 상에서 실현되기 전에 아키텍처 (architecture)로서 채택될 때.
모두 실행하기: 오케스트레이션 (orchestration)
12. 오케스트레이션 (Orchestration) — 아무도 모니터링하지 않는 심장 박동
DAG는 플랫폼의 심장 박동이자, 가장 모니터링이 덜 되는 구성 요소입니다. 모두가 파이프라인 (pipelines)을 지켜봅니다. 하지만 그것들을 실행하는 스케줄러 (scheduler)를 지켜보는 사람은 거의 없습니다. 조용히 성능이 저하된 스케줄러는 단순히 파이프라인을 시작하지 않음으로써, 모든 파이프라인을 한꺼번에, 그리고 조용히 실패하게 만듭니다.
# 대부분의 플랫폼이 놓치고 있는 알림: "작업이 실패했는가?"가 아니라
# "스케줄러가 시도조차 하지 못했는가?"입니다.
from airflow.sensors.base import BaseSensorOperator
...
실패한 DAG는 누군가에게 호출을 보냅니다. 하지만 시작조차 되지 않은 DAG는 아무에게도 호출을 보내지 않습니다. 아침 대시보드가 비어 있고 12개의 팀이 동시에 그 사실을 깨닫기 전까지는 말입니다.
실수가 되는 경우: 실수가 아닙니다. 모니터링되지 않는 상태라면, 그것은 그저 조용한 밤을 기다리고 있는 실수일 뿐입니다.
차트에서 빠진 것
주의하십시오. 이 12가지 중 어느 것도 클라우드 전용 (cloud-specific)이 아닙니다. 동일한 패턴이 AWS나 GCP에서, Databricks에서, 혹은 당신이 충분히 고집스럽다면 순수 Postgres와 cron 위에서도 실행됩니다. 도구는 5년마다 바뀝니다. 패턴은 커리어입니다.
원칙: 여기 있는 모든 패턴은 문제를 해결하는 동시에 새로운 문제를 만들어냅니다. 아키텍처 (architecture)란 당신이 어떤 문제를 겪고 싶은지 선택하는 것입니다.
당신은 이 중 무엇을 너무 일찍 채택했습니까? 그리고 그 대가는 무엇이었습니까?
저는 Vinicius Fagundes입니다 — 상파울루의 수석 데이터 엔지니어 (principal data engineer), 독립 컨설턴트이자 MBA 강사입니다. 저는 데이터 파이프라인 (data pipelines)과 그 위에서 작동하는 수학에 대해 글을 쓰며, vf-insights.com을 통해 분기당 몇 개의 아키텍처 프로젝트를 수행합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기