동적 테이블 빠른 시작 모범 사례

동적 테이블 빠른 시작 모범 사례

프로덕션에서 동적 테이블 파이프라인을 구축할 때 다음 사례를 따르세요.

📌 Frozen regions는 이전에 immutability constraints라고 불렸고, FROZEN WHERE 절은 이전에 IMMUTABLE WHERE 구문을 사용했어요. 레거시 IMMUTABLE WHERE 구문은 계속 지원되며, SHOW DYNAMIC TABLES는 여전히 immutable_where 컬럼을 사용해요.

출처: Snowflake 문서

본문

1. 파이프라인의 각 테이블에 적절한 target lag 설정

Target lag은 지연(staleness) 목표이지, 보장된 지연 상한이나 갱신 스케줄이 아니에요. 실제로 필요한 것보다 짧은 lag을 설정하면 가치를 제공하지 않으면서 갱신이 더 자주 일어나고 비용이 더 들어요. 올바른 설정은 파이프라인에서 테이블의 역할에 따라 달라져요:

  • 리프(leaf) 동적 테이블(소비자 지향): 신선도 요구 사항과 일치하는 명시적 lag을 설정하세요(예: TARGET_LAG = '10 minutes'). 이 값이 모든 업스트림 테이블의 갱신 스케줄을 결정해요.
  • 중간(intermediate) 동적 테이블: TARGET_LAG = DOWNSTREAM을 설정해 테이블에 독립적인 갱신 스케줄이 없고 다운스트림 소비자가 신선한 데이터를 필요로 할 때만 갱신되게 하세요. Snowflake는 모든 다운스트림 소비자 중 가장 짧은 target lag에서 타이밍을 도출해 컴퓨팅 크레딧을 낭비하는 불필요한 갱신을 피해요.
-- 중간 테이블: dt_orders_daily가 필요로 할 때만 갱신
CREATE OR REPLACE DYNAMIC TABLE dt_orders
    TARGET_LAG = DOWNSTREAM
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
AS
    SELECT ... FROM raw_orders;

-- 리프 테이블: 전체 파이프라인의 갱신 스케줄을 결정
CREATE OR REPLACE DYNAMIC TABLE dt_orders_daily
    TARGET_LAG = '10 minutes'
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
AS
    SELECT ... FROM dt_orders;

리프 테이블에서는 허용 가능한 가장 긴 lag으로 시작하고 비즈니스 요구 사항이 요구할 때만 좁혀요. 기존 테이블의 lag을 조정하려면 ALTER DYNAMIC TABLE dt_orders_daily SET TARGET_LAG='60 minutes'를 실행해요.

소비자가 없는 DOWNSTREAM 테이블은 절대 갱신되지 않음 DOWNSTREAM으로 설정된 테이블에 다운스트림 소비자가 없으면 자동으로 절대 갱신되지 않아요. 이는 오류나 경고를 만들지 않아요. 쿼리나 대시보드를 직접 제공하는 리프 테이블에는 항상 명시적 target lag을 설정하세요. 전체 세부 정보는 TARGET_LAG=DOWNSTREAM 문서를 참조하세요.

2. REFRESH_MODE를 명시적으로 설정

AUTO는 생성 시점에 INCREMENTAL과 FULL 사이를 선택하는 휴리스틱을 사용해요. 자세한 내용은 동적 테이블 갱신 모드 문서를 참조하세요. REFRESH_MODE를 명시적으로 설정하면 모든 재생성에서 같은 갱신 모드가 보장되고, 쿼리가 지정된 모드를 지원하지 않으면 생성 시점에 오류를 반환해요.

AUTO를 사용한다면 생성 후 SHOW DYNAMIC TABLES로 해결된 모드를 확인하세요. 지침은 동적 테이블 갱신 모드 문서를 참조하세요.

3. 과거 데이터에는 frozen region 사용

동적 테이블에 크고 안정적인 과거 부분과 작은 활성 head가 있을 때 frozen region을 정의하세요. frozen region의 행은 갱신 중 건너뛰어져 컴퓨팅 시간과 크레딧 소비를 모두 줄여요.

