Sourceを定義しよう:dbt-osmosisで自動生成

こんにちは、六本木アナリティクスエンジニアのTaku(@aelabdata )です。

dbt入門の第6回です。前回は、成功するdbtプロジェクトを構築するための2つの重要な概念、「3層構造」と「ディメンショナルモデリング」について解説しました。今回からデータモデリングをしていきましょう。

成功するdbtプロジェクトの秘訣:3層構造とディメンショナルモデリング

第4回で、dbt seedコマンドを使い、raw_customersraw_ordersraw_itemsいう3つのCSVファイルをデータウェアハウス(DuckDB)にロードしました。

dbt seedでcsvデータをロードしよう

データはウェアハウス内に存在しますが、dbtはこれらの生データをどのように正式に認識し、管理するのでしょうか?

ここで登場するのがSourceです。Sourceは、dbtが外部からロードされた生データに接続するための最初のステップです。これを定義することで、データ変換パイプライン全体の強固な基盤を築くことができます。

この記事では、以下の2つの目的を達成します。

  1. Sourceの基本的な概念と、データモデリングにおける重要性を解説します。
  2. Jaffle Shopプロジェクトのために、sources.ymlファイルを実際に作成するハンズオンガイドを提供します。

それでは、まずはSourceの概念的な理解から始めましょう。

Sourceの概念と重要性

コードを書き始める前に、「なぜ」Sourceを定義するのかを理解することが不可欠です。このセクションでは、Sourceとは何かを明確にし、それを明示的に宣言することによって得られる強力なメリットを解説します。

Sourceとは何か?

Sourceとは、一言で言えば、外部のシステムによってデータウェアハウスにロードされた生データを表現するdbtの機能です。これらはdbtの変換(transformation)によって作成されたデータではなく、データパイプラインの出発点となるデータそのものを指します。

通常、これらの生データはFivetranやAirbyteのようなELTツールによって、アプリケーションのデータベースやSaaSのAPIから抽出・ロードされます。このJaffle Shopチュートリアルでは、学習の便宜上dbt seedコマンドを使ってCSVからデータをロードしました。

なぜソースを宣言するのか? 3つのメリット

ソースの宣言は単なる形式的な作業ではありません。dbtの中核的な機能を最大限に引き出すための戦略的なプラクティスです。主に以下の3つの大きなメリットがあります。

  1. データリネージ(DAG)の確立: dbtモデル内で生データを参照する際、raw.jaffle_shop.raw_customersのようにテーブル名をハードコーディングする代わりに{{ source('jaffle_shop', 'raw_customers') }}関数を使います。これにより、dbtはどのモデルがどのソースデータに依存しているかを正確に把握し、完全な依存関係グラフ(DAG)を構築できます。この結果、生データから最終的なアウトプットまで、データの流れをエンドツーエンドで可視化できます。これは、問題発生時のデバッグ、変更時の影響範囲分析、そしてデータに対する信頼性の構築に不可欠です。
  2. 生データの品質担保(テストとドキュメント): ソースをYAMLファイルで定義することにより、生データテーブルに直接ドキュメント(説明文)やデータテスト(例:not_null, unique)を付与できます。これにより、「ゴミを入れればゴミしか出てこない(Garbage In, Garbage Out)」という事態を防ぎ、パイプラインの最も早い段階でソースデータに関する前提条件を検証できます。データの品質を入り口で担保することは、パイプライン全体の信頼性を確保する上で極めて重要です。
  3. 鮮度の監視: dbtには「ソースの鮮度(Source Freshness)」を監視する機能があります。これを設定すると、ソーステーブルのデータが最後に更新されてからどれくらいの時間が経過したかをチェックできます。例えば、「このソースは24時間以上更新がなければ警告を出す」といったルールを設定できます。これにより、データロード処理が正常に実行されているかを監視し、分析が常に最新のデータに基づいていることを保証するのに役立ちます。

これらのメリットが明確になったところで、次はいよいよ実践です。実際にソース定義ファイルを作成していきましょう。

dbt-osmosisでsources.ymlを自動作成する

ここからは、実際に手を動かしてsources.ymlファイルを作成する手順を解説します。

dbt seedでロードしたraw_customersraw_ordersraw_itemsテーブルを、便利な補助ツールdbt-osmosisを使い、手作業を最小限に抑えながら効率的にファイルを作成する方法を紹介します。

準備:dbt-osmosisの概要とインストール

dbt-osmosisは、dbtプロジェクト内のYAMLファイルの管理を自動化し、手作業による記述の手間を大幅に削減してくれる非常に強力なツールです。これを使えば、データウェアハウス内のテーブル構造をスキャンし、sources.ymlの雛形を自動で生成できます。

dbt-osmosisのようなツールは、単なる時間節約以上の戦略的価値を持ちます。それは、一貫性を保ち、人的ミスを減らし、ソースの数が増えてもプロジェクト構造がスケールし続けることを保証するための重要な選択です。

まず、以下のコマンドでdbt-osmosisをインストールします。

pip install dbt-osmosis

ステップ1:dbt-project.ymlでdbt-osmosis用の設定を追加

次に、dbt-osmosisに対して、どのデータベース/スキーマにある生データをスキャン対象とするか、そして生成したYAMLファイルをどこに保存するかを教える必要があります。この設定をdbt_project.ymlファイルに追記します。

