はじめまして、データエンジニア業務委託のjcです。今回は、Elementaryアップデートに関する試行錯誤の話を共有したいと思います。Elementaryの運用にお困りの方、これからElementaryを導入しようとする方の参考になれば嬉しい限りです。
Elementaryとは
Elementaryはdbtのモニタリングツールの一つであり、データ品質を可視化するためのダッシュボード作成・Slackへのアラート通知機能を提供しています。
先日、データエンジニアの吉田さんがTokyo dbt Meetup #8で「Elementaryを用いたデータ品質の可視化とデータ基盤の運用改善」を題として発表を行いました。Elementaryの活用例に興味のある方はぜひ併せて確認してみてください。
Elementaryアップデートへの道程
Elementaryの初リリースは2022年1月であり、現在(2024年3月17日時点)はまだstable版に至っておらず、後方互換性が担保されてないアップデートもしばしばあります。先日0.9.4から0.13.2にアップデートするのに苦労したため、その際に遭遇したエラーとその対処法を紹介します。
- dbt_columnsを生成する際に
too many subqueries or query is too complex
エラーが発生 - elementaryのon-run-endを実行する際に
too many subqueries or query is too complex
エラーが再び発生 - dbt_run_resultsを生成する際に
The query is too large. The maximum standard SQL query length is 1024.00K characters, including comments and white space characters.
エラーが発生
※DWHはBigQueryを利用しており、RedshiftとSnowflakeは検証していません。
dbt_columnsを生成する際に too many subqueries or query is too complex
エラーが発生
まずはelementaryを0.13.2に上げた後、モデルを実行したらdbt_columnsを生成する際に too many subqueries or query is too complex
エラーが発生しました。
(ここではdbt_columnsの説明を割愛します。ご興味のある方は前述した吉田さんの発表資料にご参照ください)
Maximum number of resources referenced per query are 1,000 resources
Quotas and limits | BigQuery | Google Cloud
調査した結果、BigQueryのサブクエリの上限(1,000)に引っかかっていることがわかりました。
Elementaryのドキュメントに記載されていませんが、ソースコードを参照することで、Artifact(成果物)のアップロードには以下の2つのモードがあることが判明しました。
- chunk: 長いクエリを
dbt_artifacts_chunk_size
で指定されたchuck_sizeに分割して処理する - max_query_size: 設定したクエリ文字列数の上限までギリギリ使い、不足があれば新しいクエリに分割する
デフォルトは max_query_size
モードになっており、文字列数の上限も適切に設定されているようですが、このモードではBigQueryのサブクエリ数の上限を考慮していません。つまり、レコードが短い場合、同じ文字列数の上限でも、一度insertするレコードが増えるためBigQueryの制限にひっかかることがあります。
ログから、エラーメッセージが出る直前、1000以上のレコードをunion allしてからinsertするクエリを確認できました。
2024-02-13 04:39:14.190704 (MainThread): 04:39:14 On master: /* {"app": "dbt", "dbt_version": "1.7.7", "profile_name": "user", "target_name": "prod", "connection_name": "master"} */ insert into `my-project`.`elementary`.`dbt_columns__tmp_20240213043537835414` (full_table_name,database_name,schema_name,table_name,column_name,data_type) values (NULL,'hoge','hoge','hoge',NULL,'STRING'), (NULL,'hoge','hoge','hoge',NULL,'STRING'), ... 2024-02-13 04:39:18.028758 (MainThread): 04:39:18 BigQuery adapter: Retry attempt 1 of 1 after error: BadRequest('Resources exceeded during query execution: Not enough resources for query planning - too many subqueries or query is too complex.; reason: resourcesExceeded, message: Resources exceeded during query execution: Not enough resources for query planning - too many subqueries or query is too complex.')
そのため、max_query_sizeモードをやめてchuckモードに変更しました。
dbt_project.yml
からElementaryに変数を渡すことで設定を変更できます。
vars: elementary: "insert_rows_method": "chunk" "dbt_artifacts_chunk_size": 500
chuckモードに変更し、さらに dbt_artifacts_chunk_size
を500に変更することにより、too many subqueries or query is too complex
エラーが解消されました。
Elementaryのon-run-endを実行する際に too many subqueries or query is too complex
エラーが再び発生
chuckモードに変更することで上記エラーを解消しましたが、elementaryのon-run-endを実行する際に too many subqueries or query is too complex
エラーが再び起きました。
ソースコードを調査したところ、on-run-endを実行する際にinsert_rows関数にchunk_sizeを明示的に渡しておらず、デフォルトの5,000を使っていることが判明しました。このバグを修正するためにelementaryにpull requestを送りました。
4日後マージされ、0.14.1でリリースされました
Release 0.14.1 · elementary-data/dbt-data-reliability · GitHub
dbt_run_resultsを生成する際に The query is too large. The maximum standard SQL query length is 1024.00K characters, including comments and white space characters.
エラーが発生
Elementaryを0.14.1に上げると上記エラーを解消しましたが、dbt_run_resultsを生成する際に The query is too large. The maximum standard SQL query length is 1024.00K characters, including comments and white space characters.
エラーが発生しました。
テーブルdbt_run_resultsにはクエリのコンパイル結果を保存するcompiled_codeという項目があります。10Xでは、dbtのマクロやjinjaテンプレートを多用しているため、クエリのコンパイル結果が長くなりがちです。このような場合、クエリのコンパイル結果が長くなると、BigQueryのクエリ文字列数の上限1024Kを超えてしまいます。
この問題はchuckモードにおいて起きます。dbt_artifacts_chunk_size
で指定されたchuck_sizeに分割して処理できるが、max_query_size
モードのように文字列の制約はありません。
このバグを改修するために、Elementaryにクエリ文字列の長さを制限するPRを出しました。
現時点はまだレビューされておらず、マージされるまで時間かかるでしょう。
その間、一時的な対策としてchuck_sizeを小さ目な値(例えば100)に設定して、クエリの長さを制限しています。このエラーを一時的に回避できますが、実行するクエリ数自体が増えるため、モデルの実行速度が落ちる可能性があります。この修正により、最新のElementary v0.14.1でも動作するようになりました。
おまけ:一気にバージョンを上げることにならないように、dbtのpackages.ymlをRenovateに対応させる
Renovateという依存管理のツールを使用して、dbt関連のパッケージを一気にバージョンを上げることにならないように自動的にアップデートすることができます。Renovateの設定方法自体の説明は割愛します。
Renovateは、Npm、Pypi、Helmなど複数のデータソースに対応していますが、現時点ではdbtのデータソースが直接サポートされていないので、データソースをカスタマイズする機能を利用します。
ドキュメントの例を参考にして、dbthubのAPIから最新バージョンの情報をparseするcustomDatasourceと、リポジトリ内のpackages.yml
にあるバージョン情報をマッチさせるcustomManagerを作ってみました。
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" ], "customManagers": [ { "customType": "regex", "fileMatch": ["packages\\.yml$"], "datasourceTemplate": "custom.dbt_hub", "matchStrings": [ "- package: (?<depName>.+)\n version: (?<currentValue>.+)" ], "versioningTemplate": "semver" } ], "customDatasources": { "dbt_hub": { "defaultRegistryUrlTemplate": "https://hub.getdbt.com/api/v1/{{packageName}}.json", "transformTemplates": [ "{\"releases\":[{\"version\": $string(latest)}], \"homepage\": \"https://hub.getdbt.com/{{packageName}}/latest/\"}" ] } } }
mainブランチにマージしたあと数秒経つと、バージョン更新のPRが自動的に作成されることを確認できました。
まとめ
本記事では
- Elementaryアップデート時に遭遇したエラーとその解消法
- dbtのパッケージを小刻み更新するためにRenovoteを活用する方法
について紹介しました。皆さんのElementaryのアップデートにご参考になれば幸いです。