frozen region은 다음에 가장 적합해요:

  • 컷오프 날짜보다 오래된 행이 절대 변하지 않는 IoT 텔레메트리나 클릭스트림 로그처럼 롤링 활성 창이 있는 append-heavy 시계열·이벤트 데이터.
  • 위원회 결제 경계(settlement boundary)가 있는 팩트 테이블로, 과거 팩트가 확정되고 차원이 업데이트될 때 최근 팩트만 재계산하면 되는 경우.
  • is_processed = TRUE 또는 status_id IN (3, 4)처럼 행의 하위 집합이 최종(ffinal)인 것으로 알려진 부울 플래그 또는 ID 기반 파티션.
  • 큰 데이터셋에 대한 전체 갱신(full-refresh) 동적 테이블로, frozen region을 건너뛰면 갱신 비용이 크게 줄어드는 경우.

과거 데이터가 자주 변경되고 그 변경이 전파되어야 한다면 frozen region을 피하세요. 동결된 기본 테이블 데이터의 드문 변경은 기본 테이블의 DML이나 동적 테이블의 주기적 전체 갱신으로 처리할 수 있어요. 또한 정의에 frozen 행과 활성 행을 구분하는 자연스러운 컬럼(날짜, 부울 플래그, 또는 ID)이 없을 때도 피하세요. 예를 들어 어떤 행이든 업데이트될 수 있는 사용자 프로필 테이블은 frozen region의 혜택이 없어요.

CREATE OR REPLACE DYNAMIC TABLE dt_events
    TARGET_LAG = '10 minutes'
    WAREHOUSE = transform_wh
    REFRESH_MODE = INCREMENTAL
    FROZEN WHERE (event_date < CURRENT_TIMESTAMP() - INTERVAL '30 days')
AS
    SELECT
        event_id, user_id, event_date, event_type, payload
        ...
    FROM raw_events;

조건자 제한, ALTER 구문, BACKFILL FROM, METADATA$IS_FROZEN 메타데이터 컬럼은 Frozen regions and backfill 문서를 참조하세요.

4. 증분 갱신에는 기본 키 사용

기본 테이블에 RELY 속성이 있는 PRIMARY KEY 제약 조건이 있으면 Snowflake는 그 키 값을 안정적인 행 식별자로 사용해요. 이는 특히 INSERT OVERWRITE로 로드되는 기본 테이블에서 중요한데, 기본 키가 있으면 Snowflake가 덮어쓰기 전후의 키 값을 비교해 실제로 변경된 행만 처리할 수 있기 때문이에요. INSERT OVERWRITE로 로드되는 테이블에 기본 키가 없으면 Snowflake는 변경된 행만이 아니라 매 갱신마다 모든 행을 다시 처리해야 해요.

기본 키를 추가하려면 기본 테이블에 제약 조건을 선언하고 RELY를 설정하세요:

ALTER TABLE dim_customers
  ADD CONSTRAINT pk_dim_customers PRIMARY KEY (customer_id) RELY;

기본 키, 시스템 파생 고유 키, 그리고 이들이 변경 추적에 미치는 영향에 대한 자세한 내용은 동적 테이블 데이터 입력 최적화 문서를 참조하세요.

5. 동적 테이블로 감싸기 전에 쿼리를 독립적으로 테스트

SELECT를 독립적으로 실행하면 오류를 잡고, 출력을 검증하고, 동적 테이블을 만들기 전에 쿼리 복잡도를 대략 파악하는 데 도움이 돼요. 독립 실행 시간은 증분 갱신 시간의 신뢰할 수 있는 예측자는 아니에요. 증분 갱신은 변경된 행만 처리하므로 독립 실행보다 빠를 수 있어요. 하지만 독립 쿼리가 문제를 나타낼 만큼 느리다면(예: 누락된 필터나 비효율적인 조인) 동적 테이블로 감싸기 전에 그 문제를 고치세요.

