#️⃣

SQLMeshを再検証する

に公開

はじめに

普段、業務では dbt core を使ってデータ基盤を運用しています。

昨今、LLMに向けたデータ整備の重要性が増す中で、「もっと高速に、もっと効率的にデータパイプラインを構築できないか?」と模索する日々が続いています。特にLLM開発の現場では、試行錯誤のサイクルを高速に回すことが求められます。

そんな中、SQLMeshによるQuaryの買収[1]、FivetranによるSQLMeshの買収[2]、Fivetranとdbtのパートナーシップ[3]を取り巻くModern Data Stack界隈の激しい変化やニュースを横目に、ふと「そういえば SQLMesh ってどうなったんだろう?」と思い出しました。以前触ったことはありましたが、改めてこのタイミングで再検証してみることにしました。

本記事では、dbtユーザーの視点から、SQLMeshがLLM時代のデータ整備においてどのような選択肢になり得るか、実際に手を動かして感じた「開発体験(DX)」を中心に紹介します。

https://sqlmesh.readthedocs.io/en/stable/

過去の記事
https://zenn.dev/kashira/articles/ad470ce26c5e79

基礎となる知識:Terraform × Git × dbt

この記事は、すでに dbt を業務で使用している、あるいはその概念(DAG、モデル、Transformation)を理解しているエンジニアを想定読者としています。

SQLMeshをdbtユーザー向けに一言で表現するなら、以下の3つの概念の掛け合わせだと言えます。

SQLMesh = Terraform × Git × dbt

  • dbt: SQLを書いてデータを変換し、DAG(依存関係)を構築する。
  • Git: ブランチを切って開発し、マージする(データの持ち方がGitライク)。
  • Terraform: 変更を Plan(計画)し、確認してから Apply(適用)する。

特に、Terraformのような「Plan / Apply」による環境管理こそが、SQLMeshの最大の特徴であり、dbtとは決定的に異なるアーキテクチャの部分です。

個人的に感動した7つの「シンプルさ」と「発明」

久しぶりにSQLMeshを触り、dbtで日々感じていた「痒いところ」が驚くほど解消されていることに感動しました。
特に心が動かされたポイントを、「書き心地」「構造」「開発体験」の3つの視点で紹介します。

【書き心地】 コードが圧倒的にシンプルになる

1. ファイル名とモデル名の分離(ディレクトリが美しい)

地味ですが、ディレクトリ構成にこだわりたいエンジニアにとっては革命的です。

dbtでは原則として「ファイル名 = モデル名」となるため、ユニーク性を保つためにファイル名が長くなりがちでした(例: stg_stripe__payments.sql)。
SQLMeshでは、モデル名はファイル内の MODEL(...) ブロックで定義するため、ファイル名は自由に決められます。

# dbtの場合 (ファイル名で一意性を担保)
models/staging/stripe/stg_stripe__payments.sql

# SQLMeshの場合 (中身で name: stg.stripe.payments と定義)
models/staging/stripe/payments.sql  <-- スッキリ!

名前空間(ディレクトリ)を活かしてシンプルに命名できるため、エディタのサイドバーの見通しが劇的に良くなりました。

2. ref を書かなくていい(コピペで動く)

dbtでは依存関係解決のために {{ ref('model') }} が必須ですが、SQLMeshはSQLをパースして構造を理解するため、生のテーブル名のままで依存関係を解決してくれます

BigQueryコンソールで書いたクエリをそのまま .sql ファイルに貼り付けるだけで動きますし、逆に .sql ファイルのクエリをコンソールに貼り付けても動きます。「ref への翻訳作業」がなくなるだけで、試行錯誤のスピードが格段に上がりました。
また、dbt特有のルールを覚えるのが難しく、生SQLのまま管理しているチームには非常に良いソリューションかなと思いました。

3. Jinjaからの解放とPythonマクロ

「Jinjaで複雑なロジックを書くのは辛い」と感じたことがある人は多いはずです。
私はJinjaマクロでロジックをテストしていますが、テストの実行速度が遅く、成熟したテストフレームワークに乗れないことに課題を感じています。
https://zenn.dev/pixiv/articles/8b4fc2b870b8f9

SQLMeshのマクロは Pythonネイティブ です。

from sqlmesh import macro

@macro()
def pivot_payment_methods(evaluator):
    methods = ["card", "bank", "cash"]
    # Pythonのリスト内包表記でSQLを生成
    return ", ".join([f"SUM(CASE WHEN method = '{m}' THEN amount ELSE 0 END) AS {m}_amount" for m in methods])

使い慣れたリスト内包表記やf-stringを使ってSQLを生成でき、型安全性やテスト容易性が担保されます。「ロジックをプログラミング言語(Python)で制御できている」という安心感は絶大です。

