Workflowsから実行したDataformでリポジトリアクセスエラーが発生した場合の回避方法

Google Cloud
この記事は約14分で読めます。

こんにちは。長畑です。

WorkflowsからDataformを実行するパイプラインを構築した際に、発生した事例を基に回避する方法をご紹介します。案の一つとして参考になれば幸いです。

本記事でわかること

  • Dataform実行時のリポジトリエラーの回避方法

構築イメージ

構築イメージを確認します。
schedulerを介して定期的にWorkflowsからDataformを呼び出しています。Dataformのソース管理は外部のリポジトリサービスを利用しています。WorkflowsはDataform実行の他、ファイル出力や出力したファイルを他サーバへ連携を行うFunctionsを実行しています。

発生した事象

外部リポジトリが停止しており、Dataform実行時にリポジトリへのアクセスができずエラーが発生、
後続のFunctionsが実行できずにファイル出力、送信ができませんでした。

Cloud Workflowsのソースの確認

Googleドキュメントを参考に以下のようにWorkflowsからDataformを実行していました。
外部リポジトリサービスが停止していたため、コンパイルの実施でエラーとなりDataformの実施ができなくなっていました。

- main:
    steps:
        # コンパイルの実施
        - compilation_dataform:
          call: http.post
          args:
            url: ${"https://dataform.googleapis.com/v1beta1/<リポジトリパス>/compilationResults"}
            auth:
              type: OAuth2
            body:
              gitCommitish: main
          result: compilationResult

        # 実行 
        - exec_dataform: 
          call: http.post
          args: 
            url: ${"https://dataform.googleapis.com/v1beta1/<リポジトリパス>/workflowInvocations"} 
            auth: 
              type: OAuth2 
            body: 
              compilationResult: ${compilationResult.body.name}

事象の確認

利用していた外部リポジトリサービスの可用性は99.9%、サービス停止の時間は1年で約8時間程度。Dataform実行は1日1回。その際に外部リポジトリサービスが停止している確率を考えると頻繁には発生しないと考えられるのですが、それでも起こる時は起こります。

Dataformを実行する場合コンパイル情報が必要な為、どうにかして取得する方法を考える必要があります。

対応方法

対応方法を考えます。

案1. Dataformの定期コンパイル機能を利用する

Dataformには「リリースとスケジュール」という機能があり、定期的なコンパイルが行えます。この機能と最新のコンパイル情報を取得するAPIを利用すれば良いかと考えました。

Dataformのコンパイル情報を取得するにはAPIを利用します。
ドキュメントを見るとcompilationResultsをGETメソッドでアクセスすれば、コンパイル情報が取得できるようです。

ということで取得してみた結果が以下です。
codeCompilationConfig属性がコンパイル情報となり、過去にコンパイルした情報が羅列されています。
Dataform実行のためにname属性の情報が必要なのですが、見ての通りどれが最新なのかを特定するような日付情報などはありません。

{"body":{"compilationResults":[
{"codeCompilationConfig":{"assertionSchema":"dataform_assertions","defaultDatabase":"nagahata","defaultLocation":"asia-northeast1","defaultSchema":"dataform","schemaSuffix":"dataflow"},"dataformCoreVersion":"2.4.2","name":"projects/nagahata/locations/asia-northeast1/repositories/test_dt_rep/compilationResults/c480ea27-7a04-4af0-bebd-374d5c9eb220","releaseConfig":"projects/<project_num>/locations/asia-northeast1/repositories/test_dt_rep/releaseConfigs/test_dt_release","resolvedGitCommitSha":"40f9ee4624eda7590659bce5f17880e09a2ebc3b"},
,{"codeCompilationConfig":{"assertionSchema":"dataform_assertions","defaultDatabase":"nagahata","defaultLocation":"asia-northeast1","defaultSchema":"dataform","schemaSuffix":"dataflow"},"dataformCoreVersion":"2.4.2","name":"projects/nagahata/locations/asia-northeast1/repositories/test_dt_rep/compilationResults/bbbfde15-095a-4b09-b336-1cc563fe2ef4","releaseConfig":"projects/<project_num>/locations/asia-northeast1/repositories/test_dt_rep/releaseConfigs/test_dt_release","resolvedGitCommitSha":"40f9ee4624eda7590659bce5f17880e09a2ebc3b"},
,{"codeCompilationConfig":{"assertionSchema":"dataform_assertions","defaultDatabase":"nagahata","defaultLocation":"asia-northeast1","defaultSchema":"dataform","schemaSuffix":"dataflow"},"dataformCoreVersion":"2.4.2","name":"projects/nagahata/locations/asia-northeast1/repositories/test_dt_rep/compilationResults/bfd37c76-6d53-4c7d-99cd-f4d4053f0c3a","releaseConfig":"projects/<project_num>/locations/asia-northeast1/repositories/test_dt_rep/releaseConfigs/test_dt_release","resolvedGitCommitSha":"40f9ee4624eda7590659bce5f17880e09a2ebc3b"},
]},"code":200,"headers":{"Alt-Svc":"h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000","Cache-Control":"private","Content-Length":"15303","Content-Type":"application/json; charset=UTF-8","Date":"Wed, 22 May 2024 01:55:51 GMT","Server":"ESF","Vary":"Origin, X-Origin, Referer","X-Content-Type-Options":"nosniff","X-Frame-Options":"SAMEORIGIN","X-Xss-Protection":"0"}}

