Elementaryアップデートの試行錯誤

はじめまして、データエンジニア業務委託のjcです。今回は、Elementaryアップデートに関する試行錯誤の話を共有したいと思います。Elementaryの運用にお困りの方、これからElementaryを導入しようとする方の参考になれば嬉しい限りです。

Elementaryとは

Elementaryはdbtのモニタリングツールの一つであり、データ品質を可視化するためのダッシュボード作成・Slackへのアラート通知機能を提供しています。

先日、データエンジニアの吉田さんがTokyo dbt Meetup #8で「Elementaryを用いたデータ品質の可視化とデータ基盤の運用改善」を題として発表を行いました。Elementaryの活用例に興味のある方はぜひ併せて確認してみてください。

speakerdeck.com

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を送りました。

fix: add arg chunk_size when calling `insert_rows` by aibazhang · Pull Request #669 · elementary-data/dbt-data-reliability · GitHub

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を出しました。

support query_max_size for insert_rows_method `chunk` by aibazhang · Pull Request #679 · elementary-data/dbt-data-reliability · GitHub

現時点はまだレビューされておらず、マージされるまで時間かかるでしょう。

その間、一時的な対策としてchuck_sizeを小さ目な値(例えば100)に設定して、クエリの長さを制限しています。このエラーを一時的に回避できますが、実行するクエリ数自体が増えるため、モデルの実行速度が落ちる可能性があります。この修正により、最新のElementary v0.14.1でも動作するようになりました。

おまけ:一気にバージョンを上げることにならないように、dbtのpackages.ymlをRenovateに対応させる

Renovateという依存管理のツールを使用して、dbt関連のパッケージを一気にバージョンを上げることにならないように自動的にアップデートすることができます。Renovateの設定方法自体の説明は割愛します。

Renovateは、Npm、Pypi、Helmなど複数のデータソースに対応していますが、現時点ではdbtのデータソースが直接サポートされていないので、データソースをカスタマイズする機能を利用します。

Custom - Renovate Docs

ドキュメントの例を参考にして、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が自動的に作成されることを確認できました。

Update dependency elementary-data/elementary to v0.14.1 by renovate[bot] · Pull Request #11 · aibazhang/renovate-tutorial · GitHub

まとめ

本記事では

  • Elementaryアップデート時に遭遇したエラーとその解消法
  • dbtのパッケージを小刻み更新するためにRenovoteを活用する方法

について紹介しました。皆さんのElementaryのアップデートにご参考になれば幸いです。