改めて BigQuery の Partitioned tables と戯れた話
こんにちは、開発本部の宍戸です。先日のメドレー社内勉強会「TechLunch」で、BigQuery のPartitioned tableについて発表しましたので、その話について書きたいと思います。
なぜ今 Partitioned table?
ある案件でユーザーの操作ログを扱う必要があり、データ保管先に BigQuery を利用しようと考えていました。その際に、「以前は β 版だった分割テーブル、そういえば今使えるよね」という話になり色々調べてみた、というのが今回このテーマを選んだ背景です。
なぜ分割テーブルを使おうと思ったのか
ユーザーの行動/操作ログの確認については API サーバーのアクセスログで、ある程度その目的を果たすことができていました。
しかし、API のアクセスログですと今回対象にしたい”ユーザーの操作”以外にも多くの操作ログ(通知の取得ログなどをはじめとする各種データ参照など)を抱えてしまっているため、必要な情報を抽出するためには無駄が発生してしまう状態でした。無駄なスキャンで無駄なコストが発生するのは嫌ですよね。
ということで目的に即したデータに限定して保存/閲覧できるように、行動/操作ログは新しく Partitioned table に保存することにしました。
また、新しいテーブルに保存する対象となる操作に関しては、これまで保存していた過去分のアクセスログのデータも新しいテーブルへ移行したいというニーズもあり、既存のデータを取得し加工をしたうえでデータを移行するというタスクも行いました。
分割テーブルの各種方法について
まずひとくちに「分割テーブル(Partitioned table)」と言っても、BigQuery ではいくつかの実現方法があります。
- 「取り込み時間で分割」されたテーブル
- 「特定のカラムに基づいて分割」されたテーブル
- 「時間ベースの命名方法を使用して分割」したテーブル
前述のとおり今回は過去データの移行なども同時に行いたい背景があったため「取り込み時間で分割」されたテーブルではなく、「特定のカラムに基づいて分割」されたテーブルを利用するようにしました。こちらのテーブルは分割するための列として DATE 型または TIMESTAMP 型を指定することで利用することができます。
こうして作られた分割テーブルは、パーティションと呼ばれるセグメントに内部的にさらに分割されており、参照するパーティションを限定することで、効率の良いアクセス(スキャン対象データ量の限定、高速化)が可能になります。
注意点として公式ドキュメントにもありますが、クエリの記述方法によっては期待したパーティションの絞り込みがされないことがあります。また分割テーブルへのクエリにはレガシー SQL を利用することができないため、標準 SQL を利用する必要があります。
クエリキャッシュ
分割テーブルを使うときに気になるコストについてですが、クエリキャッシュの仕組みを利用することでコスト削減が見込めます。そこで今回利用する分割テーブルで、過去のパーティションに対するアクセスはクエリキャッシュが効くのかどうか確認してみました。
そもそも、クエリキャッシュの仕組み上「対象のテーブルがストリーミングバッファを添付されている状態ではクエリの結果はキャッシュに保存されない」という制約(例外)があります。
今回の私達の用途ですと断続的に書き込みが発生する状況になるため、テーブルがストリーミングバッファを持たなくなる状況というのは発生しづらいです。この状態ではフェッチしたいデータ(パーティション)がいかに過去のものであってもテーブルは一つであるため、前述の制約が残り続けてしまいます。
そのため残念ながらクエリキャッシュが有効になるケースは稀そうだなという結論に至りました。(ストリーミングバッファが付いているかどうかは、WebUI のテーブル情報の詳細タブから確認できます)
データ移行
前述の要件の中に「過去のアクセスログから一部データを新しいテーブルに移行したい」という話がありました。こちらの作業内容としては以下のようなものになります。
- 「access_yyyymm」的なテーブルから、移行用のデータを取得
- 必要なデータを付与
- 移行先のテーブルにデータを insert
とてもシンプルですね。データが少なければ適当なスクリプトで簡単に移行できそうです。
やってみた
まずは試しに google-cloud-ruby を使って、ストリーミングインサートを利用した移行プログラムを作って、実行してみたところ...
「You can only stream to date range within 7 days in the past and 3 days in the future relative to the current date.」
と、怒られてしまいました。
分割テーブルの制限としてストリーミングインサートは、過去7日までしか書き込むことができないようでした。・・・と!発表当時は公式ドキュメントにも書いてあったと思ったのですが、改めて公式ドキュメント(パーティショニング オプションの比較)見てみると、過去1年まで扱えるようになっている!?ようです。この辺りは定期的にアップデートされているようなので、実際に作業する前にきちんと公式情報を確認しておく必要がありそうです。
最終的に
前述のようなストリーミングに関する話もあり、また移行対象データの量もかなり多かった為、ストリーミングの方式は諦めて、GCS にインポート用のファイルを用意してそちらからインポートする形にし、無事移行することができました。
作業当初はデータ移行を行う手段として Embulk を利用することも検討していました。Embulkはさまざまなストレージ、データベース、NoSQL、クラウドサービス間のデータ転送を支援してくれる並列バルクデータローダーです。しかし今回は簡易的な方法で移行できそう、との思惑から利用を見送っていました。
あとから気になったので、Embulk のプラグインであるembulk-output-bigqueryを調べてみると、やはりストリーミングインサートは実装しておらず、また GCS 経由でのデータインポートができるなど、結果的には独自手法でやったことはまさにこのプラグインが提供してくれる機能そのものでした。こちらを最初から利用していればよかったかも...という気持ちで今はいっぱいです ( ꒪⌓꒪)
まとめ
今更ながら BigQuery の Partitioned table 周りについて調べてみたこと、やってみたことを発表しました。なんとなく知ってるような気がする、という感じだったコスト周り、分割テーブル、クエリキャッシュなどの項目に対する理解を私自身少し深めることができたと思います。また、弊社では色々な場面で BigQuery を利用しているため、このネタが他チームでの今後の活用に何か役に立てば嬉しいなと思います。
今回のデータ移行を行った後あたりにClustered Table(beta)のことを知りましたが、こちらも対応することでさらにスキャン量の削減、高速化が期待できそうです。
また、TechLunch での発表後、今回のような用途に対して、先日公開された Amazon QLDB あたりも使えるのでは?と同僚から教えてもらいました。監査機能などを目的としたログデータについては、QLDB はより適した用途のように思いますので、こちらも今後検証していきたいと思います。