どうやって最新のコンパイル情報を取得できるのか調査を勧めますとGoogle Communityの投稿が見つかりました。
読んでみると最新のコンパイル情報はAPIからは取得できないという趣旨のコメントがありました。解決策としてはコンパイル情報をどうにかしてトラッキングしてほしいとのコメントがあり、APIで取得することはあきらめました。

案2. コンパイルと実行を分離する。

Dataformの実行に必要な情報はコンパイル情報のname属性です。この情報をDBなりStorageになり保持しておいて、実行時に参照すればよさそうです。
Dataform実行時にエラーによる実行を止めたくない為、Dataformのコンパイルのみを実行するパイプラインとDataformを実行するのみのパイプラインを分ければよさそうです。
今回はコンパイル情報をBigQueryに保持しました。

  • BigQueryにデータセットとテーブルを作成します。
    テーブルは簡単にname項目のみ定義します。
  • Dataformのコンパイルのみを行うWorkflowsの作成
    外部リポジトリアクセスエラー等発生した場合は、BigQueryにコンパイル情報を書き込みません。
- main:
    steps:
        try:
            steps:
                # Dataformコンパイルの実施
                - compilation_dataform:
                  call: http.post
                  args:
                    url: ${"https://dataform.googleapis.com/v1beta1/<リポジトリパス>/compilationResults"}
                    auth:
                      type: OAuth2
                    body:
                      gitCommitish: main
                  result: compilationResult

                # BigQueryにコンパイル情報を保存する。
                - insert_compilation_result:
                  call: googleapis.bigquery.v2.jobs.insert
                  args:
                      projectId: ${project_id}
                      body:
                          configuration:
                              query:
                                  query: ${"select '" + compilationResult.body.name + "' as name"}
                                  destinationTable:
                                      projectId: <プロジェクトID>
                                      datasetId: "dataform_setting"
                                      tableId: "dataform_compilation"
                                  createDisposition: "CREATE_IF_NEEDED"
                                  writeDisposition: "WRITE_TRUNCATE"
                                  allowLargeResults: false
                                  useLegacySql: false
                                  
        except:
            steps:
                - log:
                    call: sys.log
                    args:
                        text: "コンパイル情報を取得できませんでした。"
  • Dataformの実行のみを行うWorkflowsの作成
- main:
    steps:
      #BigQueryからコンパイル情報を取得する。{
      - get_compilation_result:
          call: googleapis.bigquery.v2.jobs.query
          args:
              projectId: <プロジェクトID>
              body:
                  defautDataset:
                      projectId: <プロジェクトID>
                      datasetId: "dataform_setting"
                  query: ${"select name from `" + <プロジェクトID> + "." + "dataform_setting" + "." + "dataform_compilation" + "`"}
                  useLegacySql: false
          result: queryResult
    
        # Dataform実行
        - run_dataform: 
          call: http.post
          args: 
            url: ${"https://dataform.googleapis.com/v1beta1/<リポジトリパス>/workflowInvocations"} 
            auth: 
              type: OAuth2 
            body: 
              compilationResult: ${queryResult.rows[0].f[0].v}

分離したWorkflowsをそれぞれ異なるタイミングで実行します。

まとめ

Googleが用意してくれているAPIは便利でよく利用させてもらっていますが、たまにかゆいところに手が届かないAPIもあり、それらをどう組み合わせて最適なサービスを提供できるかということを考えるいい機会になったかと思います。
不便だと感じるAPIも時がたてば便利なものに変わり、こういった機会も徐々に減っていくとは思いますが、いいサービスを提供する為には日々トライアンドエラーで勉強するしかないのでしょうね。

Google および Google Cloud Platform™ service は Google LLC の商標であり、この記事は Google によって承認されたり、Google と提携したりするものではありません。

コメント

タイトルとURLをコピーしました