4. YAML地獄からの脱却(Inline Config & Audits)

dbtではモデル定義と設定(YAML)が別ファイルになりがちですが、SQLMeshではSQLファイル内にこれらを同居させることができます。また、ファイル数を1/2に出来るのはすごく嬉しいです。

MODEL (
  name models.users,
  kind FULL,
  audits (assert_positive_order_ids) /* 下で定義したデータテストを登録する */
);
SELECT
  order_id, /* このコメントがカラムのdescriptionに反映される. */
  sku_id, /* 型は自動で推測されるので書く必要はない. BigQuery ARRAY・STRUCTも解釈してくれる */
  ....
FROM orders

/* データテストもここに書ける */
AUDIT (name assert_positive_order_ids);
select * from @this_model
where order_id < 0;

コードと設定/テストが同じファイルにあるため、ファイルを行き来するコンテキストスイッチが発生しません。

【構造】 「賢すぎる」変更検知と環境管理

5. セマンティックな変更検知(コスト削減)

dbtの state:modified よりもさらに踏み込み、SQLMeshは「変更が下流に影響を与えるか?」を理解します。

例えば、上流テーブルにカラムを追加しても、下流テーブルがそのカラムを使用していなければ、SQLMeshは**「下流の再計算は不要」と判断し、スキップします**。
無駄な再実行が防げるため、BigQueryのコンピュートコスト削減や、CI/CD時間の短縮に直結します。

例えば、こんな時に「賢さ」を実感します。

  • 使わないカラムを追加した時
    • 上流のテーブルに新しいカラムを追加しても、下流のテーブルがそのカラムを使っていなければ、SQLMeshは「下流の再計算は不要」と判断してスキップしてくれます。dbtだと無駄にフルリフレッシュが走りがちな場面です。
  • コメントや改行だけの修正
    • SQLの意味(ロジック)が変わっていなければ、変更として検知しません。「コメントを直しただけで全テーブル再計算」という悲劇がなくなります。
  • 特定のカラムロジックの修正
    • あるカラムの計算式だけを直した場合、そのカラムを参照している下流モデルだけを特定して再計算します。関係ないモデルは巻き込まれません。

詳細は本家の記事を読むと良いです。
https://www.tobikodata.com/blog/virtual-data-environments

6. 仮想環境による「ゴミ」のない世界

私が最も「発明だ」と感じたのが、Virtual Environments(仮想環境) の概念です。
SQLMeshは、物理的なデータ実体(Internal)と、それを指すポインタ(View)を明確に分けて管理しています。

  • 物理層: データの実体。ハッシュ付きのテーブル名で保存される(例: models_users__123abc)。
  • 仮想層: 私たちが普段見る devprod 環境。これらは物理テーブルへの View(ポインタ) です。

これにより、開発中に試行錯誤して作った一時的なテーブルは自動的にクリーンアップされ、DWHがゴミテーブルだらけになるのを防げます。また、本番環境へのデプロイも本番環境のRevertも「Viewの向き先を変えるだけ」なので一瞬で完了します。

細かい解説

BigQueryでの「フォルダ(データセット)」の見え方

SQLMeshを導入すると、BigQueryのコンソール(エクスプローラ)は以下のような構成になります。
ポイントは、「物理層は1つだけ」で、「仮想層(環境)」が複数あるという構造です。

参考イメージ
【引用元: Iaroslav Zeigerman, tobiko data Blog, 2023/4/18, Figure 6: Virtual Data Environments end-to-end】

1. 裏側の世界(物理層)

まず、ユーザーは普段あまり意識しなくていい「実体」の置き場所です。SQLMeshが勝手に管理します。

sqlmesh__dwh  <-- 【物理層】dwh Dataset(Schema)に関連するテーブルの実体は全てここに保存される!
 ├── dwh__users__1a2b3c...  (v1: 初期のdwh.usersテーブル)
 └── dwh__users__9z8y7x...  (v2: カラム追加後のdwh.usersテーブル)
 └── dwh__orders__5f6g7h... (dwh.ordersテーブル)

sqlmesh__stg <-- 【物理層】Datasetが別の場合は別のデータセットになる
 └── stg__users__xa2b3c...  (stg.usersテーブル)
  • ここには、ハッシュ値(フィンガープリント)が付いたテーブルが無数に保存されます。
  • これがGitでいう .git/objects (コミットの実体)にあたります。

2. 表の世界(仮想層 / Environment)

私たちが普段クエリを叩いたり、BIツールが参照したりする場所です。これらは**全て「ビュー(View)」**であり、物理層へのポインタです。

