dbt Core の定期実行

dbt Core にスケジューラはない。これは、ちゃんとしたものの中で一番小さい。

dbt Core は CLI までで止まっているので、定期実行は自分で解決することになります。選択肢はたいてい、リトライも履歴も夜間ビルド失敗の通知もない cron か、メタデータ DB を運用することになる本格的なオーケストレーション基盤か。Dagu は状態をファイルに持つ単一バイナリです。

単一バイナリ。dbt の横で動かすメタデータ DB が不要
dbt は普段どおりの CLI のまま、コマンドとして呼ぶ
失敗時も run_results.json を確保するので dbt retry が効く
セルフホストは無料。席数課金もジョブ数課金もなし
01

穴は実在する。dbt Cloud が課金しているのはそこ

dbt Core にスケジューラは含まれていません。本番で動かしているチームは全員が何かしらの起動手段を選んでいて、その選択はたいてい比較検討ではなく納期の都合で決まっています。

  • cron は dbt を起動するだけで、何も教えてくれません。リトライも履歴も、昨夜のビルドが落ちたという通知もない
  • Airflow も Dagster も Prefect も解決しますが、どれもメタデータ DB とスケジューラプロセスと、その先のアップグレード作業をついてきます
  • dbt Cloud は、Core が省いた部分に課金することで解決しています
02

dbt は CLI なので、CLI のまま境界にする

Dagu は dbt を専用のオペレーターで包んだり、dbt のグラフを解析したりしません。ステップは端末で打つのと同じように dbt を実行します。つまりバージョンもフラグも profiles も手元のままで、dbt が変わってもこちら側に直す箇所がありません。

  • 鮮度チェックがビルドの門番になります。ソースが古ければビルドは始まらず、昨日のデータを黙ってモデル化することがありません
  • ビルドには意図的にブラインドのリトライを置いていません。途中まで成功した dbt build を丸ごと再実行すると、成功済みのモデルまで流し直すことになり、問題のなかった処理をやり直すためにウェアハウスの費用が出ていきます
  • handler_on.exit は成功でも失敗でも走ります。成果物が使えるものになるのはそのおかげです
鮮度チェック付きの夜間 dbt ビルド
# dbt-nightly.yaml
schedule: "0 3 * * *"
max_active_runs: 1
working_dir: /opt/analytics/dbt

artifacts:
  enabled: true

env:
  - DBT_PROFILES_DIR: /opt/analytics/dbt/.dbt

steps:
  - id: deps
    run: dbt deps

  - id: freshness
    run: dbt source freshness --target prod
    depends: deps

  # No blind retry here: re-running a partial build re-runs models that
  # already succeeded. Resuming is dbt retry's job, in dbt-retry.yaml.
  - id: build
    run: dbt build --target prod
    depends: freshness

# run_results.json matters most when the build failed, so keep it either way
handler_on:
  exit:
    run: |
      cp -f target/manifest.json target/run_results.json \
        "${context.paths.artifacts_dir}/" || true
  failure:
    run: ./scripts/notify-dbt-failure.sh

mail_on:
  failure: true
03

run_results.json を残す。dbt retry が読むのはそれ

dbt retry は run_results.json を読んで、前回の実行を失敗地点から再開します。ただしそれは、失敗を越えてそのファイルが残っていた場合の話です。素朴に組んだパイプラインがちょうど捨ててしまうのが、まさにその瞬間です。

  • exit ハンドラーが manifest.json と run_results.json を、失敗の有無にかかわらず実行ごとの成果物ディレクトリへ退避します
  • 成果物は実行履歴に紐づくので、失敗を生んだ状態がその失敗の記録と一緒に残ります
  • 再開は、担当者がログからフラグを組み立て直す作業ではなく、小さなワークフロー 1 本になります

前回の実行が 1 つもノードを実行しないまま落ちた場合、たとえばウェアハウスへの接続エラーや権限エラーの場合、dbt retry は何もしません。そのときは再開ではなく最初から流す必要があります。

04

本番の再実行の前に承認を挟む

