データ基盤の設計で最初に決めるべきは「層」の切り方です。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層RawBronze
第2層StagingSilver
第3層MartGold
出自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層の導入を推奨します。中長期の保守性を考えれば、導入の一手間は十分に報われます。