Firestore のエクスポートデータを timetamp パーティショニングして BigQuery に取り込む

Firestore または Datastore の中身を柔軟に検索したい場合、エクスポートしたデータを BigQuery へロードすることで実現できる。 コンソール上に UI も提供されていて簡単だけど、パーティショニング選択肢が "取り込み時間" しかない。

f:id:pokutuna:20200108035353p:plain
コンソール上のパーティショニングオプション

でもエクスポート&ロードはバッチ的な処理なので、ほぼ1つのパーティションに固まってしまい、あまり意味がない。

パーティションを切るなら TIMESTAMP でパーティションを切りたいけど、以下のような記述もありパーティションだけ作ることはできなさそう。エクスポートデータから自動でスキーマ検出したいのでうまく噛み合わない。

スキーマ定義を持たない空のパーティション分割テーブルを作成することはできません。パーティションの作成に使用される列を識別するためには、スキーマが必要です。
日付 / タイムスタンプ パーティション分割テーブルの作成と使用  |  BigQuery  |  Google Cloud

あれこれ考えて、エクスポートデータの metadata ファイルの protocol buffer から BigQuery スキーマに変換するグッズを作るか?? ...とか考えていたけど、実は CLI から bq load コマンドを使えばできることが分かった。

--time_partitioning_filed=[FIELD]

bq load コマンドの --time_partitioning_field=[FIELD] オプションで Timestamp 型のフィールドを指定すればタイムスタンプパーティションを作れるし、Firestore の構造から自動でスキーマも定義してくれる。

createdAt フィールドでパーティションを切りたい場合、具体的には以下のようなコマンドになる。

$ bq load \
  --source_format=DATASTORE_BACKUP \
  --time_partitioning_field="createdAt" \
  MyProject:MyDataset.MyTable \
  gs://mybucket/2020-01-07T00:00:00/default_namespace/kind_Hoge/default_namespace_kind_Hoge.export_metadata

Cloud Firestore のネイティブモード(いわゆる Firestore)と、Datastore モードのエクスポートデータは同じフォーマットなので、Datastore でも使える。どちらも一貫したスキーマが必要なので、Firestore なら collection、Datastore なら kind 単位のエクスポートデータを作っておく必要がある。 ちなみに1テーブルあたりの最大パーティション数は 4000 なので、11年ぐらいのサービスのデータを日付でパーティショニングすると意外と到達してしまう......

パーティショニングについて

ある程度以上の規模ならスキャン範囲を抑えるためにテーブルを分割したくなるはず。 BigQuery でテーブルを分割する方法はいくつかあるけど、テーブルの命名でシャーディングしたり、BigQuery へ到着した時間でパーティショニングする(取り込み時間)より、データに対してパーティションが作られるタイムスタンプパーティショニングのほうが扱いやすいしデータの持ち方も自然に感じる*1RDB でテーブル定義時に PARTITION BY するのと雰囲気も近い。

f:id:pokutuna:20200108044807p:plain
取り込み時間パーティションは UI からわかりやすい

テーブルに保存されているパーティション情報は、 その他 > クエリの設定 > SQL 言語 を "レガシー" にして以下のようなクエリを投げると確認できる from BigQuery の Partitioned Table 調査記録 - Qiita

SELECT partition_id,creation_time,last_modified_time FROM [Dataset.MyTable$__PARTITIONS_SUMMARY__]

f:id:pokutuna:20200108045318p:plain

一応アイコンで区別はされていて、上から、計算機みたいなのはパーティションなし、具のないハンバーガーはパーティション、顔がひっくり返りながら飛び出てるのは テーブル名_[YYMMDDD] シャーディングの意味みたいですね。

*1:他に整数範囲でパーティショニングする方式もあり、これもデータからパーティションを作れる