CASE 1: 全環境が同じ状態のとき
まだ誰も変更を加えていない平和な状態です。

📂 dwh (prod)         <-- 【本番環境】
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__1a2b3c...

📂 dwh__stg (stg)     <-- 【ステージング環境】
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__1a2b3c...

📂 dwh__dev_kashira (個人) <-- 【開発環境】
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__1a2b3c...
  • ポイント: 全ての環境の users ビューが、物理層の 同じテーブル (...1a2b3c) を指しています。
  • データの実体が1つしかないので、ストレージコストは1つ分です。

※ 私のnicknameはkashiraです。


CASE 2: 個人環境で users を修正したとき
あなたが開発環境で users テーブルにロジック変更を加えたとします(plan して apply した状態)。

📂 dwh (prod)
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__1a2b3c... (古いまま)

📂 dwh__stg (stg)
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__1a2b3c... (古いまま)

📂 dwh__dev_takumi (個人)
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__9z8y7x... (✨ここだけ切り替わる!)
  • ポイント:
    1. SQLMeshは、変更されたロジックに基づいて、新しい物理テーブル ...9z8y7xinternal に作成します。
    2. 個人のViewだけ が、新しい物理テーブルを向くように更新されます。
    3. ProdやStgは古いテーブルを向いたままなので、本番への影響はゼロです。

CASE 3: 本番へデプロイ(Promote)したとき
検証が終わったので、本番へマージしてデプロイします。

📂 dwh (prod)
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__9z8y7x... (✨ここが切り替わる!)

📂 dwh__stg (stg)
 └── 👁️ users (View)  --> 参照先: sqlmesh__dwh.dwh__users__9z8y7x... (✨ここも切り替わる!)
  • ポイント:
    • データのコピー(INSERT INTO ...)は発生しません。
    • ProdとStgの**「Viewの定義(参照先)」を書き換えるだけ**です。
    • だから、何億行あるテーブルでも一瞬でデプロイが完了します。

【体験】 OSSでPro級のDX

7. コストパフォーマンスと爆速Lint

SQLMeshはOSSでVS Code拡張や、Web UIがついてくるので簡単にLintやエラー検知が可能です。
また裏側で使われているSQLGlotはRustで書かれているので、高速に動きます。 sqlfluff の遅さに悩まされていた身としては、入力した瞬間にエラーが返ってくる体験は感動的でした。CursorやVS Codeを中心としたAI時代のコーディングスタイルにも非常にマッチしています。

課題と注意点

ここまで魅力を語ってきましたが、実運用、特にチーム開発への導入を考えると、無視できない課題もあります。

1. ステート管理の壁(スケールさせるためのコスト)

PoCからチーム開発へ移行する際、最大のボトルネックになるのが 「State(状態)の管理」 です。

SQLMeshは「誰がどの環境を使っているか」という情報を管理する必要があります。個人ならローカル(DuckDB)で完結できますが、チーム開発ではコンフリクトします。
解決するには、Postgresをセルフホストするか、有償のTobiko Cloudを利用する必要があります。ここが導入の分水嶺になりそうです。

2. エコシステムと「部分実行」

dbtの強力なエコシステム(パッケージ群)はまだSQLMeshにはありません。
また、個人的にdbt Power Userなどで重宝している 「CTE(共通テーブル式)の一部だけを選択して実行する」 機能が見当たらず、長いSQLのデバッグ時にはdbtの方に分があると感じました。

まとめ

検証を終えての感想を一言で言えば、SQLMeshは 「dbtを使っていて感じる『細かいやりづらさ』を、モダンな設計思想で解消してくれるツール」 でした。

特に「ファイル名の自由度」や「Pythonマクロの書き心地」、「Ref不要のSQL体験」は、一度慣れてしまうと戻りたくなくなるほどの快適さがあります。

将来的なエコシステムの発展や、買収・統合が激しいModern Data Stack界隈における「不安定さ」は懸念材料ですが、以下のようなケースでは試してみる価値が大いにあります。

  • 一人で動かせるプロジェクト: 関わる開発者が少ない。
  • 新規プロジェクト: 負債がなく、最初からモダンな設計で始められる。
  • コードのシンプルさを追求したい: YAML地獄やファイル名の制約から解放されたい。

気になった方は、まずはローカルのVS Codeで、このサクサク動く開発体験を試してみてください。

参考文献

脚注
  1. https://www.tobikodata.com/blog/tobiko-acquires-quary ↩︎

  2. https://www.fivetran.com/press/fivetran-acquires-tobiko-data-to-power-the-next-generation-of-advanced-ai-ready-data-transformation ↩︎

  3. https://www.getdbt.com/blog/dbt-labs-and-fivetran-merge-announcement ↩︎

Discussion