dbt の Source freshness で loaded_at_field を省略してテーブルの鮮度判定を試してみる

dbt の Source freshness で loaded_at_field を省略してテーブルの鮮度判定を試してみる

2026.05.13

はじめに

dbt の Source freshness では、loaded_at_fieldに時刻カラムを指定して鮮度判定を行うのが一般的ですが、loaded_at_fieldを省略してもテーブルメタデータを使った鮮度テストが可能です。
こちらを試してみた内容を本記事でまとめます。

Source freshness

Source freshness は、Source として定義したテーブルのデータが「どれくらい新しいか」を判定する dbt の機能です。warn_after/error_afterの閾値を超えてデータが更新されていない場合に、warn / error として検知できます。

本機能については以下に記載があります。

https://docs.getdbt.com/docs/deploy/source-freshness?version=1.12

Source freshness 機能の主要なポイントは以下のとおりです。

  • loaded_at_fieldにカラムを指定すると、そのカラムの最大値と現在時刻の差で鮮度を判定
  • loaded_at_fieldを省略した場合、アダプタが提供するメタデータベースの判定が使用される
    • Snowflake ではINFORMATION_SCHEMA.TABLES.LAST_ALTEREDが参照されます
  • 閾値はwarn_after/error_afterで設定

loaded_at_fieldの省略は、執筆時点で Snowflake, Redshift, BigQuery (dbt-bigquery version 1.7.3 以降), Databricks (dbt Fusion engine)でサポートされています。

https://docs.getdbt.com/reference/resource-properties/freshness?version=1.12

loaded_at_field省略時の挙動

loaded_at_fieldを省略した場合、Snowflake ではINFORMATION_SCHEMA.TABLESビューのLAST_ALTEREDカラム(テーブルへの最終 DML/DDL 時刻)が鮮度判定に使われます。

https://docs.snowflake.com/ja/sql-reference/info-schema/tables#usage-notes

この方式には以下のような特性があります。

  • 鮮度判定用のカラム(updated_atなど)をテーブル側に持っていなくても利用可能
  • LAST_ALTEREDTIMESTAMP_LTZのため、後述の NTZ 起因の誤判定が発生しない
  • 一方で、DML 他に DDL やバックグラウンドでのメタデータ処理によって最後に変更された日時も記録されるため、業務的な意味でのレコードのタイムスタンプとは別物になる点に注意します

ここでは、以下のパターンで動作を確認します。

# パターン loaded_at_field
1 メタデータベース 省略
2 NTZ 値を指定 updated_at
3 2 のカラムをCONVERT_TIMEZONEで明示的に UTC 変換 convert_timezone('Asia/Tokyo', 'UTC', updated_at)

前提条件

検証環境

以下の環境を使用しています。

  • DWH:Snowflake
  • dbt
    • dbt platform
    • Fusion Stable リリーストラック

テストテーブルの作成

検証用のテーブルを作成し、現在から 2 時間前を表す値を 1 件 INSERT します。

CREATE SCHEMA IF NOT EXISTS RAW.FRESHNESS_TEST;

CREATE OR REPLACE TABLE RAW.FRESHNESS_TEST.ORDERS_NTZ_JST (
    order_id   NUMBER,
    customer   VARCHAR,
    amount     NUMBER(10, 2),
    updated_at TIMESTAMP_NTZ
);

ALTER SESSION SET TIMEZONE = 'Asia/Tokyo';

-- JST で 2 時間前を表す NTZ 値を 1 件 INSERT
INSERT INTO RAW.FRESHNESS_TEST.ORDERS_NTZ_JST VALUES
  (1, 'Alice', 100.00, DATEADD('hour', -2, CURRENT_TIMESTAMP()::TIMESTAMP_NTZ));

LAST_ALTEREDの確認

loaded_at_field省略時に参照されるINFORMATION_SCHEMA.TABLES.LAST_ALTEREDの値を確認します。

