
Surrogate keys in dbt: Integers or hashes? | dbt Developer Blog
Wondering how to build a data model with surrogate keys? Dave Connors walks you through two strategies.
자연 키(Natural Key)
와 달리, 비즈니스 의미 없이 단순히 데이터 식별자 역할만 수행합니다.전략 | 설명 | 장점 | 단점 | 사용 적합 |
MIISK
(정수형 순차 surrogate key) | 1, 2, 3, ... 처럼 생성되는 정수 기반 surrogate key
(예: Snowflake SEQUENCE 사용) | - 성능 최적화
- 외부 시스템 연동 용이
- 전통적 방식 | - 병렬 실행 시 충돌/갭 발생
- View나 재실행에는 부적합
- 관리 복잡 | 정수형 키가 필요한
외부 연동 또는 복잡한 SCD |
Hash 기반 Surrogate Key | 여러 컬럼을 조합해 md5 해시로 생성 (dbt_utils.generate_surrogate_key) | - 멱등성(idempotent)
- 재실행에도 동일한 결과
- 유지보수 용이
- 병렬 처리 안전 | - 문자열 기반으로 성능/공간 약간 손해
- 이론상 충돌 가능성 | BI 분석, 내부 데이터 모델링,
대부분의 dbt 프로젝트에서 추천 |
select
{{ dbt_utils.generate_surrogate_key(['user_id', 'event_date']) }} as user_event_sk,
...
from {{ ref('stg_events') }}
재현 가능성(idempotency)
과 잘 맞음md5(concat_ws('||', col1, col2, ...))
version: 2
models:
- name: dim_customers
description: 고객 차원 테이블
config:
meta:
surrogate_key: true
create sequence if not exists {{ model.database }}.{{ model.schema }}.{{ model.name }}_seq;
select
{{ increment_sequence() }} as customer_id,
...
ROW_NUMBER() OVER (ORDER BY something)
SELECT my_schema.my_table_seq.NEXTVAL
CREATE TABLE my_table (
id INT AUTOINCREMENT PRIMARY KEY,
...
)
🔍 이유 | 설명 |
멱등성 요구 | 같은 입력 → 항상 같은 출력이 나와야 함 |
병렬 처리 환경 | 순차적으로 증가하는 값을 만들기 어려움 |
dbt의 replace 전략 | IDENTITY 초기화, SEQUENCE 부작용 등 충돌 발생 |
유지보수 어려움 | key 값 일관성 보장과 downstream 영향 관리가 매우 복잡 |
항목 | 키 조합 (Natural Key) | Surrogate Key (추천) |
조인 성능 | 느림 | 빠름 |
컬럼 길이 | 복합키 → 길어짐 | 단일 정수형 |
확장성 | 낮음 (SCD 관리 어려움) | 높음 |
데이터 변경 대응 | 취약 | 유연 |
┌───────────────────────┐
│ dbt_project.yml │
│ meta.surrogate_key: T │
└────────┬──────────────┘
▼
┌────────────────────────────┐
│ macros/generate_sequences.sql │ ← on-run-start
└────────────────────────────┘
▼
┌───────────────────────────┐
│ 모델 내 {{ increment_sequence() }} 사용 │
└───────────────────────────┘
{% macro generate_sequences() %}
{% if execute %}
{% set models = graph.nodes.values() | selectattr('resource_type', 'eq', 'model') %}
{% set sk_models = [] %}
{% for model in models %}
{% if model.config.meta.surrogate_key %}
{% do sk_models.append(model) %}
{% endif %}
{% endfor %}
{% endif %}
{% for model in sk_models %}
{% if flags.FULL_REFRESH or model.config.materialized == 'table' %}
create or replace sequence {{ model.database }}.{{ model.schema }}.{{ model.name }}_seq;
{% else %}
create sequence if not exists {{ model.database }}.{{ model.schema }}.{{ model.name }}_seq;
{% endif %}
{% endfor %}
{% endmacro %}
{% macro increment_sequence() %}
{{ this.name }}_seq.nextval
{% endmacro %}
select
{{ increment_sequence() }} as customer_id,
first_name,
last_name
from source_customers
┌──────────────────────────────┐
│ dbt_utils.generate_surrogate_key() │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ md5(concat_ws('||', col1, col2, ...)) │
└──────────────────────────────┘
select
{{ dbt_utils.generate_surrogate_key(['user_id', 'event_date']) }} as user_event_sk,
user_id,
event_date,
...
from {{ ref('stg_events') }}
md5(cast(user_id as string) || '||' || cast(event_date as string))
Input (stg_customers)
┌────────┬───────────┐
│ cust_id│ name │
├────────┼───────────┤
│ 101 │ Alice │
│ 101 │ Alice Kim │ ← 변경
└────────┴───────────┘
SCD 적용 후 (dim_customers)
┌─────────────┬───────────┬────────────┬────────────┬────────────┐
│ surrogate_sk│ cust_id │ name │ start_date │ end_date │
├─────────────┼───────────┼────────────┼────────────┼────────────┤
│ 1 │ 101 │ Alice │ 2022-01-01 │ 2023-06-01 │
│ 2 │ 101 │ Alice Kim │ 2023-06-02 │ 9999-12-31 │
└─────────────┴───────────┴────────────┴────────────┴────────────┘
{{ config(
materialized='incremental',
unique_key='surrogate_sk'
) }}
with source as (
select *,
{{ dbt_utils.generate_surrogate_key(['cust_id', 'name']) }} as natural_sk
from {{ ref('stg_customers') }}
),
deduped as (
select *,
row_number() over (partition by cust_id order by updated_at desc) as row_num
from source
)
select
{{ dbt_utils.generate_surrogate_key(['cust_id', 'name']) }} as surrogate_sk,
cust_id,
name,
current_timestamp() as start_date,
'9999-12-31' as end_date,
true as is_current
from deduped
where row_num = 1
상황 | 추천 방식 |
내부 분석/BI 중심, 자동화 지향 | ✅ Hash 기반 Surrogate Key |
외부 시스템(PK)과 연동 필요 | ⚠️ MIISK 방식 (정수형) |
기존 DB에 Surrogate Key 존재 | 🔁 해당 값을 그대로 사용 |
매우 큰 데이터셋, 성능 최우선 | ⚠️ 정수형 고려 가능 (단, 관리 복잡) |
dbt에서는 기본적으로 해시 기반 surrogate key 생성 방식을 사용하며, 이는 멱등성, 자동화, 유지보수 관점에서 가장 권장되는 방식입니다. Snowflake SEQUENCE 기반의 MIISK 방식도 가능하지만, 운영/성능/순서 보장이 필요한 상황이 아니라면 일반적으로는 해시 방식이 더 적합합니다.
Surrogate keys in dbt: Integers or hashes? | dbt Developer Blog
Wondering how to build a data model with surrogate keys? Dave Connors walks you through two strategies.
Surrogate Key and Natural Key
28 votes, 18 comments. From my understanding, surrogate keys should be a meaningless unique integer and natural keys should be business unique value…
YUKI’s TIL BLOG