データ基盤の設計で最初に決めるべきは「層」の切り方です。Raw→Staging→Martという3層構造は、dbtコミュニティが広めた標準パターンで、データ品質の段階を明確にし、保守性・再処理性・影響範囲を大幅に改善します。本記事では各層の設計ベストプラクティスをdbtコード例付きで解説します。
なぜ「層」に分けるのか
層構造の意義は3つあります。第一に「品質段階の明確化」で、どのデータが信頼できるかを階層で示せます。第二に「影響範囲の限定」で、ソース側に変更があってもStaging層で吸収できるためMart層が安定します。第三に「再処理の容易さ」で、障害時にどこから再実行すれば良いかが一目で分かります。
逆にRaw層から直接Mart層を作ると、ソースの小さな変更がMart層に直接波及し、ダッシュボードが次々と壊れる地獄に陥ります。層構造は「保険」ではなく「戦略」です。
3層構造の設計
3層の役割を簡潔にまとめると次のようになります。Raw層はソースの原形保存、Staging層はクリーニングと型変換と命名統一、Mart層はビジネスロジック適用と分析用テーブル作成です。データフローを簡易図で示します。
【3層構造データフロー】
[Source Systems]
SaaS / RDB / API
|
v
[Raw層] -- 原形保存、変換なし、タイムスタンプ付加
|
v
[Staging層] -- 型変換、NULLハンドリング、命名統一
|
v
[Intermediate層(任意)] -- 再利用する共通ロジック
|
v
[Mart層] -- ビジネスロジック、集計、分析用
|
v
[BI / 業務還流]
各層の特徴を表に整理しました。
| 層 | 目的 | データ品質 | 主なアクセス者 | 更新頻度 | dbtプレフィクス |
|---|---|---|---|---|---|
| Raw | 原形保存 | 保証なし | データエンジニア | 取り込みのたび | raw_ / src_ |
| Staging | クレンジング | 型とNULLを整備 | データエンジニア | 日次〜時次 | stg_ |
| Intermediate | 共通ロジック | 整備済み | データエンジニア | 日次 | int_ |
| Mart | 分析用 | 高品質 | 全社員 | 日次 | fct_ / dim_ / mart_ |
dbtでは各層をmodels配下のディレクトリで分けて管理します。最小構成は次のようになります。
-- models/staging/stg_orders.sql
SELECT id AS order_id, cast(amount AS NUMERIC) AS amount, created_at::timestamp AS created_at
FROM {{ source('raw', 'orders') }}
-- models/intermediate/int_orders_enriched.sql
SELECT o.*, c.customer_name FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('stg_customers') }} c USING (customer_id)
-- models/marts/fct_orders.sql
SELECT order_id, customer_id, customer_name, amount FROM {{ ref('int_orders_enriched') }}
Raw層の設計ベストプラクティス
Raw層の鉄則は「触らない」ことです。ソースから取り込んだデータは原形のまま保存し、型変換も命名変更も行いません。これは「壊れた時に元データを参照できる保険」を残すためです。
ただし、取り込み時の監査情報――ロード時刻、ソースファイル名、バッチIDなど――は付加しておきます。こうした監査カラムがあると、後から「このレコードはいつ取り込まれたか」を追跡でき、障害調査が格段に楽になります。また、データ量が多い場合はパーティショニングとクラスタリングを設計しておくことが重要です。
CREATE TABLE raw.orders (
id STRING,
amount STRING,
created_at STRING,
_loaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
_source_file STRING
)
PARTITION BY DATE(_loaded_at)
CLUSTER BY id;
Staging層の設計ベストプラクティス
Staging層はRaw層とMart層の緩衝帯として機能します。役割は型変換、NULLハンドリング、命名規約の統一、不要カラムの除去の4つに集約されます。命名規約は`stg_{source}__{table}`が定着しており、ソース名と元テーブル名を明示することで読み手が元を追えるようにします。
重要なのは「Staging層ではビジネスロジックを入れない」ことです。JOINも集計も入れず、あくまで「綺麗な元テーブル」を作ることに徹します。こうすることで、後段のIntermediate層・Mart層で自由に組み合わせて使えるようになります。
-- models/staging/stripe/stg_stripe__charges.sql
SELECT
id AS charge_id,
customer AS customer_id,
amount / 100.0 AS amount_usd,
created::timestamp AS created_at,
status
FROM {{ source('stripe', 'charges') }}
WHERE status = 'succeeded'
Mart層の設計ベストプラクティス
Mart層は分析利用者と直接接する層です。ここではビジネスロジックを適用し、ダッシュボードや業務還流で使いやすい形に整えます。ドメイン別にテーブルを分割し(finance / marketing / product 等)、ファクト/ディメンションモデリングを採用するのが王道です。
テーブル命名は`fct_{fact}`、`dim_{dimension}`、または業務ドメイン名+`_`の組み合わせが標準です。データモデリング手法の詳細はA-12、ディメンション設計はA-13の記事を参照してください。
3層構造とメダリオンアーキテクチャの対応関係
3層構造とよく混同されるのが、Databricksが提唱するメダリオンアーキテクチャ(Bronze / Silver / Gold)です。両者は本質的に同じ思想で、呼び方が異なるだけです。以下に対応表を示します。
| 観点 | 3層構造(dbt) | メダリオン(Databricks) |
|---|---|---|
| 第1層 | Raw | Bronze |
| 第2層 | Staging | Silver |
| 第3層 | Mart | Gold |
| 出自 | dbtコミュニティ | Databricks |
| 想定環境 | クラウドDWH全般 | レイクハウス中心 |
| ストレージ | テーブル / ビュー | Delta Lake推奨 |
メダリオンアーキテクチャの詳細はC-03の記事で解説しています。どちらの呼び方でもチームが共通認識を持っていれば問題ありません。
まとめ
3層構造は、データ基盤の保守性を大きく左右する基本パターンです。Raw層で原形を守り、Staging層で境界を吸収し、Mart層でビジネスに届ける――この役割分担を徹底することで、ソースの変更に強い基盤が手に入ります。まずは小さなプロジェクトからdbtで実装してみてください。
よくある質問
データ基盤はなぜ3層に分けるのですか?
データの品質段階を明確にし、影響範囲を限定し、障害時の再処理を容易にするためです。各層に明確な責務を持たせることで保守性が向上します。Raw→Staging→Martと段階的に品質を上げていく発想が鍵となります。
3層構造とメダリオンアーキテクチャの違いは?
本質的には同じ思想です。3層構造はdbtコミュニティ発の呼び方(Raw / Staging / Mart)、メダリオンはDatabricks発の呼び方(Bronze / Silver / Gold)です。呼び方の違いでチームが混乱しないように、社内で統一しておきましょう。
Staging層は必ず必要ですか?
小規模ではRaw→Mart直接変換も可能ですが、ソーステーブルの変更影響を吸収しMart層を安定させるためにStaging層の導入を推奨します。中長期の保守性を考えれば、導入の一手間は十分に報われます。