SELECT
  TABLE_CATALOG,
  TABLE_SCHEMA,
  TABLE_NAME,
  LAST_ALTERED,
  CONVERT_TIMEZONE('UTC', LAST_ALTERED) AS last_altered_utc,
  ROW_COUNT
FROM RAW.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'FRESHNESS_TEST'
  AND TABLE_NAME   = 'ORDERS_NTZ_JST';

結果は以下のとおりです。

+---------------+----------------+----------------+-------------------------------+-------------------------------+-----------+
| TABLE_CATALOG | TABLE_SCHEMA   | TABLE_NAME     | LAST_ALTERED                  | LAST_ALTERED_UTC              | ROW_COUNT |
|---------------+----------------+----------------+-------------------------------+-------------------------------+-----------|
| RAW           | FRESHNESS_TEST | ORDERS_NTZ_JST | 2026-05-12 23:39:49.272 -0700 | 2026-05-13 06:39:49.272 +0000 |         1 |
+---------------+----------------+----------------+-------------------------------+-------------------------------+-----------+
  • LAST_ALTEREDの型はTIMESTAMP_LTZです。検討時のアカウントデフォルトであるAmerica/Los_Angelesで表示されオフセット-0700が付与されています
  • UTC 換算すると2026-05-13 06:39:49.272 +0000となり、後述の INSERT 直後の時刻とほぼ一致します

データの確認

追加されたサンプルデータを参照すると以下のようになっています。

SELECT
  order_id,
  updated_at                                          AS updated_at_raw_ntz,
  CONVERT_TIMEZONE('Asia/Tokyo', 'UTC', updated_at)   AS updated_at_as_utc,
  CURRENT_TIMESTAMP()                                 AS now_session_tz_jst,
  CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP())        AS now_utc
FROM RAW.FRESHNESS_TEST.ORDERS_NTZ_JST;

+----------+-------------------------+-------------------------+-------------------------------+-------------------------------+
| ORDER_ID | UPDATED_AT_RAW_NTZ      | UPDATED_AT_AS_UTC       | NOW_SESSION_TZ_JST            | NOW_UTC                       |
|----------+-------------------------+-------------------------+-------------------------------+-------------------------------|
|        1 | 2026-05-13 13:39:47.847 | 2026-05-13 04:39:47.847 | 2026-05-13 15:39:49.347 +0900 | 2026-05-13 06:39:49.347 +0000 |
+----------+-------------------------+-------------------------+-------------------------------+-------------------------------+
  • updated_at の値は JST ベースの 13:39:47です
  • CONVERT_TIMEZONE を適用すると UTC 04:39:47 に変換されます
  • クエリ実行時の UTC は 06:39:49 であり、UPDATED_AT_AS_UTC (04:39:47) との差は約 2 時間です(サンプルレコード値は現在から 2 時間前のデータという設定です)

dbt 側で Source を定義

検証用の dbt プロジェクトに Source 定義を作成します。loaded_at_field省略パターンと、比較用の他パターンも追加しています。

source_freshness_test.yml
version: 2

sources:
  # --- 1. loaded_at_field を省略(Snowflake メタデータベース)
  - name: freshness_test_a_metadata
    database: raw
    schema: freshness_test
    config:
      freshness:
        warn_after:  { count: 1, period: hour }
        error_after: { count: 3, period: hour }
    tables:
      - name: orders_ntz_jst

  # --- 2. NTZ 値を loaded_at_field に指定
  - name: freshness_test_b_ntz_raw
    database: raw
    schema: freshness_test
    config:
      freshness:
        warn_after:  { count: 1, period: hour }
        error_after: { count: 3, period: hour }
      loaded_at_field: updated_at
    tables:
      - name: orders_ntz_jst

  # --- 3. convert_timezone で明示的に UTC 変換
  - name: freshness_test_c_ntz_converted
    database: raw
    schema: freshness_test
    config:
      freshness:
        warn_after:  { count: 1, period: hour }
        error_after: { count: 3, period: hour }
      loaded_at_field: "convert_timezone('Asia/Tokyo', 'UTC', updated_at)"
    tables:
      - name: orders_ntz_jst

