데이터 엔지니어 인터뷰 — 데이터 마트 구축

모델링 기초

데이터 웨어하우스, 데이터 마트, 데이터 레이크의 차이는 무엇인가요

데이터 레이크는 원본을 형태 그대로 쌓아두는 저장소입니다. 스키마를 읽는 시점에 적용합니다(schema-on-read).

데이터 웨어하우스는 정제·통합된 구조화 데이터를 두는 곳입니다. 전사 단위이고 스키마가 먼저 정의됩니다(schema-on-write).

데이터 마트는 특정 부서나 주제 영역에 맞춰 웨어하우스에서 잘라낸 부분집합입니다. 조회 패턴이 정해져 있어 그에 맞게 비정규화되어 있습니다.

요즘은 레이크하우스로 경계가 흐려졌습니다. 오브젝트 스토리지 위에 Iceberg·Delta 같은 테이블 포맷을 얹어 레이크의 유연성과 웨어하우스의 트랜잭션·스키마를 함께 얻는 구조입니다.

스타 스키마와 스노우플레이크 스키마를 비교해보세요

스타 스키마는 팩트 테이블 하나를 여러 디멘전이 둘러싼 형태이고, 디멘전은 비정규화되어 있습니다. 조인이 한 단계라 쿼리가 단순하고 빠릅니다.

스노우플레이크는 디멘전을 정규화해 계층으로 쪼갠 형태입니다. 중복이 줄지만 조인이 깊어집니다.

분석용으로는 대체로 스타 스키마를 씁니다. 저장 비용보다 조회 성능과 이해하기 쉬움이 중요하고, 컬럼너 포맷에서는 중복 문자열의 압축 효율이 좋아 저장 부담도 적습니다.

팩트 테이블의 종류를 설명해보세요

종류 내용 예시
Transaction 사건 하나가 한 행 주문, 결제, 클릭
Periodic Snapshot 일정 주기의 상태 일별 재고, 월별 잔액
Accumulating Snapshot 프로세스 진행에 따라 행이 갱신됨 주문→결제→출고→배송 완료

Accumulating Snapshot은 여러 마일스톤 날짜 컬럼을 두고 단계가 진행될 때마다 UPDATE합니다. 단계별 소요 시간 분석에 유용합니다.

grain(입도)이 왜 중요한가요

팩트 테이블의 한 행이 무엇을 의미하는지 정의하는 것입니다. 설계에서 가장 먼저 정해야 하는 것이고, 이게 흔들리면 집계가 전부 틀어집니다.

“주문 1건”과 “주문 품목 1건”은 다른 grain입니다. 후자로 두면 주문 금액을 합칠 때 중복 계산됩니다. 서로 다른 grain의 지표를 한 테이블에 섞으면 안 됩니다.

가법성(additivity)을 설명해보세요

  • Additive — 모든 디멘전에 대해 합산 가능 (매출액, 수량)
  • Semi-additive — 일부 디멘전에서만 합산 가능 (재고 수량 — 시간 축으로 더하면 안 됨)
  • Non-additive — 합산 불가 (비율, 단가)

비율은 미리 계산해 저장하면 안 되고, 분자와 분모를 각각 저장한 뒤 조회 시점에 나눠야 합니다. 평균의 평균이 전체 평균이 아닌 것과 같은 이유입니다.

SCD

SCD Type 1, 2, 3을 설명해보세요

디멘전 속성이 변할 때 이력을 어떻게 다룰지에 대한 방식입니다.

Type 1 — 덮어씁니다. 이력이 없습니다. 오타 수정처럼 과거를 보정해야 하는 경우.

Type 2 — 새 행을 추가하고 기존 행을 만료 처리합니다. 시점별 진실을 보존합니다.

customer_key | customer_id | grade | valid_from | valid_to   | is_current
1            | C001        | SILVER| 2025-01-01 | 2026-03-01 | false
2            | C001        | GOLD  | 2026-03-01 | 9999-12-31 | true

Type 3 — 이전 값을 별도 컬럼에 하나만 보관합니다. 직전 값만 필요할 때.

실무에서는 Type 1과 Type 2가 대부분입니다.

대리 키(surrogate key)를 쓰는 이유는 무엇인가요

SCD Type 2에서는 같은 자연 키(customer_id)가 여러 행으로 존재하므로, 팩트가 어느 시점의 디멘전 행을 가리키는지 구분할 식별자가 필요합니다. 그게 대리 키입니다.

부수적으로 소스 시스템의 키 체계 변경으로부터 웨어하우스를 격리하는 효과도 있습니다.

SCD Type 2를 MERGE로 구현할 때 주의할 점은 무엇인가요

순서가 뒤바뀐 이벤트입니다. 늦게 도착한 과거 변경을 그대로 적용하면 최신 행을 잘못 만료시킵니다.

이벤트 시각을 기준으로 정렬하고, 이미 반영된 시점보다 오래된 변경은 중간에 끼워 넣거나 무시하는 로직이 필요합니다. 그리고 삭제를 어떻게 표현할지도 정해야 합니다. 소스에서 행이 사라진 것을 CDC 없이 감지하기는 어렵습니다.

파이프라인

메달리온 아키텍처를 설명해보세요

계층을 나눠 책임을 분리하는 패턴입니다.