Snowsight의 쿼리 프로필(실행 시간 탐색)을 확인하거나 INFORMATION_SCHEMA.QUERY_HISTORY_BY_SESSION을 쿼리해 쿼리가 오류 없이 실행되고 예상 출력을 생성하는지 확인하세요.

쿼리가 성능 문제를 드러내면 동적 테이블을 만들기 전에 쿼리를 최적화하세요. 일반적인 쿼리 튜닝 지침은 성능을 위한 웨어하우스 최적화 문서를 참조하세요.

6. 각 동적 테이블을 단일 비행 단위(non-row-wise) 연산으로 제한

비행 단위 연산(GROUP BY, DISTINCT, 윈도우 함수)은 Snowflake가 입력 변경이 출력 행에 어떻게 매핑되는지 추적해야 해요. 서로 다른 키 집합에서 여러 연산을 결합하면 Snowflake가 영향을 받는 출력을 좁힐 수 없어, 갱신이 필요 이상으로 더 많은 데이터를 다시 처리하게 돼요.

일반적으로 각 증분 동적 테이블을 하나의 비행 단위 연산으로 유지하세요. 로직에 여러 연산이 필요하면 더 단순한 동적 테이블의 파이프라인으로 나누세요. 예를 들어:

  • 서로 다른 키에서의 GROUP BY + JOIN: 집계와 조인을 별개의 동적 테이블로 옮겨 각각 하나의 연산만 포함하게 하세요.
  • 서로 다른 컬럼에서의 GROUP BY + DISTINCT: DISTINCT를 업스트림 동적 테이블에 스테이징한 다음 다운스트림에서 집계하세요.

이 예시에 사용된 dt_orders와 dt_orders_daily 테이블은 동적 테이블 만들기 튜토리얼에 정의돼 있어요.

증분 갱신 성능에 영향을 주는 연산자에 대한 지침은 증분 갱신용 쿼리 최적화 문서를 참조하세요.

7. 비용 격리를 위해 전용 웨어하우스 사용

공유 웨어하우스는 갱신 비용을 임시 쿼리 비용과 격리하기 어렵게 만들어요. 전용 웨어하우스를 할당하면 갱신 비용을 독립적으로 추적하고 갱신 워크로드가 다른 쿼리와 경쟁하는 것을 방지할 수 있어요. 큰 초기 갱신이 필요한 증분 동적 테이블의 경우, 안정 상태(st steady-state) 갱신 웨어하우스를 과도하게 크게 만들지 않도록 별도 INITIALIZATION_WAREHOUSE를 지정하는 것을 고려하세요. INITIALIZATION_WAREHOUSE는 전체 갱신(full refresh) 동적 테이블에는 적용되지 않아요. 생성 시점에 CREATE DYNAMIC TABLE의 WAREHOUSE 매개 변수로 웨어하우스를 설정하거나, 나중에 ALTER DYNAMIC TABLE dt_orders SET WAREHOUSE=transform_wh로 변경하세요.

비용 분석을 위해 QUERY_HISTORY 뷰를 웨어하우스 이름으로 필터링해 동적 테이블 갱신 비용만 보세요.

8. 구현을 통제하지 않는 뷰는 DYNAMIC_TABLE_REFRESH_BOUNDARY()로 감싸기

DYNAMIC_TABLE_REFRESH_BOUNDARY()는 감싼 참조를 파이프라인의 갱신 의존성 그래프에서 제거해요. 동적 테이블은 그 지점 아래의 혈통 추적이나 갱신 조정 없이, 갱신 시점에 참조된 객체가 가진 어떤 상태든 읽어요. 그 참조 뒤 객체의 변경(예: 기본 테이블의 변경 추적 비활성화나 중간 동적 테이블 추가)은 파이프라인의 갱신 능력에 영향을 주지 않아요.

같은 시점을 반영해야 하는 조인 입력은 감싸지 마세요. 그 입력들은 감지 않은 채로 두어 같은 조정된 파이프라인 갱신에 참여하게 하세요.

파이프라인 경계에 대한 전체 설명은 DYNAMIC_TABLE_REFRESH_BOUNDARY()로 파이프라인 분리 문서를 참조하세요.

