BigQueryをログの保存先としていると、BigQueryを使ったアプリケーションを利用・作成する機会が増えてきます。アプリケーションを作成するために必要な「ジョブ」の概念について軽くおさらいします。
BigQueryにおけるジョブとは
ジョブは簡単に言えばクエリの実行やデータのロードなどBigQueryで何らかの動作を実行させるための仕組みで、非同期で実行されます。ジョブは必ず1つのプロジェクトに紐づくので、どのプロジェクトに対して課金されるかを明らかにするのにも役立ちます。APIからジョブを実行する場合、プロジェクト内でユニークなIDを持たせて実行できます。そうすることで同じジョブを実行させてしまうことを防いだりそのIDを利用してジョブの結果を取得したりということができます。ジョブIDを指定しなかった場合はBigQueryが自動でユニークな値を割り当てます。
BigQueryのAPI
BigQueryのAPIは REST API として用意されていて、Projects
・Datasets
・Tables
・Tabledata
・Jobs
というコレクションがあります。REST API ですのでget
やlist
などおなじみのメソッドでアクセスできます(コレクションの特性上用意されていないメソッドもあります)。
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を利用したアプリケーションでも非同期で実行する仕組みがある方が自分は好みです。