계층 내용 특징
Bronze (Raw) 원본 그대로 변환 없음, 재처리의 기준점
Silver 정제·표준화·중복 제거 타입 정리, 스키마 통일, 조인 가능한 형태
Gold 비즈니스 집계 마트, 지표, 대시보드용

Bronze를 원본 그대로 두는 것이 핵심입니다. 로직이 바뀌거나 버그가 발견되면 Bronze에서 다시 만들면 됩니다. 여기서 변환을 해버리면 되돌릴 수 없습니다.

증분 적재는 어떻게 설계하나요

전체를 매번 다시 읽는 것은 비용이 큽니다. 증분 기준을 무엇으로 잡느냐가 관건입니다.

  • updated_at 컬럼 — 간단하지만 삭제를 못 잡습니다. 그리고 소스가 이 컬럼을 갱신하지 않는 경우가 있습니다
  • CDC(로그 기반) — insert/update/delete를 모두 잡습니다. Debezium, Datastream 등
  • 테이블 포맷의 변경 피드 — Iceberg·Delta의 스냅샷 간 diff

워터마크(마지막 처리 지점)를 어디에 저장할지, 그리고 실패 후 재시도에서 중복이 나지 않게 할지를 함께 설계해야 합니다.

멱등성(idempotency)은 왜 중요한가요

파이프라인은 반드시 재실행됩니다 — 실패, 백필, 로직 수정. 같은 입력으로 여러 번 돌려도 결과가 같아야 안심하고 돌릴 수 있습니다.

구현 방법은 몇 가지입니다.

  • 파티션 덮어쓰기 — 대상 파티션을 통째로 지우고 다시 씀. 가장 단순하고 확실
  • MERGE(UPSERT) — 키 기준으로 갱신
  • 배치 ID 기록 — 이미 처리한 배치는 건너뜀

INSERT만 하는 파이프라인은 재실행할 때마다 데이터가 불어납니다.

백필(backfill)은 어떻게 하나요

과거 기간을 다시 채우는 작업입니다. 몇 가지를 미리 정해둬야 합니다.

  1. 파티션 단위로 나눠 실행 — 전체를 한 번에 돌리면 실패 시 처음부터
  2. 운영 파이프라인과 리소스 분리 — 백필이 정기 배치를 굶기지 않도록
  3. 날짜를 파라미터로 — 코드에 current_date가 박혀 있으면 백필이 불가능합니다
# 나쁨 — 백필 불가
WHERE dt = CURRENT_DATE - 1

# 좋음 — 실행 날짜를 주입
WHERE dt = '{{ ds }}'

Airflow의 execution_date 같은 논리 날짜를 쓰는 이유가 이것입니다.

품질과 운영

데이터 품질은 무엇을 검증하나요

  • 완전성 — 행 수가 예상 범위인가, 필수 컬럼에 NULL이 있는가
  • 유일성 — 키가 중복되지 않는가
  • 정합성 — 팩트의 외래 키가 디멘전에 존재하는가
  • 범위 — 값이 허용 범위 안인가 (음수 금액, 미래 날짜)
  • 신선도 — 최신 데이터가 기대 시각까지 도착했는가

검증을 적재 전에 막을지, 적재 후 경고만 할지 정책을 정해야 합니다. 무조건 막으면 파이프라인이 자주 서고, 무조건 통과시키면 잘못된 데이터가 대시보드에 뜹니다. 보통 치명적 규칙만 차단하고 나머지는 경고로 둡니다.

지표가 대시보드마다 다르게 나옵니다. 원인이 무엇일까요

대개 정의가 여러 곳에 흩어져 있기 때문입니다. 같은 “활성 사용자”를 대시보드마다 다르게 계산하고 있는 경우입니다.

해결은 정의를 한 곳에 모으는 것입니다 — Gold 계층에 지표 테이블을 만들거나, dbt 같은 도구로 정의를 코드로 관리하거나, 시맨틱 레이어를 두거나. 그리고 지표 정의 문서와 오너를 명시합니다.

기술적 원인일 수도 있습니다. 타임존 불일치, 집계 시점 차이(스냅샷 vs 실시간), 늦게 도착한 데이터 반영 여부 같은 것들입니다.

타임존은 어떻게 다루나요

저장은 UTC로, 표시할 때 변환하는 것이 원칙입니다. 소스마다 타임존이 다른 데이터를 그대로 섞으면 나중에 되돌릴 수 없습니다.

날짜 파티션을 만들 때 어느 타임존 기준인지 명시해야 합니다. KST 기준 일별 집계를 UTC 날짜로 파티셔닝하면 하루가 9시간 밀립니다.

스키마 변경 요청이 오면 어떻게 대응하나요

하위 호환 여부로 나눕니다.

  • 컬럼 추가는 대체로 안전 — 기존 쿼리가 깨지지 않습니다
  • 컬럼 삭제·이름 변경·타입 변경은 위험 — 다운스트림을 전부 찾아야 합니다

컬럼 리니지(어느 대시보드·잡이 이 컬럼을 쓰는지)를 알고 있으면 영향 범위를 빨리 판단할 수 있습니다. 데이터 카탈로그를 두는 이유가 이것입니다.

변경이 불가피하면 새 컬럼을 추가해 병행 운영하다가, 소비자가 전부 옮긴 뒤 옛 컬럼을 지우는 단계적 방식이 안전합니다.