パターン1ではtables配下に対象テーブル名を書くだけで、loaded_at_fieldを指定していません。これにより、メタデータベースでの判定は行われます。
閾値はwarn_after: 1h / error_after: 3hとしています。レコード自体は 2 時間前のデータですが、INSERT 直後のためテーブルのLAST_ALTEREDは直近となり、パターン1では Pass となる想定です。

dbt source freshnessの実行

以下のコマンドで Source freshness を実行します。

dbt source freshness

実行結果は以下のようになりました。

  • パターン1:Pass
    • loaded_at_field を省略したことで、テーブルの最終変更時刻からの経過時間で判定され、意図通り Pass となった
  • パターン2:Pass
    • TIMESTAMP_NTZ カラムを直接指定。誤判定
  • パターン3:Warn
    • TIMESTAMP_NTZ カラムを UTC 変換し適用。意図通り警告となった

2026-05-13_16h30_01

Freshness of freshness_test_a_metadata.orders_ntz_jst:      pass   ← loaded_at_field 省略:意図どおり pass
Freshness of freshness_test_b_ntz_raw.orders_ntz_jst:       pass   ← NTZ 生値指定:誤判定(後述)
Freshness of freshness_test_c_ntz_converted.orders_ntz_jst: warn   ← UTC 変換適用:正しい判定

target/sources.jsonから各パターンの内部値は以下の通りでした。

パターン max_loaded_at(UTC) snapshotted_at(UTC) time_ago_in_s 判定
1. メタデータ 06:39:49.272Z 06:43:06.429Z 約 3 分前 pass
2. NTZ 値 13:39:47.847Z 06:43:06.510Z 7 時間後の未来扱い pass(誤判定)
3. NTZ 値を convert 04:39:47.847Z 06:43:06.975Z 約 2 時間前 warn

まとめ

パターン1として、判定カラムを省略した場合、以下の特徴で動作することが確認できました。

  • INFORMATION_SCHEMA.TABLES.LAST_ALTEREDmax_loaded_atとして利用
  • 戻り型がTIMESTAMP_LTZのため、ソースカラム側のタイムゾーン設定や型に関係なく判定可能
  • 鮮度判定用のカラムをテーブルに用意する必要がない
  • 計測対象は「テーブルへの最終 DML/DDL 時刻」であるため、業務的な意味でのレコードのタイムスタンプとは異なるので注意が必要

カラム単位の精密な鮮度判定までは不要だが、テーブルが定期的に更新されているかは検知したいというユースケースでは、loaded_at_field省略はシンプル選択肢になると思います。

パターン2では誤判定が発生していました。dbt は NTZ をそのまま UTC として解釈するため、パターン3のように UTC への変換が必要です。
より具体的には、以下の通り誤判定となっていました。

  • 格納値:2026-05-13 13:39:47(NTZ、実態は JST の値)
  • dbt は NTZ をそのまま UTC として解釈し2026-05-13 13:39:47 UTCとみなす
  • テスト実行時の UTC は06:43:06
  • 計算結果:06:43:06 - 13:39:47 = −7 時間となり誤判定

この点は以下でも検証しているため、あわせてご参照ください。

https://dev.classmethod.jp/articles/dbt-cloud-freshness-try-snowflake/

さいごに

dbt の Source freshness でloaded_at_fieldを省略してもテーブル鮮度テストが行えることを Snowflake 上で確認してみました。
こちらの内容がどなたかの参考になれば幸いです。


dbtの導入支援はクラスメソッドにお任せください!

クラスメソッドでは dbt の導入を支援しております。
製品の詳細や支援の内容についてお気軽にお問い合わせください。

dbtの詳細を見る

この記事をシェアする

関連記事