9. 갱신 실패 알림 설정

갱신 실패는 기본적으로 알림을 만들지 않아요. 실패한 테이블은 예외를 발생시키지 않고 target lag보다 뒤처져요. 조기에 실패를 잡으려면 알림을 설정하세요. 예시가 있는 알림 설정은 이벤트 테이블로 알림 설정 문서를 참조하세요.

알림을 만든 후 ALTER ALERT dt_refresh_failure_alert RESUME으로 다시 시작하세요.

10. Cloud Services 비용 모니터링

스케줄링과 변경 감지 검사는 Cloud Services 컴퓨팅으로 실행되며, 복잡한 정의가 있는 테이블은 쿼리 컴파일이 가장 큰 기여자예요. 짧은 target lag과 많은 동적 테이블은 이 지출을 청구 가능한 임계값 이상으로 밀어 올릴 수 있어요. Cloud Services 청구에 대한 자세한 내용은 Cloud Services 청구 문서를 참조하세요. 다음 쿼리로 어떤 쿼리 유형이 가장 많이 기여하는지 확인하세요:

SELECT
    query_type,
    SUM(credits_used_cloud_services) AS cs_credits
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    start_time >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY query_type
ORDER BY cs_credits DESC;

DYNAMIC_TABLE_REFRESH가 Cloud Services 비용을 지배한다면, 공격적인 신선도가 필요하지 않은 테이블의 target lag을 늘리세요. Cloud Services 청구가 동적 테이블과 함께 작동하는 방식에 대한 자세한 내용은 동적 테이블 비용 이해 문서를 참조하세요.

11. 최소한의 중단으로 실행 중인 파이프라인 확장

실행 중인 파이프라인에 테이블을 추가하려면 배포를 차단하거나 불필요한 재초기화를 트리거하지 않도록 주의가 필요해요. 접근 방식은 새 테이블이 파이프라인의 어디에 있느냐에 따라 달라져요.

새 리프 소비자 추가. INITIALIZE=ON_SCHEDULE로 테이블을 만들어 CREATE 문이 초기 갱신이 완료될 때까지 차단하는 대신 즉시 반환되게 하세요(이것이 기본 INITIALIZE=ON_CREATE 동작임). 테이블은 초기 갱신까지 비어 있어요. 대시보드나 다운스트림 소비자를 연결하기 전에 DYNAMIC_TABLE_REFRESH_HISTORY를 쿼리해 초기 갱신이 성공했는지 확인하세요.

ON_SCHEDULE 테이블은 초기 갱신까지 비어 있음 초기 갱신이 완료될 때까지 테이블에 대한 쿼리는 "Dynamic table is not initialized" 오류를 반환해요. 데이터가 있음을 확인하기 전까지 소비자를 테이블에 연결하지 마세요.

중간 테이블 삽입. 새 중간 테이블을 TARGET_LAG=DOWNSTREAM으로 만들어 기존 소비자에서 갱신 스케줄을 상속받게 하세요. 그런 다음 그것을 읽어야 하는 다운스트림 테이블을 COPY GRANTS와 함께 CREATE OR REPLACE DYNAMIC TABLE로 다시 만들어 접근 제어를 보존하세요.

CREATE OR REPLACE는 재초기화를 캐스케이드함 동적 테이블의 CREATE OR REPLACE는 그 테이블과 다운스트림 증분 동적 테이블의 재초기화를 트리거해요. 다운스트림 전체 갱신 동적 테이블은 영향을 받지 않아요. 일시적인 지연 급증이 허용되는 유지 관리 창에 이 작업을 스케줄하세요.

이름 변경, 컬럼 변경, 의존성 재배선을 다루는 안전한 진화 워크플로 전체는 동적 테이블 수정 문서를 참조하세요.

다음 단계

  • 동적 테이블 비용 이해하기
  • 증분 갱신용 쿼리 최적화
  • 동적 테이블 모니터링
  • 동적 테이블 디자인 패턴

더 알아보기 (Learn more)