RAKSUL TechBlog

RAKSULグループのエンジニアが技術トピックを発信するブログです

生成AI で dbt の開発を加速させるための「ガードレール」とTips

この記事はノバセル テクノ場 出張版2025 - Qiita Advent Calendar 2025 - Qiita の 9 日目の記事です。

はじめに

こんにちは。ノバセルでデータエンジニアをしている森田です。

弊社では、Cursor や Claude Code などのコーディングアシスタントを用いた開発に全社的に力を入れており、dbt によるデータモデリングを行う私のチームでも積極的に活用しています。 特に直近で開発した dbt project では、その 9割以上のコードを生成AIによって開発しました。

コードの大部分を AI に任せつつ、可読性や品質を失わずに開発を進めるためには、適切な「ガードレール」を敷くことが重要です。 この記事では、実際に dbt 開発を行う上で私たちが取り組んだ工夫やルールについてお伝えします。

コーディング規約の徹底

dbt で記述する分析用の SQL は長くなりやすく、可読性が低下しがちです。 そのため、AI には明確なコーディング規約を「ナレッジ」として与えています。

具体的には、AI と対話しながらコード開発を行った後、そのセッションでの指摘事項や修正内容を最後にドキュメントへまとめさせ、規約として蓄積しています。

例えば、Import CTE、Functional CTE、Final CTE の3層構造を必ず守るように指示しています。 以下のようなドキュメントを AI にナレッジとして渡すことで、生成されるコードの品質を担保しています。

### 3層構造

with 
    -- ========================================
    -- Import CTEs: ref(), source() のみ
    -- ========================================
    source_a as (select * from {{ ref('upstream_model_a') }}),
    
    -- ========================================
    -- Functional CTEs: ビジネスロジック・データ変換
    -- ========================================
    joined as (
        select a.*, b.additional_field
        from source_a as a
        left join source_b as b using (key)
    ),
    
    aggregated as (
        select dimension_key, sum(metric) as total_metric
        from joined
        group by 1
    ),

    -- ========================================
    -- Final CTE: 出力カラムの明示
    -- ========================================
    final as (
        select
            -- メタデータ
            id, created_at,
            
            -- ディメンション
            dimension_key,
            
            -- メトリクス
            total_metric
        from aggregated
    )

select * from final

**効果**: 責務が明確、デバッグが容易、変更の影響範囲が限定

これは以下の記事などを参考にさせていただいてます。 データ基盤のためのリーダブルSQL

Test の書き方と最適化

dbt の Tests も AI に記述させていますが、単に「漏れなく書いて」と指示すると冗長なコードになりがちです。重要なのは、テストコード自体もリファクタリングの対象とすることです。

具体的には、AI が生成したテストケースに対し、不足分の追加だけでなく、不要・冗長なケースの削除を明示的に指示します。

特に unit_tests は YAML ファイルが肥大化しやすく、可読性の低下を招きます。そのため、以下のような工夫を行っています。

  • カラムの削減: テストに関係のないカラムは削除させる
  • ケースの最小化: カバレッジを保ちながら最小限のパターンに絞る
  • 実行時間の考慮: 不要な unit test は書かない

unit_tests を書くべきかどうかの判断基準として、以下のルールを設けています。

### 判断フローチャート

モデル固有のロジックがあるか?
  ├─ YES → unit_test必要
  │        └─ MECE原則に従ってテストケース設計
  └─ NO  → 次をチェック
           ├─ マクロで既にテスト済みか?
           │   └─ YES → unit_test不要
           └─ シンプルな集約のみか?
               └─ YES → unit_test不要
                        data_testsで品質保証

### チェックリスト

- [ ] モデル固有の複雑なロジックがあるか?
- [ ] マクロで既にテスト済みのロジックではないか?
- [ ] シンプルな`GROUP BY`や`SELECT DISTINCT`のみではないか?
- [ ] 論理的に他のテストでカバーされていないか?

**重要**: unit_test作成後に削除するより、最初から適切な判断をする

これらの判断基準も、セッションの最後にドキュメント化させています。

さらに、unit_tests で macro を overrides するテクニカルな Tips を渡したり、Claude の Skills を定義して TDD(テスト駆動開発)を実践させるなど、AI との開発フローを最適化しています。

Linter の整備

AI に自動でコードを書かせる以上、機械的なチェックを行う Linter の整備も不可欠です。現在のプロジェクトでは、SQLFluff と dbt-project-evaluator を導入しています。

特に dbt_project_evaluator は、データモデルのベストプラクティスがルール化されており、構造的な問題をチェックできるため非常に便利です。

プロジェクトごとの調整も柔軟に可能です。例えば、以下のように dbt_project.yml で設定を行っています。

vars:
  # dbt evaluator config
  dbt_project_evaluator:
    # primary key test を自作のマクロで置き換え
    primary_key_test_macros: [['custom.test_unique_combination_of_columns']]

    # mart の prefix の指定
    marts_prefixes: ['dim_', 'fct_', 'mrt_']

    # 個別のルールの閾値調整
    ## Chained View Dependencies
    ## https://dbt-labs.github.io/dbt-project-evaluator/latest/rules/performance/#chained-view-dependencies
    chained_views_threshold: 10

    ## Model Fanout
    ## https://dbt-labs.github.io/dbt-project-evaluator/0.8/rules/modeling/#model-fanout
    models_fanout_threshold: 5

また、例外的にルールを無視したい場合は、以下のような CSV を seeds に定義して制御することができます。

fct_name,column_name,id_to_exclude,comment
fct_root_models,child,int_calendar,"カレンダー用データは自己完結型でデータソースを必要としない"

Pull Request の分割

生成AI にコードを書かせると、実装スピードが上がる反面、Pull Request (PR) のサイズが肥大化しがちです。これではレビュー担当者の負担が大きくなってしまいます。

そこで私たちのプロジェクトでは、Claude の Custom Command を定義し、PR を自動的に分割して作成するコマンドを用意しました。

このコマンドは、target/manifest.json の parent_map を解析します。モデル間の依存関係を分析した上で、適切な粒度にグループ化し、個別の PR として起票してくれます。

これにより、1つ1つの PR サイズが小さくなり、レビューの心理的・時間的負担が軽減されました。結果として、merge までのリードタイム短縮にも繋がっています。

PR を小さくするためには、そもそも設計段階からコンポーネントを分けチケット分割することが効果的ですが、気づいたら大きくなってしまうケースもありPR分割するコマンドは有用でした。

おわりに

この記事では、生成AI を活用した dbt 開発において、品質と生産性を両立させるための具体的な Tips をご紹介しました。

ここに記載した Tips やルールは、私一人が考えたものではなく、チームメンバーが開発の中で作り上げ、素早く共有してくれたものがほとんどです。 「AI にコードを書かせる」だけでなく、「AI にどのように書かせるかのルール(プロンプトやドキュメント)自体をチームで改善し続ける」 というプロセスこそが、チーム全体の生産性を高める上で最も重要だと感じています。