ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SCD (Slowly Changing Dimensions)
    카테고리 없음 2026. 4. 5. 00:13

    4. SCD (Slowly Changing Dimensions)

    4.1 SCD란?

    SCD(Slowly Changing Dimensions)는 데이터 웨어하우스에서 시간에 따라 변경되는 차원 데이터를 어떻게 저장하고 관리할 것인지를 정의하는 기법입니다.

    왜 필요한가?

    현실 세계의 데이터는 끊임없이 변합니다. 고객의 주소가 바뀌고, 직원이 부서를 이동하고, 제품의 가격이 변경됩니다. 이때 핵심 질문은 다음과 같습니다:

    • 변경 전 값을 보존해야 하는가?
    • 어느 시점의 데이터로 분석해야 하는가?
    • 과거 보고서를 재현할 수 있어야 하는가?

    이 질문들에 대한 답에 따라 SCD의 유형(Type 0~7)을 선택합니다.

    SCD 유형 비교 다이어그램

    SCD 유형 비교 다이어그램

    4.2 SCD Type 0 — 원본 유지 (Retain Original)

    정의

    Type 0은 차원 데이터를 최초 적재 이후 절대 변경하지 않는 방식입니다. 원본 값을 영구적으로 유지합니다. "변경 없음(No Change)" 전략이라고도 합니다.

    구현 방법

    최초 적재 시 INSERT만 수행하고, 이후 해당 레코드에 대한 UPDATE는 수행하지 않습니다.

    테이블 예시:

    customer_key customer_id name original_city signup_date
    1001 C-101 김철수 서울 2023-01-15

    김철수가 부산으로 이사해도 original_city는 "서울"로 유지됩니다.

    장점

    • 구현이 가장 단순합니다
    • 최초 상태가 영구 보존됩니다
    • ETL 로직이 간단합니다

    단점

    • 현재 상태를 반영하지 못합니다
    • 변경 이력을 전혀 추적하지 못합니다

    적용 시기

    • 최초 등록 시점의 정보가 절대 변하면 안 되는 경우 (예: 가입일, 최초 등록 지역)
    • 규제상 원본 데이터를 변경 불가한 경우
    • 생년월일, 사회보장번호 등 본질적으로 변하지 않는 속성

    4.3 SCD Type 1 — 덮어쓰기 (Overwrite)

    정의

    Type 1은 변경이 발생하면 기존 값을 새 값으로 단순히 덮어쓰는 방식입니다. 이전 값은 완전히 사라지며, 이력이 보존되지 않습니다.

    구현 방법

    변경 감지 시 해당 레코드를 UPDATE합니다.

    변경 전:

    customer_key customer_id name city phone
    1001 C-101 김철수 서울 010-1234-5678

    변경 후 (김철수가 부산으로 이사):

    customer_key customer_id name city phone
    1001 C-101 김철수 부산 010-1234-5678
    UPDATE dim_customer
    SET city = '부산'
    WHERE customer_id = 'C-101';

    장점

    • 구현이 단순합니다
    • 항상 최신 정보를 반영합니다
    • 저장 공간을 절약합니다
    • 차원 테이블 크기가 증가하지 않습니다

    단점

    • 이력이 완전히 소실됩니다
    • 과거 시점의 보고서를 재현할 수 없습니다
    • 감사(Audit) 요구사항을 충족하지 못합니다

    적용 시기

    • 이력 추적이 불필요한 속성 (예: 오타 수정, 데이터 품질 개선)
    • 최신 값만 중요한 경우 (예: 고객 전화번호, 이메일)
    • 저장 비용이 중요한 소규모 시스템

    4.4 SCD Type 2 — 새 행 추가 (Add New Row)

    정의

    Type 2는 가장 널리 사용되는 SCD 유형으로, 변경이 발생하면 새로운 행을 추가하여 이력을 완전히 보존합니다. 각 행은 해당 버전의 유효 기간을 나타내는 메타데이터 컬럼을 포함합니다.

    구현 방법

    변경 감지 시 기존 행의 유효 종료일을 설정하고, 새로운 행을 INSERT합니다.

    테이블 예시:

    customer_key (SK) customer_id (NK) name city effective_start effective_end is_current
    1001 C-101 김철수 서울 2023-01-15 2024-02-28 N
    1002 C-101 김철수 부산 2024-03-01 9999-12-31 Y
    - Surrogate Key (SK): customer_key — 각 버전을 고유하게 식별하는 대리키            
    - Natural Key (NK): customer_id — 비즈니스 식별자로 동일 엔티티의 여러 버전이 같은 값을 가짐            
    - effective_start / effective_end: 해당 버전의 유효 기간            
    - is_current: 현재 유효한 버전 표시 플래그            
    -- 1. 기존 행의 유효 종료일 업데이트
    UPDATE dim_customer
    SET effective_end = '2024-02-28', is_current = 'N'
    WHERE customer_id = 'C-101' AND is_current = 'Y';
    
    -- 2. 새 행 추가
    INSERT INTO dim_customer
    (customer_key, customer_id, name, city, effective_start, effective_end, is_current)
    VALUES (1002, 'C-101', '김철수', '부산', '2024-03-01', '9999-12-31', 'Y');

    장점

    • 완전한 이력 보존 — 과거 어느 시점이든 재현 가능
    • 포인트-인-타임 분석(Point-in-Time Analysis) 지원
    • 감사 및 규제 요구사항 충족
    • 사실 테이블과의 조인 시 정확한 시점 매핑

    단점

    • 테이블 크기가 지속적으로 증가합니다
    • 쿼리 시 is_current = 'Y' 또는 날짜 범위 조건이 항상 필요합니다
    • Surrogate Key 관리가 복잡합니다
    • ETL 프로세스가 상대적으로 복잡합니다

    적용 시기

    • 이력 추적이 비즈니스에 필수적인 경우 (예: 고객 주소, 직원 부서, 상품 가격)
    • 규제 요구사항 (GDPR, SOX, HIPAA 등)으로 변경 이력 보존이 필요한 경우
    • 시점별 분석이 중요한 경우 (예: "지난 분기 서울 고객의 매출은?")
    • 대부분의 데이터 웨어하우스에서 기본 선택

    4.5 SCD Type 3 — 새 속성 추가 (Add New Attribute)

    정의

    Type 3는 변경이 발생하면 별도의 컬럼에 이전 값과 현재 값을 함께 저장하는 방식입니다. 행은 추가하지 않되, 제한된 이력(보통 직전 1개 값)만 보존합니다.

    구현 방법

    이전 값을 저장할 별도의 컬럼(previous_*)을 추가합니다.

    변경 전:

    customer_key customer_id name current_city previous_city city_change_date
    1001 C-101 김철수 서울 NULL NULL

    변경 후:

    customer_key customer_id name current_city previous_city city_change_date
    1001 C-101 김철수 부산 서울 2024-03-01
    UPDATE dim_customer
    SET previous_city = current_city,
        current_city = '부산',
        city_change_date = '2024-03-01'
    WHERE customer_id = 'C-101';

    장점

    • Surrogate Key가 불필요합니다 (행이 추가되지 않으므로)
    • 현재/이전 값 비교 쿼리가 매우 단순합니다
    • 테이블 크기가 증가하지 않습니다

    단점

    • 직전 1개 변경만 추적 가능합니다 (2번 이상 변경 시 이전 이력 소실)
    • 추적할 속성마다 2개의 컬럼이 필요합니다
    • 여러 속성의 이력을 동시에 추적하면 테이블이 매우 넓어집니다

    적용 시기

    • "이전 값"과 "현재 값"의 비교만 필요한 경우 (예: 직원의 이전 부서 vs 현재 부서)
    • 변경 빈도가 매우 낮은 속성
    • 전체 이력보다 최근 변경 사항만 중요한 분석

    4.6 SCD Type 4 — 히스토리 테이블 분리 (Mini-Dimension / History Table)

    정의

    Type 4는 현재 값은 기본 차원 테이블에, 변경 이력은 별도의 히스토리 테이블에 저장하는 방식입니다. 이를 "미니 차원(Mini-Dimension)"이라고도 부릅니다.

    구현 방법

    2개의 테이블을 운영합니다:

    기본 차원 테이블 (dim_customer) — 항상 최신 값:

    customer_key customer_id name city phone
    1001 C-101 김철수 부산 010-1234-5678

    히스토리 테이블 (dim_customer_history) — 모든 변경 이력:

    history_key customer_id name city effective_start effective_end
    1 C-101 김철수 서울 2023-01-15 2024-02-28
    2 C-101 김철수 부산 2024-03-01 9999-12-31

    장점

    • 현재 값 조회가 매우 빠릅니다 (기본 테이블은 항상 최신)
    • 이력 테이블을 별도로 관리하므로 기본 테이블 성능에 영향 없음
    • 빈번하게 변경되는 대규모 차원에 적합

    단점

    • 2개 테이블 관리의 복잡성
    • 이력 조회 시 별도 테이블 조인 필요
    • 사실 테이블이 두 차원 테이블 중 어떤 키를 참조해야 하는지 결정 필요

    적용 시기

    • 차원 속성 변경이 매우 빈번한 경우 (예: 웹사이트 사용자의 세그먼트, 행동 점수)
    • 현재 값 조회 성능이 최우선인 경우
    • 이력과 현재 데이터의 접근 패턴이 명확히 분리된 경우

    4.7 SCD Type 5 — Type 4 + Type 1 조합 (Mini-Dimension with Outrigger)

    정의

    Type 5Type 4(히스토리 테이블 분리)Type 1(덮어쓰기)을 결합한 방식입니다. 히스토리 테이블로 이력을 관리하면서, 기본 차원 테이블에는 현재 미니 차원의 키를 Type 1 방식으로 항상 최신 상태로 유지합니다.

    구현 방법

    3가지 구성 요소를 운영합니다:

    미니 차원 테이블 (mini_dim_customer_profile):

    profile_key city income_band age_band
    501 서울 중간 30대
    502 부산 중간 30대

    기본 차원 테이블 (dim_customer) — current_profile_key를 Type 1으로 업데이트:

    customer_key customer_id name current_profile_key
    1001 C-101 김철수 502

    사실 테이블 (fact_sales) — 거래 시점의 profile_key를 기록:

    sale_key customer_key profile_key amount
    5001 1001 501 150,000
    5002 1001 502 200,000

    장점

    • 현재 프로필 조회가 빠릅니다 (Type 1의 장점)
    • 과거 시점 분석이 가능합니다 (사실 테이블의 profile_key를 통해)
    • 빈번하게 변하는 속성을 효율적으로 관리

    단점

    • 구현 복잡도가 높습니다
    • 3개 테이블 간의 관계 관리 필요
    • ETL 프로세스가 복잡합니다

    적용 시기

    • 고객 세그먼트, 인구통계 그룹 등 빈번하게 변하는 분석 속성이 있는 경우
    • 현재 값과 과거 시점 값 모두 필요한 경우
    • 대규모 고객 분석 시스템

    4.8 SCD Type 6 — 복합 접근 (Type 1 + 2 + 3 Hybrid)

    정의

    Type 6Type 1(덮어쓰기) + Type 2(새 행 추가) + Type 3(이전/현재 속성)을 모두 결합한 방식입니다. "1+2+3=6"이라는 의미에서 Type 6이라는 이름이 붙었습니다 (Kimball이 명명).

    구현 방법

    Type 2의 이력 행 관리에 Type 3의 현재/이전 컬럼을 추가하고, Type 1 방식으로 모든 행의 현재 값 컬럼을 업데이트합니다.

    테이블 예시:

    customer_key (SK) customer_id name historical_city current_city effective_start effective_end is_current
    1001 C-101 김철수 서울 부산 2023-01-15 2024-02-28 N
    1002 C-101 김철수 부산 부산 2024-03-01 9999-12-31 Y
    - historical_city: 해당 행이 생성된 시점의 값 (Type 2 방식으로 버전별 보존)              
    - current_city: 최신 현재 값 (Type 1 방식으로 모든 행에서 동일하게 업데이트)              

    변경 시 처리 로직:

    -- 1. 모든 기존 행의 current_city를 Type 1 방식으로 업데이트
    UPDATE dim_customer
    SET current_city = '부산'
    WHERE customer_id = 'C-101';
    
    -- 2. 현재 행의 유효 종료일 설정 (Type 2)
    UPDATE dim_customer
    SET effective_end = '2024-02-28', is_current = 'N'
    WHERE customer_id = 'C-101' AND is_current = 'Y';
    
    -- 3. 새 행 추가 (Type 2)
    INSERT INTO dim_customer
    (customer_key, customer_id, name, historical_city, current_city, effective_start, effective_end, is_current)
    VALUES (1002, 'C-101', '김철수', '부산', '부산', '2024-03-01', '9999-12-31', 'Y');

    장점

    • 가장 유연한 분석 지원: 이력 분석, 현재 값 분석, 이전/현재 비교 모두 가능
    • 단일 테이블에서 다양한 분석 관점 제공
    • 복잡한 비즈니스 요구사항에 대응

    단점

    • 구현 및 유지보수 복잡도가 가장 높습니다
    • 변경 시 UPDATE + INSERT를 동시에 처리해야 합니다
    • 저장 공간 및 처리 비용 증가

    적용 시기

    • 다양한 분석 관점이 동시에 필요한 경우
    • "서울에 살 때의 매출"(이력 기준)과 "현재 부산 고객의 전체 매출"(현재 기준)을 모두 조회해야 하는 경우
    • 고급 분석 요구사항이 있는 엔터프라이즈 환경

    4.9 SCD Type 7 — 듀얼 키 방식 (Dual-Key / Hybrid View)

    정의

    Type 7은 사실 테이블에 Surrogate KeyNatural Key 2개의 외래키를 모두 포함시키는 방식입니다. Type 2의 이력 차원 테이블을 유지하면서, 사실 테이블이 이력 버전과 현재 버전 모두에 접근할 수 있게 합니다.

    구현 방법

    차원 테이블 (dim_customer) — Type 2 방식으로 이력 관리:

    customer_key (SK) customer_id (NK) name city effective_start effective_end is_current
    1001 C-101 김철수 서울 2023-01-15 2024-02-28 N
    1002 C-101 김철수 부산 2024-03-01 9999-12-31 Y

    사실 테이블 (fact_sales) — 듀얼 키:

    sale_key customer_key (SK FK) customer_id (NK FK) amount sale_date
    5001 1001 C-101 150,000 2023-06-15
    5002 1002 C-101 200,000 2024-05-20

    분석 유형별 조인 방법:

    -- 이력 기반 분석: Surrogate Key로 조인 (거래 시점의 고객 정보)
    SELECT f.amount, d.city AS city_at_sale_time
    FROM fact_sales f
    JOIN dim_customer d ON f.customer_key = d.customer_key;
    
    -- 현재 기반 분석: Natural Key로 조인 (현재 고객 정보)
    SELECT f.amount, d.city AS current_city
    FROM fact_sales f
    JOIN dim_customer d ON f.customer_id = d.customer_id
    WHERE d.is_current = 'Y';

    장점

    • 이력 분석과 현재 기준 분석을 모두 단일 사실 테이블에서 지원
    • BI 도구에서 분석 목적에 따라 조인 키를 선택 가능
    • Type 2의 완전한 이력 보존 + 현재 값 빠른 접근

    단점

    • 사실 테이블에 2개의 외래키가 필요하여 저장 공간 증가
    • 사용자가 올바른 키를 선택하여 조인해야 하므로 혼란 가능
    • ETL에서 두 키를 모두 관리해야 함

    적용 시기

    • BI 사용자가 분석 관점(이력 vs 현재)을 자유롭게 전환해야 하는 경우
    • 동일 데이터셋에서 이력 보고서와 현재 보고서를 모두 생성하는 경우
    • 고급 분석 플랫폼에서 유연한 차원 접근이 필요한 경우

    4.10 SCD 유형 비교

    유형 이력 보존 구현 복잡도 저장 비용 쿼리 복잡도 대표 사용 사례
    Type 0 없음 (원본 고정) 매우 낮음 최소 매우 낮음 생년월일, 가입일
    Type 1 없음 (덮어쓰기) 낮음 최소 낮음 오타 수정, 이메일 변경
    Type 2 완전 보존 중간 높음 중간 고객 주소, 직원 부서, 상품 가격
    Type 3 직전 1개만 낮음 낮음 낮음 이전/현재 비교가 필요한 속성
    Type 4 완전 보존 (별도 테이블) 중간 중간 중간 빈번한 변경이 있는 대규모 차원
    Type 5 완전 보존 + 현재 키 높음 중간 중-고 고객 세그먼트, 인구통계 프로파일
    Type 6 완전 보존 + 현재/이전 값 높음 높음 중간 다중 분석 관점 필요 시
    Type 7 완전 보존 + 듀얼 접근 중-고 중-고 유연함 BI 분석 관점 자유 전환

    4.11 현대 플랫폼에서의 SCD 구현

    dbt Snapshots

    dbt의 snapshot 기능은 SCD Type 2를 선언적으로 구현할 수 있게 해줍니다.

    -- models/snapshots/snap_customers.sql
    {% snapshot snap_customers %}
    {{
        config(
          target_schema='snapshots',
          unique_key='customer_id',
          strategy='timestamp',
          updated_at='updated_at',
        )
    }}
    SELECT * FROM {{ source('raw', 'customers') }}
    {% endsnapshot %}

    dbt가 자동으로 dbt_valid_from, dbt_valid_to, dbt_scd_id 컬럼을 관리하여 SCD Type 2 이력을 생성합니다.

    Apache Iceberg MERGE INTO

    Apache Iceberg는 테이블 포맷 수준에서 SCD를 효율적으로 구현합니다.

    MERGE INTO dim_customer t
    USING staging_customer s
    ON t.customer_id = s.customer_id AND t.is_current = true
    WHEN MATCHED AND t.city <> s.city THEN
      UPDATE SET effective_end = current_date(), is_current = false
    WHEN NOT MATCHED THEN
      INSERT (customer_id, name, city, effective_start, effective_end, is_current)
      VALUES (s.customer_id, s.name, s.city, current_date(), '9999-12-31', true);

    또한 Iceberg의 Time Travel 기능을 통해 별도의 SCD 구현 없이도 과거 시점의 데이터를 조회할 수 있습니다:

    -- 특정 시점의 테이블 상태 조회
    SELECT * FROM dim_customer
    FOR SYSTEM_TIME AS OF TIMESTAMP '2024-01-15 00:00:00';

    BigQuery

    BigQuery에서는 MERGE 문과 파티셔닝을 활용하여 SCD Type 2를 효율적으로 운영합니다:

    MERGE INTO `project.dataset.dim_customer` t
    USING `project.dataset.stg_customer` s
    ON t.customer_id = s.customer_id AND t.is_current = TRUE
    WHEN MATCHED AND (t.city != s.city OR t.phone != s.phone)
    THEN UPDATE SET t.effective_end = CURRENT_DATE(), t.is_current = FALSE
    WHEN NOT MATCHED BY TARGET
    THEN INSERT (customer_id, name, city, effective_start, effective_end, is_current)
    VALUES (s.customer_id, s.name, s.city, CURRENT_DATE(), DATE '9999-12-31', TRUE);

    Snowflake

    Snowflake의 STREAM + TASK 기능을 활용하면 CDC 기반의 자동화된 SCD 관리가 가능합니다:

    -- Stream으로 변경 감지
    CREATE STREAM customer_changes ON TABLE raw.customers;
    
    -- Task로 SCD 자동 업데이트
    CREATE TASK update_dim_customer
      WAREHOUSE = compute_wh
      SCHEDULE = 'USING CRON 0 * * * * UTC'
      WHEN SYSTEM$STREAM_HAS_DATA('customer_changes')
    AS
      -- SCD Type 2 MERGE 로직
      ...

    4.12 SCD 유형 선택 의사결정 가이드

    의사결정 플로우

    SCD 유형을 선택할 때는 아래의 의사결정 흐름을 따르면 체계적으로 판단할 수 있습니다.

    Q1. 이 속성의 변경 이력을 보존해야 하는가?
    ├── NO → 이력 불필요
    │   ├── 원본 값이 절대 변하면 안 되는가? → YES → Type 0 (원본 유지)
    │   └── 최신 값으로 덮어써도 되는가? → YES → Type 1 (덮어쓰기)
    │
    └── YES → 이력 필요
        ├── 현재 값과 직전 값만 비교하면 충분한가? → YES → Type 3
        ├── 전체 이력 필요 + 주 테이블을 가볍게 유지? → YES → Type 4
        ├── 이력 분석 + 현재 기준 분석을 동시에? → YES → Type 6
        ├── BI 도구에서 이력/현재 관점을 자유롭게? → YES → Type 7
        └── 위 특수 케이스 아님 → Type 2 ← 기본 선택 ⭐

    속성별 SCD 타입 선택 가이드

    속성의 특성에 따라 어떤 SCD 타입을 적용할지 결정하는 실무 가이드입니다.

    속성 유형 예시 권장 타입 이유
    생년월일, 가입일 2000-01-15 Type 0 절대 변하지 않음
    전화번호, 이메일 010-1234-5678 Type 1 이력 불필요
    고객 주소, 직원 부서 서울 → 부산 Type 2 이력 분석 필수
    직전 부서 비교 마케팅 → 영업 Type 3 현재/이전 비교만 필요
    고객 행동 점수 매일 변동 Type 4 빈번한 변경, 성능 유지
    지역별 전체 이력 분석 현재 지역 기준 Type 6 두 관점 동시 필요
    다목적 BI 분석 이력/현재 모두 Type 7 분석 유연성 최대화

    하나의 테이블에서 여러 SCD 타입 혼용 예시

    실무에서는 하나의 차원 테이블 내에서 속성별로 서로 다른 SCD 유형을 적용하는 것이 일반적입니다. 아래는 직원 차원 테이블에서 Type 0, 1, 2, 3을 혼용하는 예시입니다.

    CREATE TABLE dim_employee (
      employee_sk     INT PRIMARY KEY,  -- Surrogate Key (Type 2)
      employee_id     VARCHAR,          -- Natural Key
    
      -- Type 0: 절대 변하지 않는 속성
      hire_date       DATE,
      birth_date      DATE,
    
      -- Type 1: 이력 불필요 (항상 최신값)
      phone           VARCHAR,
      email           VARCHAR,
    
      -- Type 2: 이력 추적 대상
      department      VARCHAR,
      job_title       VARCHAR,
    
      -- Type 3: 직전 값만 보존
      current_manager VARCHAR,
      previous_manager VARCHAR,
    
      -- SCD Type 2 메타데이터
      effective_start DATE,
      effective_end   DATE,
      is_current      BOOLEAN
    );

    플랫폼별 SCD 구현 난이도

    각 데이터 플랫폼에서 SCD를 구현할 때의 난이도와 권장 도구를 정리한 표입니다.

    플랫폼 Type 1 Type 2 Type 6 권장 도구
    BigQuery MERGE MERGE MERGE (복잡) dbt snapshots
    Snowflake MERGE Streams+Tasks Streams+Tasks dbt snapshots
    Databricks MERGE INTO APPLY CHANGES INTO DLT 활용 Delta Live Tables
    Apache Iceberg MERGE INTO MERGE INTO MERGE INTO Spark/Flink
    dbt (공통) - snapshots custom macro dbt-utils

    댓글