🫨

dbtをとりあえず動かしたら混乱したので、何をしているツールなのか整理した

に公開

はじめに

dbt(Data Build Tool)を触ってみた。

  • dbt run が通った
  • view や table ができた
  • docs も見れた

……が、正直よくわからなかった

この記事は、

  • dbtを「とりあえず動かした人」
  • でも「何をしているツールなのか腹落ちしていない人」

向けに、自分が理解するために整理したメモです。


まずは dbt を「動かしてみた」

理解する前に、とにかくローカルで dbt を動かしてみた。
今回は Docker + Postgres を使い、Mac(Apple Silicon)上で検証している。

ディレクトリ構成

<root>
├─ docker-compose.yml
├─ dbt/
│  ├─ Dockerfile
│  └─ profiles.yml

docker-compose.yml

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: dbt
      POSTGRES_PASSWORD: dbt
      POSTGRES_DB: analytics
    ports:
      - "5432:5432"

  dbt:
    build:
      context: ./dbt
      dockerfile: Dockerfile
    platform: linux/amd64
    volumes:
      - ./dbt:/usr/app/dbt
    working_dir: /usr/app/dbt/dbt_lab
    environment:
      DBT_PROFILES_DIR: /usr/app/dbt
    depends_on:
      - postgres
    ports: ["8080:8080"]

Dockerfile

dbt/Dockerfile

FROM ghcr.io/dbt-labs/dbt-postgres:1.7.9

RUN pip install --no-cache-dir "protobuf<5"

profiles.yml

dbt/profiles.yml

dbt_lab:
  target: dev
  outputs:
    dev:
      type: postgres
      host: postgres
      user: dbt
      password: dbt
      port: 5432
      dbname: analytics
      schema: public

Postgres はデータ保存用、dbt は DBに対して操作を行うだけのCLI である。

生データ(raw)を用意する

postgresのコンテナに入って

psql -d analytics -U dbt

でDBに入る。
テーブルとサンプルデータを挿入する。

CREATE TABLE raw_users (
  id SERIAL PRIMARY KEY,
  name TEXT,
  is_active BOOLEAN
);

INSERT INTO raw_users (name, is_active) VALUES
  ('Alice', true),
  ('Bob', false),
  ('Carol', true);

この raw_users は dbtが作るものではない。dbtは「すでに存在する生データ」を前提にする。

dbtの初期化

docker compose run --rm dbt init dbt_lab

いろいろ聞かれるが、上記のprofiles.ymlに記載の内容で入力すればよい。
これで各種ファイルが生成される。

staging(下ごしらえ)モデルを書く

models/staging/stg_users.sql

SELECT
  id,
  name,
  is_active
FROM raw_users
WHERE is_active = true

ここでは不要なデータを落としただけ。集計や計算はしていない。

dbt run を実行する

docker compose run --rm dbt run

これにより dbt は

  • SQLをDBに投げ
  • stg_users という view を作成した

schema.ymlを追加

dbt/dbt_lab/models/staging/schema.yml

version: 2

models:
  - name: stg_users
    description: "raw_users から有効ユーザーだけ抽出したステージングビュー"
    columns:
      - name: id
        description: "ユーザーID"
        tests:
          - not_null
          - unique
      - name: name
        description: "ユーザー名"

集計モデルを書く

dbt/dbt_lab/models/analytics/active_user_count.sql

{{ config(materialized='table') }}

SELECT
  COUNT(*) AS active_user_count
FROM {{ ref('stg_users') }}
  • ref() で依存関係を明示
  • 結果は table として保存

dbt test / docs

docker compose run --rm dbt test
docker compose run --rm dbt docs generate
docker compose run --rm --service-ports dbt docs serve

testはschema.ymlに書いた内容と実際のデータが相違ないかをテストする。
docs を開くと、モデルの依存関係が自動で可視化された。


この時点で、

  • view ができた
  • table ができた
  • docs も見れた

……が、動かして自分の言葉で説明できないと正直よくわからなかった。

  • dbtが何をしているのか
  • なぜ view と table を分けるのか
  • どこにデータが残っているのか

が曖昧なままだった。

そこで、「dbtは何者なのか」を整理することにした。

dbtは何をするツールか(結論)

dbtはデータベースでも、APIでもない。

  • SQLで変換処理を書き
  • 依存関係を明示し
  • view / table を適切に作り分け
  • DBに「使いやすい形」を用意する

ための ビルドツール だった。

  • dbt自体はデータを持たない
  • クエリを受け付けるサーバでもない
  • 実際にデータを持つのは DB(BigQuery / Postgres など)

という点が最重要です。

dbt run は何をしている?

dbt run は、

  1. .sql ファイルを読む
  2. そのSQLを DBに投げる
  3. 結果を view / table としてDBに作る

だけです。

つまり、dbtは「SQLを実行してDBオブジェクトを作る係」

stg(staging)とは何か?

stg_usersのようなモデルは staging(下ごしらえ) を意味します。

stgの特徴

  • rawデータを少し整えるだけ
  • 行数はほぼ元データと同じ
  • 集計はしない
  • 再利用される部品
SELECT *
FROM raw_users
WHERE is_active = true

料理で言うと、材料を洗って切った状態

集計(analytics / mart)とは何か?

一方、active_user_countのようなモデルは 集計結果 です。

集計の特徴

  • 行数が激減(1行など)
  • BIやアプリが直接使う
  • よくクエリされる
SELECT COUNT(*) FROM stg_users

料理で言うと、完成品

なぜ「stgはview、集計はtable」なのか?

BigQueryではクエリコスト=スキャンしたデータ量です。

stgをtableにすると

  • rawの巨大データを保存
  • でも別の集計でまた raw を読む
    → コスト削減にならない

集計をviewにすると

BIが叩くたびに raw をフルスキャン
→ クエリコスト爆増

よくある正解パターン

raw(巨大)

stg(view)

集計(table)
  • stg:保存しない
  • 集計:保存して再利用

ref() は何が嬉しい?

FROM {{ ref('stg_users') }}

これは、**「このモデルは stg_users に依存している」**という 宣言 です。

ref を使うことで:

  • 実行順序をdbtが決める
  • 名前変更に強くなる
  • docsで依存グラフが描ける
    SQLの入れ子を「ファイル分割」しただけ、と考えると理解しやすい。

materialized='table' は何をした?

{{ config(materialized='table') }}

これは dbt に対して、**「このSQLの結果を 物理テーブルとして保存して」**と伝えています。

  • view:毎回計算
  • table:一度計算して保存

BigQueryでは、集計はtableにすることでクエリコストを下げる のが重要です。

毎日データが増えたらテーブルも増える?

増えません。毎日dbt runしても同じテーブルが更新されるだけ、日付付きテーブルが勝手に増えることはない。
ただし、毎日フル再計算するとコストが高いので
→ incremental model や partition を使う
これは別途ちゃんと考える必要があります。

アプリケーションはどう使う?

重要な点としてアプリやBIは dbt を一切意識しない

App / BI

DB(dbtで作られた table / view)

dbtは 事前に整備する係 です。

まとめ

dbtは魔法のツールではなく、

  • SQLを整理し
  • 依存関係を明示し
  • view / table を適切に作り分け
  • コストと可読性を守る
    ための仕組みでした。

「とりあえず動かしたけどよくわからない」状態から、
「何をしているか説明できる」状態になるまでの整理として、
同じように迷っている人の参考になれば幸いです。

Discussion