BigQueryを利用したアプリケーション作成時に理解しておくべきジョブの概念

  • 0
    いいね
  • 0
    コメント

    BigQueryをログの保存先としていると、BigQueryを使ったアプリケーションを利用・作成する機会が増えてきます。アプリケーションを作成するために必要な「ジョブ」の概念について軽くおさらいします。

    BigQueryにおけるジョブとは

    ジョブは簡単に言えばクエリの実行やデータのロードなどBigQueryで何らかの動作を実行させるための仕組みで、非同期で実行されます。ジョブは必ず1つのプロジェクトに紐づくので、どのプロジェクトに対して課金されるかを明らかにするのにも役立ちます。APIからジョブを実行する場合、プロジェクト内でユニークなIDを持たせて実行できます。そうすることで同じジョブを実行させてしまうことを防いだりそのIDを利用してジョブの結果を取得したりということができます。ジョブIDを指定しなかった場合はBigQueryが自動でユニークな値を割り当てます。

    BigQueryのAPI

    BigQueryのAPIは REST API として用意されていて、ProjectsDatasetsTablesTabledataJobsというコレクションがあります。REST API ですのでgetlistなどおなじみのメソッドでアクセスできます(コレクションの特性上用意されていないメソッドもあります)。
    https://cloud.google.com/bigquery/docs/reference/rest/v2/

    低レベルのAPIを使ったクエリ実行と結果の取得

    Queriesのようなコレクションはありませんのでクエリを実行するにはJobsを使います。 Jobs.insertのパラメータにクエリを指定し、BigQueryに対してジョブを挿入します。 するとBigQueryはジョブを実行するのでJobs.getでジョブの結果が取得できます。そのジョブが成功している場合、クエリ結果の保存先として指定したdataset・table(指定しなかった場合は無名のIDがJobs.getの Response Body に含まれている)に対してTabledata.listを呼び出せばクエリの結果を取得することができます。このようにBigQueryではAPIを組み合わせることで非同期的にクエリ実行と結果の取得ができます。

    Jobs.insert のリクエストパラメータの例

    {
      "configuration": {
        "query": {
          "query": "select * from [project-id-001:dataset.table] limit 5"
        }
      }
    }
    

    Jobs.get のレスポンスの例

    {
     // (一部省略)
     "kind": "bigquery#job",
     "configuration": {
      "query": {
       "query": "select * from [project-id-001:dataset.table] limit 5"
       "destinationTable": {
        "projectId": "project-id-001",
        "datasetId": "[_dataset_id...]",
        "tableId": "[table_id...]"
       },
       "createDisposition": "CREATE_IF_NEEDED",
       "writeDisposition": "WRITE_TRUNCATE"
      }
     },
     "status": {
      "state": "DONE"
     },
     "user_email": "test@example.com"
    }
    

    getQueryResult

    おそらくBigQueryに挿入するジョブの多くはクエリの実行でしょうから、何度も別々のAPIを呼び出すのは少し面倒です。そこでBigQueryはgetQueryResultsという便利なAPIも提供してくれています。このAPIにジョブIDを渡せばTabledata.listを呼び出さなくてもクエリの結果を返してくれます。

    Jobs.queryによるクエリ実行と結果の取得

    クエリ実行やその他の動作をBigQueryに指示するとき、結果取得にかなり長い時間かかってくることもあるのでジョブという仕組みは都合がいいです。しかし、ジョブを挿入してその結果を取得というプロセスを踏まなければならないのでやはり面倒です。

    実はこれにも便利な方法があって、Jobs.queryにクエリを乗せて呼び出せばAPI一発で同期的にクエリの実行と結果の取得ができます。ただし、指定したタイムアウト秒数(デフォルトでは10秒)が経過した時点で結果の取得ができなくてもリクエストが終了するという特徴があります。タイムアウトになってしまうのはそのリクエストだけで、ジョブは動き続けています。タイムアウト時にレスポンスで返ってきたジョブIDを使ってJobs.getQueryResultを呼び出せば結果を取得することができます。

    アプリケーションからAPIを呼び出すときの注意

    アプリケーションからBigQueryのAPIを呼び出す際はこのジョブの仕組を理解していなければなりません。Jobs.queryは便利なのですがジョブの仕組みを理解していなければ予期せずユーザーにタイムアウトエラーを見せてしまうことになります。

    「クエリを実行して一定時間結果が返ってこなかったらタイムアウトになる」という仕様であればJobs.queryで良いのですが、そうでないなら一度Jobs.insertでジョブを挿入してあとからJobs.getQueryResultを呼び出すのが良いでしょう。もちろんこれは実装の問題なのでJobs.queryを呼んで例外処理をするのでもいいしタイムアウトとして扱うのもいいと思います。

    自分が実装するときだけでなく人が書いたコードをレビューする際は特に注意する必要があります。実装者がAPIの仕様を理解していない場合も考えられるで仕様と照らし合わせて指摘するべきポイントだと思います。特にAPIをラップする何らかのライブラリを使用している場合はおそらくquery()のようにわかりやすいメソッドがあるので、何も考えずに実装するとJob.queryを呼ぶ率が多くなることが予想できます。

    おわりに

    以前pandasのコードを読んだときにどういうわけかJob.queryしか呼び出していなかったと記憶しています。統計用ライブラリなのでわざとそういう設計にしているのか、あるいは自分が読んだときには単純に未実装だったのかはわかりませんが、pandasとBigQuueryを利用したアプリケーション作成時に苦労した記憶があります。BigQueryには非同期で結果を取得する仕組みがあるので、そのBigQueryを利用したアプリケーションでも非同期で実行する仕組みがある方が自分は好みです。