elementaryで Transaction is aborted due to concurrent updateのエラーが起きる場合の対処方法

データ品質の可視化などに役に立つelementaryだが、稀に以下のようなエラーが発生することがある。このエントリではこのエラーに対する対応方法について考える。

00:03:24    on-run-end failed, error:
 Query error: Transaction is aborted due to concurrent update against table my-project:my_dataset_elementary.dbt_tests. Transaction ID: xxxx at [6:13]

前提: elementaryの成果物

elementaryはdbt_models / dbt_tests / dbt_run_results / dbt_source_freshness_resultsなど様々な便利な成果物を出力してくれるが、その成果物は大きく分けると2種類に分けられる。

  • A: 現在の設定値
    • 現在の設定値であり、履歴を持たない。要するにyamlファイルの内容をSQLで叩きやすいように整形したもの、と思っておけばよい
    • dbt_modelsdbt_testsが該当する
    • 最新の状態のみを持つので、基本的に洗い替えになる
  • B: 実行の履歴
    • dbt_run_resultsdbt_source_freshness_resultsが該当する
    • 実行の履歴なので、yamlには残らない。dbt Cloudを使っているのであればjobの実行履歴に相当するものだし、dbt-coreを使っているのであればログファイルに相当するものでもある
    • 基本的にappendしていくもの

エラーの原因

エラーの原因になっているのは主にこの辺upload_artifacts_funcに関わるところである。引数でshould_commit=trueが指定されており、この場合はトランザクションを掴みにいく挙動になる。そのため、複数のdbtのジョブが走る場合にelementaryが主に動くon-run-endが被ってしまうと、先にトランザクションを掴めたジョブは成功し、他のジョブは成功(あるいはretryで成功)という挙動になる。

成果物のアップロードをする際、トランザクションを掴みにいく対象としてはdbt_models / dbt_tests / dbt_sources / dbt_snapshots / dbt_metrics / dbt_exposures / dbt_seedsになっており、上述した「A: 現在の設定値」に関するものが今回のエラーを発生し得る形となる。

エラーの解消方法

dbtを使う上でelementaryは非常に便利なので、今回のエラーが出るからといって利用を諦めるのはもったいない。回避策として以下のようなものが考えられる。

  • 同時に実行され得るdbtのjobに関してはdisable_dbt_artifacts_autoupload設定し、トランザクションでこけ得る対象の除外する
  • 「A: 現在の設定値」に関する成果物をアップロードするため、同時に実行されることがない時間帯にjobの設定をしておく
    • トランザクションの掴み合いが起きなくなるため、エラーは発生しない
    • ほぼ無意味なdbt_run_resultsが挿入される形になるが、気になるのであればそれらのみ無効化することができる
  • jobによって設定値を変えたい場合、dbtのコマンドライン引数から設定することができるので、それを活用しましょう