Dataformのチーム開発環境を考える
この記事はdatatech-jp Advent Calendar 2022の12日目の記事となります。
はじめに
DataformがGCPIntegratedPreviewとして公開されました。
チーム開発を考えた時、dbtのSaaS版では$50/Monthかかりますが、Dataformは無料*のため非常にリーズナブルです。
*ただし、当然BigQueryのクエリコストなどはかかってきます
しかし、このGCP版ですがPreview版のためチームで運用するにはまだ少し課題があると感じています。この記事ではその課題感と、それに対して暫定的にどういう対応をとっているのかを共有したいと思います。
Dataformとは
ELT[1]を実現してくれるツールです。元々はSaaS版が公開されていましたが、2020年にGoogleに買収されGCPへのインテグレーションが進められており[2]、現在SaaS版はサインアップはクローズとなっています。
他に有名なELTツールとしては前述したdbt[3]もあり、こちらの方がコミュニティとしては活発な印象はありますが、DWHとしてBigQueryを採用しているチームならばネイティブに連携できてかつ無料で使えるという点でDataformのメリットが出てきやすいのかなと思ってます。
※Dataformの活用記事はまだまだ少ない気がするので皆さん是非🙏
GCP版Dataformに感じる課題
Dataformより少し立ち戻って、チーム開発でELTを行う上で自分が求める要素として考えたのは以下です。
- データ整形をコードで管理でき、トレーサビリティや再利用性を担保できる
- 本番テーブルへの反映は適切な承認プロセスを強制したい
- 開発者が安全かつ容易にデータ整形の試行錯誤が可能
この内、1はELTツールを使うのであればある程度自然に解決します。
問題なのは2と3です。
現状のGCP版Dataformでは開発用のコンソールが提供されておりGit連携などが既に実装されています[4]。開発者は各リポジトリに対してブランチをワークスペースという形で定義し、その中で作業をすることになります。各ワークスペースでの開発が終了した際には、開発者はコンソール上で該当ワークスペースに対応するブランチからdefaultブランチへのPRを作成することができます。これによってコード自体に承認フローを組み込むことが可能ですが、下図左側のBigQueryへのsqlxの実行に関してはコンソール上から承認フローなしで直接実行できてしまうため、下手をすれば他の開発者の作業とバッティングしデータの上書きを行ってしまうなど、事故のリスクがあります。
また、「3. 開発者が安全かつ容易にデータ整形の試行錯誤が可能」という点については、本番テーブルに影響を与えないように開発者が独立に検証できるように環境の分離などが実現できると良さそうです。この部分に関しては、元々のSaaS版のDataformでは特定のブランチと環境を紐付けて管理する機能が存在していましたが、GCP版では一旦外されています。
※ただし、以下のTweetによると今後順次統合予定のようです。
将来的に機能統合されていったタイミングである程度緩和されそうな問題意識ではありますが、上記のような課題を感じているGCP版をそのまま使うのは現時点では難しいという判断をしました。
どうしたか
とはいえコストの観点やDataformの機能自体への不満は特になかったため、暫定的にDataformのCLI版で簡易なCI/CDを組み、GCP版の機能が拡張されチーム開発で十分に使えると判断できたタイミングでスムーズに移行できるようにしておく方針にして、現在チームでの運用を始めています。
では、どのように運用しているかということですが、以下の図のようにCI/CDを組んでいます。
開発者は検証したいタイミングでCD for devを手動でトリガし検証用のdev
環境へデータを反映して確認作業を行います。その際にslackで作業時間を事前に通知し、作業中はワークフローのトリガをdisableにして作業が混じらないようにするなどの開発者ガイドを定めて運用しています*。
*ブランチ毎の環境を作って、完全独立に作業できるようにしてしまうという道もありましたが、コストを考えた結果今回のような実装をとっています。
この仕組みでは、事故が起きない程度に最低限度のものにすることを念頭に置きつつ以下の事柄にフォーカスしました。
- 簡単なコードのチェックとテストが自動で実行されること
- 環境を分離して開発者が安全に試行錯誤できること
- 本番テーブルへの反映はreviewがプロセスとして必須となること
- 変更内容に応じたテーブルへの反映が可能なこと
順に説明していきます。
簡単なコードのチェックとテストが自動で実行される
main
ブランチへのPR openをトリガにしてcompile
、test
、run --dry-run
の3つを実行しています。
環境を分離して開発者が安全に試行錯誤できる
run
コマンドオプションの--schema-suffix
を使います。これにより、任意のsuffixを付与されたスキーマが生成されます。
これを上図のように、CD for devの時はdev
、CD for prodの時はprod
として実行されるように組みます。
本番テーブルへの反映はreviewがプロセスとして必須
これはBranch protection ruleを設定しmain
ブランチを保護した上で、前述したCD for prodをmain
ブランチへのマージと紐づければ良いだけです。
変更内容に応じたテーブルへの反映が可能
現状で起こり得そうな変更操作としては以下が考えられました。
- workflowなどデータに関係ない更新
- full-refreshなデータ更新
- incrementalなデータ更新
これら、それぞれの変更操作に対してブランチの命名規則を用いて対応づけています。
具体的には、
- workflowなどデータに関係ない更新 ->
nodeploy/*
- full-refreshなデータ更新 ->
refresh/*
- incrementalなデータ更新 ->
{refresh,nodeploy以外}/*
これらのブランチ名に応じてCDを制御します。
実装
あとは、以下を参照しつつ順番に上記の事柄を書いていくだけです。
まず、CIは以下のように実装すれば良いでしょう。name: CI
on:
pull_request:
branches:
- main
jobs:
compile-test-dry-run:
runs-on: ubuntu-latest
steps:
- name: Checkout code into workspace directory
uses: actions/checkout@v3
- name: Install project dependencies
uses: docker://dataformco/dataform:latest
with:
args: install
- name: Decrypt dataform credentials
run: gpg --quiet --batch --yes --decrypt --passphrase="$CREDENTIALS_GPG_PASSPHRASE" --output .df-credentials.json .df-credentials.json.gpg
env:
CREDENTIALS_GPG_PASSPHRASE: ${{ secrets.CREDENTIALS_GPG_PASSPHRASE }}
- name: Compile
uses: docker://dataformco/dataform:latest
with:
args: compile
- name: Test
uses: docker://dataformco/dataform:latest
with:
args: test
- name: Dry run
uses: docker://dataformco/dataform:latest
with:
args: run --dry-run
次にCDはdev
/prod
の2系統ありますが、共通項が多いのでworkflow_callで再利用しています。内部でマージ元ブランチ名に応じてデータ更新方法を変えています。
name: CD
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
run-dataform:
runs-on: ubuntu-latest
steps:
- name: Checkout code into workspace directory
uses: actions/checkout@v2
- name: Install project dependencies
uses: docker://dataformco/dataform:latest
with:
args: install
- name: Decrypt dataform credentials
run: gpg --quiet --batch --yes --decrypt --passphrase="$CREDENTIALS_GPG_PASSPHRASE" --output .df-credentials.json .df-credentials.json.gpg
env:
CREDENTIALS_GPG_PASSPHRASE: ${{ secrets.CREDENTIALS_GPG_PASSPHRASE }}
- name: Dataform run full-refresh
if: contains(github.event.head_commit.message,'refresh/')
uses: docker://dataformco/dataform:latest
with:
args: run --run-tests --full-refresh --schema-suffix=${{ inputs.environment }}
- name: Dataform run
if: ${{!contains(github.event.head_commit.message,'refresh/')}}
uses: docker://dataformco/dataform:latest
with:
args: run --run-tests --schema-suffix=${{ inputs.environment }}
次にCD for devです。baseのワークフローをenvironment
変数を与えて実行します。
name: CD for dev
on:
workflow_dispatch:
jobs:
run-dataform:
uses: ./.github/workflows/cd_base.yaml
with:
environment: dev
secrets: inherit
最後にCD for prodです。ほぼCD for devと同じですが、こちらのみnodeploy/*
ブランチに対してはスキップされるようにしています。
name: CD for prod
on:
push:
branches:
- main
jobs:
run-dataform:
if: ${{!contains(github.event.head_commit.message,'nodeploy/')}}
uses: ./.github/workflows/cd_base.yaml
with:
environment: prod
secrets: inherit
最後に
チーム開発において現在のGCP版Dataformで課題に感じていることと、それに対処するためにGithubActions+DataformCLIで簡易なCI/CDを組んだ内容をご紹介しました。
まだ実際にDataformを運用し始めて日が浅いため、これをベースにコツコツと改善していく予定です。とりあえずスケジュール実行やタグ制御などは必要に応じておそらくつけることになりそうかなと思ってます。
とはいえ、依存関係ツリーの可視化とかめちゃくちゃ待ち遠しいので、やはりあまり作り込まずに最低限困らないような感じでやっていきたいなと思ってます。
世界のラストワンマイルを最適化する、OPTIMINDのテックブログです。「どの車両が、どの訪問先を、どの順に、どういうルートで回ると最適か」というラストワンマイルの配車最適化サービス、Loogiaを展開しています。recruit.optimind.tech/
Discussion