本番ウェアハウスに対してビルドを再開するのは実費が出る操作なので、全員が押せるボタンであるべきではありません。human.task が、誰かが判断するまでワークフローを止めます。dbt retry は前回の実行設定をそのまま再利用するため、聞く価値のある問いは「そもそも再開するか」だけです。

  • 待っている間プロセスを保持しないので、判断は誰かが起きるまで置いておけます
  • 完了操作は Web UI、REST API、CLI のいずれからでも行え、実施者が記録されます
  • 前回の実行内容を人が組み立て直す必要がありません。target やセレクタを間違えるのはたいていその工程です
承認を挟んで失敗地点から再開する
# dbt-retry.yaml
working_dir: /opt/analytics/dbt

env:
  - DBT_PROFILES_DIR: /opt/analytics/dbt/.dbt

steps:
  # dbt retry reuses the previous invocation's configuration, so the only
  # decision left is whether to spend the credits resuming it.
  - id: confirm
    action: human.task
    with:
      prompt: Resume last night's failed dbt build from its point of failure?

  - id: retry
    run: dbt retry
    depends: confirm

mail_on:
  failure: true
05

ウェアハウスだけで完結しない場合

dbt のスケジュールは、たいてい長い連鎖の 1 ステップです。dbt に流し込む抽出処理、その後のリバース ETL、片端にあるオンプレのシステムは、dbt の担当範囲ではなく、ウェアハウスの担当範囲でもないことが多いはずです。

  • 抽出、dbt、後続の配信を、タイミングを祈る 3 つの無関係な cron ではなく、1 本のワークフローの順序付きステップとして実行します
  • 同じワークフローから SSH でオンプレのデータベースやファイルシステムに届きます
  • dbt 自身のセレクタでモデルをスケジュール分割すれば、毎時のモデルが夜間のフルビルドを待たずに済みます
06

別のツールのほうが合う場合

Dagu はスケジューラであってデータ基盤ではありません。この線引きは意図的なものです。

  • アセット単位のリネージ、データカタログ起点のバックフィル、タスクではなくテーブルを中心に据えた UI が欲しいなら、それは Dagster の設計目標であって Dagu のものではありません
  • すでに Airflow をうまく運用できているなら、メモリ削減のために 2 つ目のオーケストレーターを増やすのは割に合いません
  • スケジューラーは単一インスタンスでリーダー選出を内蔵していないため、夜間ビルドがクリティカルパス上にあるなら可用性の設計は自社で組む必要があります

FAQ

導入前によくある質問

Dagu は dbt のグラフを解析して個別タスクに展開しますか?

しません。これは意図的です。dbt はすでに自分のモデルグラフを解決しているので、それをオーケストレーター側で再現するのは、dbt のリリースに追従するパーサーを抱え込み、しかも微妙に違う 2 つ目のグラフを持つことを意味します。ステップは dbt build を実行し、その内側の順序は dbt が持ちます。粗い分割が欲しい場合は、dbt 自身のセレクタでステップやワークフローを分けてください。

cron を使うのと何が違いますか?

cron はプロセスを起動します。与えてくれないのは、一時的なウェアハウスエラーでのリトライ、失敗したときの通知、3 週間後に見返せる履歴、遅い実行が次の実行に重ならない保証、ソースが古いときにビルドを止める門番です。これらは cron の周りに雑に作り込まれがちなものですが、ここではワークフローの項目になっています。

このために DB が必要ですか?

不要です。Dagu は実行状態をファイルに持つ単一バイナリなので、ウェアハウスの横に用意してバックアップしてアップグレードするメタデータ DB がありません。オーケストレーションの用途が「dbt をちゃんと定期実行する」だけのチームにとって、Airflow・Dagster・Prefect との実務上の差はほぼここです。

dbt をコンテナ内で実行できますか?

できます。ステップにコンテナイメージを指定できるので、dbt のバージョンとアダプタはホストに入っているものではなくイメージ側に固定されます。ワークフローの定義はどちらでも同じです。

dbt docs はどうしますか?

dbt docs generate も 1 つのコマンドなので、他と同じくステップとして実行します。生成されるのは静的ファイルのディレクトリなので、社内ドキュメントを配信している場所に置くか、履歴として残せば十分なら実行の成果物として添付してください。

次のステップ

まず 1 本から始める。

Dagu を入れて、いま cron で動いているスクリプトを 1 本だけ YAML に移し、実際の実行履歴で判断してください。