ドキュメント・系譜・運用 ― 整える層を組織の資産に
dbtはモデルからドキュメントと系譜(lineage)を自動生成し、freshnessやCIで品質を運用に乗せます。計測設計の連載から本連載のマート、そして各連載へのデータの流れを一枚の系譜にし、誰が見ても辿れる状態を作ります。属人的な「秘伝のタレ」を、組織で再現できる資産へ変える最終回です。
by Shin
整えたマートを、自分一人でなく組織が再現できる資産にするには何が必要か。答えは派手な追加ツールではなく、docs・系譜(lineage)・freshness・CIという、ありふれた運用の4点セットです。これで「辿れる・壊れたら止まる・誰でも読める」状態を作る——それが、第0回で挙げた「秘伝のタレSQL」への最終的な回答になります。この記事では、モデルから自動で立つドキュメントと系譜、データの鮮度を見張るfreshness、変更を守るCI、そして実行の順序を決めるorchestrationを、SOLNA(過去の受託案件をもとに設計した架空D2C)の実装で具体化します。
前回までで、ローデータを staging → intermediate → marts の3層に整え、テストで壊れたデータを止め、増分処理とスナップショットで「過去の状態」まで保てるようになりました。ここまでは「正しく作る」話でした。本回は「作ったものを属人化させない」話です。整える層の価値は、一人の手元で動くことではなく、その人が抜けても組織で再現できることにあります。
モデルからドキュメントと系譜が立つ
dbtの運用上の最大の利点は、ドキュメントと系譜を別途メンテせず、モデルそのものから自動で立てられることです。ref と source で依存を宣言してあれば(第1回)、それがそのまま有向グラフ(DAG)になります。手で描いた構成図は更新が止まった瞬間に嘘になりますが、コードから生成される系譜は嘘をつきません。ここがソフトウェア工学の作法を分析に持ち込むことの実利です。
ドキュメントは、テストを書いた schema.yml(第3回)に説明を足していくだけで充実します。カラムの意味は、繰り返し使う定義を docs ブロックに切り出しておくと、複数モデルから同じ説明を参照できます。同じ説明を二度書かない——DRY(第1回)はドキュメントにも効きます。
# models/marts/_marts__models.yml
models:
- name: customers
description: "{{ doc('customers_grain') }}"
columns:
- name: customer_id
description: 顧客の自然キー。CRM由来。下流の結合の基準。
tests:
- unique
- not_null
- name: ga_client_id
description: "{{ doc('join_key_bridge') }}"
- name: true_cac
description: 実成約あたりの獲得コスト。媒体CVではなくCRMの実成約で割る。{% docs join_key_bridge %}
`customer_id`(CRMの実成約側)と `ga_client_id`(GA4の行動側)を突き合わせる接合キー。
両者が辿れた割合を「接合率」と呼び、テストで常時監視する(第3回)。
ここが下流の真のCAC・予測・因果の前提になる。
{% enddocs %}そして、ドキュメントを「読み物」から「運用の地図」に変えるのが exposures です。exposures は、マートの下流の消費者——ダッシュボード、予測モデル、Reverse ETLの同期——を宣言する仕組みで、「このマートを壊すと誰が困るか」を系譜の中に可視化します。本連載が根幹で、他連載が枝であることが、ここで運用情報として立ち上がります。
# models/marts/_marts__exposures.yml
exposures:
- name: true_cac_dashboard
type: dashboard
description: 真のCAC(媒体×サイト×CRM)のダッシュボード。決裁の参照元。
depends_on:
- ref('customers')
- ref('orders')
- ref('ad_spend')
owner: { name: Shin }
- name: churn_prediction
type: ml
description: 解約・CVR予測(Kaggle連載)。customers/subscriptions を学習に使用。
depends_on:
- ref('customers')
- ref('subscriptions')
- ref('sessions')
owner: { name: Shin }
- name: reverse_etl_audience
type: application
description: 活性化の連載のオーディエンス同期。マート定義がそのまま配信対象になる。
depends_on:
- ref('customers')
- ref('subscriptions')
owner: { name: Shin }exposures を宣言しておくと、dbt build --select +exposure:true_cac_dashboard のように「あるダッシュボードが依存するモデルだけ」を選んで実行・検証できます。逆に、あるマートを変更しようとしたとき、その変更がどの下流に波及するかが系譜から一目で分かる。これが「壊しても気づかない」(第0回の症状3)の構造的な対策になります。
価値連鎖の全体を一枚にすると、整える層がどこに立っているかが明確になります。計測設計の連載(なぜ計測は静かに壊れるのか ― 計測設計という土台)(データが生まれる前)が左端、本連載のマートが中央の根幹、各連載がそれを使う枝です。
[計測①: 収集・同意・識別・送信]
│ GA4 BQ Export / 広告API / Shopify / CRM(= source)
▼
stg_*(staging:ローデータに触れる唯一の層)
▼
int_*(intermediate:再利用する結合・集計)
▼
┌─────── marts(事業のことば=根幹)───────┐
│ customers / orders / sessions │
│ subscriptions / ad_spend / messages / reviews │
└───────────────┬───────────────────────┘
├─▶ true_cac_dashboard (記述・予測編 / 決裁)
├─▶ churn_prediction (Kaggle連載=作る)
├─▶ causalimpact_analysis (因果連載=測る)
└─▶ reverse_etl_audience (活性化の連載=動かす)ここでは静的な系譜図で十分です。対話的に辿れる版が必要になるのは、モデル数が数十を超えて依存が一望できなくなってからです。
データの鮮度(freshness)を見張る
マートが論理的に正しくても、素材が古ければ出力は古い。テスト(第3回)が「中身が壊れていないか」を見るのに対し、freshness は「いつ更新されたか」を見る仕組みです。両者は別の故障を捕まえます。スキーマは正しいのに昨日からデータが来ていない、というサイレント障害は、テストではなくfreshnessで止めます。
freshnessは source に対して宣言します。最終更新時刻を持つカラム(loaded_at_field)を指定し、警告と失敗の閾値を分けて置くのが運用の要です。警告は人を呼ぶだけ、失敗はパイプラインを止める——第3回の「警告 vs 失敗」の設計が、ここでも効きます。
# models/staging/_sources.yml
sources:
- name: ga4
description: GA4 BigQuery Export。日次バッチで前日分が入る。
tables:
- name: events
loaded_at_field: _PARTITIONTIME
freshness:
warn_after: { count: 26, period: hour }
error_after: { count: 36, period: hour }
- name: ad_platform
description: 広告各媒体のAPI連携。数時間ごとに更新。
tables:
- name: spend
loaded_at_field: synced_at
freshness:
warn_after: { count: 6, period: hour }
error_after: { count: 12, period: hour }閾値はそのソースの更新頻度から逆算します。GA4 BQ Exportは日次なので、24時間に多少の遅延を見て警告26時間・失敗36時間。広告APIは数時間ごとなので警告6時間・失敗12時間、というように。私が運用している都内のオフィス仲介の受託案件(匿名化・数値はレンジで丸めています)では、日次ソースの遅延が常態化していた時期に、まずfreshnessで「遅れている」事実を可視化することから始めました。閾値は固定値ではなく、最初は緩めに置いて、実際の到着分布を見ながら数週間かけて締めるのが現実的です。N=1の運用なので、この数字をそのまま自社に当てはめないでください。要点は値ではなく、鮮度を「気づいたら古かった」から「閾値で止まる」へ移すことです。
CIで守る(PRでテストを回す)
ここまでの仕組み(テスト・docs・freshness)は、実行されなければ意味がありません。手で回すと、忙しい日に飛ばされ、飛ばした日に壊れます。だから変更のたびに自動で回す——CI(継続的インテグレーション)です。発想は単純で、変更を本番のマートに反映する前に、変更したモデルとその下流だけをテスト環境でビルドしてテストを通す。通らなければマージしない。
鍵は「変更したモデルとその下流だけ」を選ぶことです。プロジェクト全体を毎回フルビルドするとCIが重くなり、重いCIは回避されます。dbtは前回の状態(manifest.json)と比較して、変更されたモデルだけを選択できます。
# CIで回す中身(概念)。本番マニフェストと比較し、差分とその下流だけをビルド&テスト
dbt build --select state:modified+ --defer --state ./prod-manifest
# 変更が exposure に波及するかも同時に検証できる
dbt build --select state:modified+,+exposure:true_cac_dashboard --state ./prod-manifestGitHub Actions等の設定ファイルの書き方はバージョンで変わるので、手順はdocsへ送ります(本連載の方針)。本文で固定するのは概念だけです——PRを開く、差分モデルとその下流をテスト環境でビルド、テスト失敗ならマージをブロック、通ればマージ、という4ステップ。前述のオフィス仲介案件では、state:modified+ による差分CIで、1回あたりの実行時間を数分のレンジに収められました。テストは汎用テスト(第3回)と接合率の閾値テストを合わせて数十件規模。これも規模依存なので、件数より「壊れた変更は人間が気づく前にCIが止める」という性質のほうが本質です。
なお、この「差分だけ回す」発想を実行レイヤーまで押し広げる仕組みが、次節で触れる dbt State です。PRごとのフルビルドを避ける方向で、CIの実行時間とコストにも効いてきます。
orchestration(いつ・どの順で実行するか)
最後が、本番でいつ・どの順に流すか、です。これも概念に絞ります。原則は3つだけ。
第一に、依存順に流す。dbtは系譜(DAG)を持っているので、staging → intermediate → marts の順序は自分で管理する必要がありません。dbt build に任せれば、依存の上流から順に、テストを挟みながら実行されます。手でジョブの順番を組まないことが、順序ミスの事故を消します。
第二に、鮮度を入口に置く。実運用の並びは、おおむね「source freshnessの確認 → build(モデル生成+テスト)→ docs生成」です。素材が古ければ、その時点で止めて下流のビルドに進ませない。古い素材で作り直したマートは、新鮮な嘘になるだけです。
第三に、失敗時の挙動を決めておく。あるモデルがテストで落ちたとき、その下流を止めるのがデフォルトの正しさです(壊れたデータを流さない=第3回の原則)。一方、無関係な下流は止める必要がありません。失敗を「全停止」か「該当する下流だけ停止」かは、exposures が示す影響範囲から判断します。決裁ダッシュボードに波及するなら止める、まだ誰も使っていない実験的なマートなら警告に留める、という線引きです。
この3原則に加えて、2026年6月、実行の設計に新しい軸が公式に入りました——「変わっていないものを実行しない」です。dbt Labs が発表した dbt State は、各ノードのロジックと素材データを前回のビルドと比較し、変わっていなければ実行をスキップする(あるいは既存のリレーションを再利用する)仕組みです。かつて dbt platform 限定だった state-aware orchestration の後継にあたり、dbt Core でも Fusion エンジンでも、そして任意のオーケストレータでも動くようになりました。「毎回フルで作り直す」前提だった cron 運用に、「変わっていない部分は作り直さない」という選択肢が、特定の実行基盤に縛られない形で加わったことになります。
位置づけを整理しておくと、増分モデル(前回)が「モデルの中で処理する行を差分化する」話だったのに対し、dbt State は「変わっていないモデルの実行そのものを飛ばす」話——別レイヤーの答えであり、両方を併用できます。ただし執筆時点(2026年7月)ではプレビュー段階で、無償ではありません(dbt platform か専用アカウントでの認証を要する有償機能・トライアル提供あり)。導入の判断は提供条件を公式で確認してから。本記事は地図に線を足すに留め、使ってみた所感の深掘りは別記事に譲ります。
スケジューラやマネージドな実行基盤の選定は、規模と予算の話なので本連載の主題ではありません。重要なのは、「依存順・鮮度起点・失敗時の波及制御」という3原則が、どのツールでも変わらないことです。ツールは陳腐化しますが、この3原則は陳腐化しません。
「整える」を属人性から組織の資産へ
ここで連載の最初に戻ります。第0回で挙げた「秘伝のタレSQL」の正体は、定義が散逸し、再現できず、壊れても気づかないという3つの症状でした。本回までに組み上げた仕組みは、その一つひとつへの構造的な回答になっています。
定義の散逸には、schema.yml のdocsと docs ブロックで説明を一箇所に集約することで対応します。再現できない属人化には、ref/source から自動で立つ系譜と exposures で「誰が何に依存しているか」を可視化し、コードとして共有することで対応します。壊れても気づかないサイレント障害には、テスト+freshness+CI で「壊れたら人間が気づく前に止まる」状態を作ることで対応します。
これらはどれも、新しい魔法ではありません。version管理・テスト・CI・ドキュメントという、ソフトウェア開発では当たり前の作法を、分析の変換層に持ち込んだだけです。これは結局、ソフトウェア工学が解いてきた問題と同じものです。アナリティクスエンジニアリングの新しさは概念の新しさではなく、その作法が分析の世界でようやく当たり前になった、という普及の新しさにあります。
そして、整える層は手段であって目的ではありません。属人性から組織の資産へ移したマートの価値は、その上で何が動くかで決まります。整った customers/orders/sessions の上で、初めて予測が安定し、因果が測れ、セグメントが自動で配信される。土台が静かに正しいからこそ、その上の判断が信頼できる——それが、この連載全体の主張でした。
章末チェックリスト
- 系譜と
exposuresで「誰がこのマートに依存しているか」を辿れるか - ドキュメント(説明・docsブロック)が一箇所に集約され、定義の散逸が起きていないか
- freshnessで「素材が古い」を閾値で検知できるか(warn と error を分けたか)
- 変更をCIで自動テストし、壊れた変更がマージ前に止まるか
- orchestrationが依存順・鮮度起点・失敗時の波及制御の3原則に沿っているか
- 整える層を、自分が抜けても再現できる状態にできたか
ここまでで、整える層を「辿れる・止まる・読める」組織の資産にする運用が揃いました。整えた土台の上で「何を測り、何を予測するか」は記述・予測編へ。土台を実際に作る(予測する=Kaggle連載)・測る(因果連載)・動かす(活性化の連載(なぜ「分析して終わり」になるのか ― ループの不在))は、各連載がこのマートを使う側として続きます。投資の是非そのものは、対になる決裁者向けの基幹記事データ基盤に投資すべきかへ。
なお、マートを作っても、KPIの計算をBI・スプレッドシート・Reverse ETLでそれぞれ書けば、また定義はぶれます。その最後の一段——指標定義をdbtに一本化するセマンティックレイヤー——は、任意の発展回(次回)で扱います。