こんにちは。技術本部研究開発部 Architectグループの田辺です。本記事は Sansan Advent Calendar 2024 の 1日目です。弊チームでは新規dbtモデル開発時に dbt run を検証環境で検証してから本番環境へ反映させていました。検証環境にあるデータ量は少なく、ソースとなるプロダクトデータを模したものも検証用のものであるため、そこで期待通りに動作しても本番環境にリリースしてから期待通りでない動作になることがありました。例えば、エッジケースによるデータの不整合・ファンアウトの発生・実際のデータ量に対してはパフォーマンスの問題があるクエリなどが挙げられます。そのため、本番相当の環境でdbt runを安全に検証することに取り組みました。本記事ではその取り組みに関してご紹介します。 まず初めに、開発環境である全社横断データ分析基盤のアーキテクチャをご紹介します。 Amazon Aurora などから S3, Cloud Storage を経由して BigQuery で管理しているデータレイクへプロダクトデータが蓄積されます。その後、dbt ツールを用いてトランスフォーム処理を行い、エンドユーザーが利用する形へデータを加工します。 コードはGitHubリポジトリにて管理されており、mainブランチに統合されたものが本番環境で動作します。
そのため、統合前にステージングプロジェクトで動作確認を行ってからmainブランチへ取り込みます。
しかし、ステージングプロジェクトの持つデータは本番環境のそれと比較して非常に少量だったり、ほとんどなかったりします。
そのため、本番相当の環境でdbt runを安全に検証したい動機がありました。 当該取り組みを行うにあたって、考慮したポイントが3つあります。 dbt run によって生成されたデータに削除漏れの懸念が起きないよう、検証用のデータセットを作成し、デフォルトの有効期限を3日にしてデータがすぐに消えるようにしました。dbt run の実行結果はこのデータセットに格納することで、3日で削除されるため安全です。 検証用データセット。 dbt run 実行時に次のようなコマンドを与えます。 generate_schema.sqlにおいて、varsの値を読み込んで、mode: prod_ci_testを与えた時のみ当該データセットに結果を格納します。 私達のdbtプロジェクトに置いては、モデル名を${スキーマ名}___${テーブル名(エイリアス名)}で定義しています。
本番環境では、モデル名を解析し、異なるスキーマへモデルがデプロイされるようマクロを組んでいます。 本検証においてはデプロイ先のスキーマは固定です。
モデル名(${スキーマ名}___${テーブル名(エイリアス名)})がそのままテーブル名となるようにしています。 私たちのコードでは node.name.split('___')[0]がデータセット名に該当しcustom_alias_nameがモデル名に該当しました。
ここまでで、特定データセットに向けて dbt run を実行することは実現できたと思います。 私たちには feature ブランチで、作成or変更を加えたモデルをテストしたい動機がありました。
そのようなモデルを検知するスクリプトは下記になります。 mainブランチからの変更差分モデルを収集すること、それを dbt run の引数に与える形への成形しています。 変更差分に対して dbt run を実行しています。 このスクリプトによって、変更差分がないときやdbt runによって発生したエラーを拾うことが実現できます。
ここまでで、シェルスクリプトによる実行が実現できました。 これをGitHub Actions で動かせるようにします。 workflow_dispatch をオンにすることで、web UI から手動実行もできますし、次のようにCLI実行も可能です。 当該ワークフローが複数同時に並行起動されても問題にならないよう、concurrencyを適切に設定することを推奨します。git diff が取れるように fetch-depthを適切に設定してください。このようなGitHub Actionsワークフローで検証スクリプトを動かす事が実現できます。 本記事では dbt runの検証を本番相当の環境で安全に検証する取り組みを紹介しました。これまでは新規モデルを開発してもリリースするまで十分な検証が難しく、リリースして本番環境で動作してから問題が発生することもありました。本成果物によって本番相当の環境で安全に検証しつつ、PRなどでGitHub Actionsの証跡をポイントできるようになりました。
チームからの声として、当該ワークフローによる開発体験向上の声は多かったです。 Future Work として、今のアプローチでは差分モデルが多数になったり、そもそも実行の重いモデルを実行するときに長大な実行時間を要したりといった懸念があります。
そのため、十分な検証性を維持しつつ実行時間をリーズナブルにすることが課題です。 Sansanは、創業以来着実に成長を続けており、その成長に伴い扱うデータ量も飛躍的に増加しています。データ基盤の整備やデータの利活用がますます重要性を増しており、エンジニアにとって非常に刺激的でやりがいのある環境が整っています。このようなフィールドで活躍したいとお考えの方は、ぜひエントリーをご検討ください。はじめに
開発環境のご紹介
dbt run テスト
dbt run によって生成されたデータは一定期間で削除する必要がある
resource "google_bigquery_dataset" "ci_test_dbt_results" {
dataset_id = "ci_test_dbt_results"
location = "asia-northeast1"
default_table_expiration_ms = 3 * 24 * 60 * 60 * 1000 # 3 days
}
dbt run --models "$CHANGED_FILES" --target prod --vars 'mode: prod_ci_test'
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- if var('mode', '') == 'prod_ci_test' -%}
{% set custom_schema_name = 'ci_test_dbt_results' -%}
{%- endif -%}
{%- endmacro %}
{% macro generate_alias_name(custom_alias_name=none, node=none) -%}
{%- if var('mode', '') == 'prod_ci_test' -%}
{% set custom_alias_name = node.name.split('___')[0] + '___' + custom_alias_name -%}
{%- endif -%}
{% endmacro %}
開発の影響があるモデルだけテストしたい
set -exo pipefail
git fetch origin main
# Gitの差分からdbtモデルファイルを取得
CHANGED_FILES=$(git diff origin/main...HEAD --name-only --relative | grep 'models/.*\.sql$' | xargs -I {} echo '@{}' | tr '\n' ' ')
if [ -z "$CHANGED_FILES" ]; then
echo "No dbt model files changed."
exit 0
fi
# dbt runを実行し、結果を指定のBigQueryデータセットへ出力
echo "Start dbt run phase"
if ! dbt run --models "$CHANGED_FILES" --target prod --vars 'mode: prod_ci_test'; then
echo "Error running dbt model: $CHANGED_FILES"
exit 1
fi
echo "dbt run completed for changed models."
CHANGED_FILES=$(git diff origin/main...HEAD --name-only --relative | grep 'models/.*\.sql$' | xargs -I {} echo '@{}' | tr '\n' ' ')
dbt run --models "$CHANGED_FILES" --target prod --vars 'mode: prod_ci_test'
GitHub Actions を使いたい
name: dbt_run_test
# git におけるmainと分岐したところから現在のコミットまでのdbtモデルに関する差分を特定データセットへdbt runするワークフロー。
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
jobs:
dbt-run:
name: Run dbt models
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
defaults:
run:
shell: bash
working-directory: 'workflows/dbt'
steps:
- name: Check out source repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # コミットのdiffを取るために取得
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: '${{secrets.GCP_WORKLOAD_IDENTITY_PROVIDER_PROD }}'
service_account: '${{ secrets.GCP_SERVICE_ACCOUNT_PROD }}'
- name: 'Setup gcloud'
uses: 'google-github-actions/setup-gcloud@v2'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dbt
run: |
python -m pip install --upgrade pip
pip install dbt-core==1.8.2 dbt-bigquery==1.8.2
- name: dbt deps # for dbt run
run: |
dbt deps
- name: dbt run
run: |
./../../tools/dbt_run_for_changed_models.sh
gh workflow run "dbt_run_test" --ref ブランチ名
まとめ