今回の例では、jaffle_shop(顧客と注文データ)とstripe(決済データ)という2つの異なるソースを扱います。

dbt_project.yml
...
# このvarsブロックを追記します
vars:
  dbt-osmosis:
    sources:
      jaffle_shop:
        database: jaffle_shop_tutorial
        schema: main_raw
        path: staging/jaffle_shop/_jaffle_shop__sources.yml
  • database: 生データが格納されているデータベース名を指定します。
  • schema: 生データが格納されているスキーマ名を指定します。
  • path: 生成されるsources.ymlファイルの保存場所とファイル名を指定します。modelsディレクトリ内の、対応するStagingモデルが置かれる場所に配置する(Co-location)のがベストプラクティスです。

Pro-Tip: ファイル名を_jaffle_shop__sources.ymlのようにアンダースコアで始めるのは一般的な慣習です。多くのコードエディタでファイルがディレクトリの先頭に表示されるため、見つけやすくなります。

ステップ2:コマンドを実行してsources.ymlを生成

設定が完了したら、いよいよコマンドを実行してファイルを自動生成します。以下のコマンドをターミナルで実行してください。

dbt-osmosis yaml organize

このコマンドは、dbt_project.ymlvars設定を読み込み、指定されたスキーマ(この例ではmain_raw)に存在するテーブルをスキャンします。そして、jaffle_shopの設定に一致するテーブルをリストアップしたsources.ymlファイルを、pathに自動で生成します。

Pro-Tip: このコマンドはdbt-project.ymlの設定に基づいてすべてのYAMLをリファクタリングします。大規模なプロジェクトでは、ソース定義が常に最新であることを保証するために、このコマンドをGitのpre-commitフックの一部として実行することもあります。

コマンドが成功すると、指定したパスに以下のようなファイルが作成されます。

models/staging/jaffle_shop/_jaffle_shop__sources.yml
version: 2
sources:
  - name: jaffle_shop
    database: jaffle_shop_tutorial
    schema: main_raw
    tables:
      - name: raw_customers
        description: ''
        columns:
          - name: id
            description: ''
...

これにより、Source定義の基本的な骨格(Scaffolding)が提供されます。次のステップは、この自動生成された構造に、人間ならではの貴重なコンテキストを加えて、真に役立つドキュメントへと昇華させることです。

ステップ3:descriptionを追記して完成させる

最後に、自動生成されたファイルをベースに、人間が価値を付与していきます。これは、私たちアーキテクトが設計図に人間が読める注釈を書き加える重要なステップであり、誰もがこの基礎の目的を理解できるようにします。

このソース全体が何なのか、そして各テーブルがどのようなデータなのかをdescriptionとして記述します。この一手間が、プロジェクトの可読性とメンテナンス性を大きく向上させます。

models/staging/jaffle_shop/_jaffle_shop__sources.yml
version: 2
sources:
  - name: jaffle_shop
    database: jaffle_shop_tutorial
    schema: main_raw
    tables:
      - name: raw_customers
        description: Raw customer data containing customer information
        columns:
          - name: id
            description: Unique identifier for each customer
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: name
            description: Customer full name
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
      - name: raw_items
        description: Raw item data containing individual product items in orders
        columns:
          - name: id
            description: Unique identifier for each item
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: order_id
            description: Foreign key to orders table
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: sku
            description: Stock keeping unit - product identifier
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
      - name: raw_orders
        description: Raw order data containing order header information
        columns:
          - name: id
            description: ''
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: customer
            description: Foreign key to customers table
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: ordered_at
            description: Timestamp when the order was placed
            data_type: TIMESTAMP
            config:
              meta: {}
              tags: []
          - name: store_id
            description: Identifier for the store where order was placed
            data_type: VARCHAR
            config:
              meta: {}
              tags: []
          - name: subtotal
            description: Order subtotal amount before tax
            data_type: INTEGER
            config:
              meta: {}
              tags: []
          - name: tax_paid
            description: Amount of tax paid on the order
            data_type: INTEGER
            config:
              meta: {}
              tags: []
          - name: order_total
            description: Total order amount including tax
            data_type: INTEGER
            config:
              meta: {}
              tags: []

descriptionが追記されたことで、このファイルは単なる設定ファイルから、チームにとって有用なドキュメントへと昇華しました。

以上で、dbtプロジェクトの信頼性を高めるsources.ymlの完成です。この「基礎」の上に、これから頑丈なデータモデルを建てていく準備が整いました。

まとめと次のステップ

この記事では、dbtプロジェクトにおけるデータリネージの起点となるSource定義について学びました。

  • Sourceとは何か データウェアハウスにロードされた生データをdbtプロジェクトに知らせるための公式な宣言です。
  • なぜ重要か 完全なデータリネージの確立、生データの鮮度監視、そして生データへのテストとドキュメント化を実現し、データパイプライン全体の信頼性と透明性を向上させるためです。
  • 作成方法 dbt-osmosisのようなツールを活用することで、設定ファイルに基づいて効率的にsources.ymlの雛形を自動生成し、手作業を最小限に抑えることができます。

Sourceの定義は、dbtプロジェクトにおける最初の重要な一歩です。これでデータの入り口が明確になりました。次の記事では、このSource定義を使い、最初のデータ変換レイヤーであるStagingモデルの作成方法を解説します。