Firestore の苦手とする部分として複雑な検索条件をもとにデータを取得するのが困難です。

  • そのような要件のあるサービスに対してそもそも Firestore を選定するべきではない
  • Firestore のデータ構造を最適化することにより解決する
  • クライアントサイドジョインを用いて解決する
  • 検索用のデータベースを別に用意しそちらからデータを取得する

選択肢としては上記が考えられます。

今回は一番最後の具体的対応として、Google BigQuery を使用した例をご紹介します。

以下の流れで説明します。

  1. Firestore 拡張機能で BigQuery にリアルタイムでデータ連携する
  2. BigQuery にスキーマビューを追加する
  3. クライアントライブラリを用いて BigQuery からデータを取得する

1.Firestore 拡張機能で BigQuery にリアルタイムでデータ連携する

手動で Firestore からエクスポート & BigQuery にインポートしても良いですし、自作でデータ連携処理を書いても良いですが、上記を実現する拡張機能を公式が提供してくれています。

こちらを利用しない理由がないので、素直に使わせていただきましょう。

Firebase Extensions | Export Collections to BigQuery

Firebase のコンソールに Extensions という項目がありますので、そちらからページ遷移し上記拡張機能をインストールします。

その他詳しい内容は以下をご参照ください。

Extensions - 概要 | Firebase

拡張機能をインストールする際に以下の設定項目に回答します。

  1. Cloud Functions location
    • 説明:この拡張機能で作成した関数を配置するロケーション。通常は Firestore のローケーションと合わせるのが良いです。
    • 回答例:asia-northeast1
  2. BigQuery Dataset location
    • 説明:この拡張機能の使用する BigQuery データセットを配置するロケーション。通常は Firestore のローケーションと合わせるのが良いです。
    • 回答例:asia-northeast1
  3. Collection path
    • 説明:エクスポートするコレクションのパス。1つの拡張機能につきエクスポートできるコレクションは1つです。複数のコレクションをエクスポートする場合はその数だけ拡張機能をインストールします。
    • 回答例:users
  4. Dataset ID
    • 説明:BigQuery のデータセット ID。デフォルト値(firestore_export)のままで良いと思います。
    • 回答例:firestore_export
  5. Table ID
    • 説明:BigQuery データセット内のテーブルやビューに使用される識別用のプレフィックス。コレクション名で良いと思います。
    • 回答例:users
  6. BigQuery SQL table partitioning option
    • この拡張機能により作成された BigQuery テーブルやビューのパーティショニングの粒度。パーティショニングが必要なければ none を選択します。
    • 回答例:none

パーティショニングについては以下をご参照ください。

パーティション分割テーブルの概要 | BigQuery | Google Cloud

エクスポートしたいコレクションの数だけ拡張機能をインストールする

説明の途中にも記載しましたが、1つの拡張機能につきエクスポートできるコレクションは1つであり、複数のコレクションをエクスポートする場合はその数だけ拡張機能をインストールします。

例えば4つのコレクションをエクスポートするならば、拡張機能は以下の通り4つインストールします。

Firebase Extensions

2.BigQuery にスキーマビューを追加する

BigQuery – Google Cloud Platform

拡張機能が動きデータが連携された後、BigQuery には各コレクションにつき1つのテーブルと1つのスキーマビュー(以降ビューと記載)が作成されています。

  • テーブル:{TABLE_ID}_raw_changelog
  • ビュー:{TABLE_ID}_raw_latest

raw_changelog がデータの本体です。Firestore に対して変更があればここにその変更内容が追加されます。

raw_latest が現在のドキュメントデータを表すビューです。data というフィールドに JSON 形式でそのドキュメントのデータが入っています。

| document_name | document_id | ~   | data                                     |
| ------------- | ----------- | --- | ---------------------------------------- |
| ~             | ~           | ~   | {"name":"Alice","sex":"female","age":35} |
| ~             | ~           | ~   | {"name":"Bob","sex":"male","age":28}     |

しかし JSON 形式だと検索し辛いため、以下の形式のビューを作成して、こちらを用いて検索する方が良いでしょう。

| document_name | document_id | ~   | name  | sex    | age |
| ------------- | ----------- | --- | ----- | ------ | --- |
| ~             | ~           | ~   | Alice | female | 35  |
| ~             | ~           | ~   | Bob   | male   | 28  |

上記のようなビューを作成するためのスクリプトも公式が用意してくれています。fs-bq-schema-views というものです。

https://github.com/firebase/extensions/blob/master/firestore-bigquery-export/guides/GENERATE_SCHEMA_VIEWS.md

(上記ページ内にある概要を日本語訳・要約)

fs-bq-schema-views スクリプトは、 Firebase 公式拡張機能 である Export Collections to BigQuery とともに使用します。

fs-bq-schema-views スクリプト(以下、schema-views)は、raw_changelog から型付けされたビューを生成します。

Export Collections to BigQuery は、生データをミラーリングするだけで、スキーマや型は適用されません。このように分離することで、スキーマの不一致や未知のフィールドによってデータが失われることがないため、スキーマ検証のリスクが低くなります。

schema-views は、JSON スキーマ設定ファイルに基づいて、BigQuery の組み込み JSON 関数を使用してビューを作成します。

上記ページ内にある Use the script に従って作業すればビューが作成できます。

Step 1: Create a schema file ではスキーマを定義する JSON ファイルを作成します。

BigQuery のビューにはこのとき作成する JSON ファイル名が使用されます。作成されるビューの具体的な名称は以下です。

{TABLE_ID}_schema_{SCHEMA_FILE_NAME}_changelog
{TABLE_ID}_schema_{SCHEMA_FILE_NAME}_latest

どのような名称でも構いませんが、私は view.json という名称で JSON ファイルを作成しました。

Step 3: Run the script 部分については対話形式での実行も可能です。慣れないうちはこちらの方がわかりやすいかもしれません。

npx @firebaseextensions/fs-bq-schema-views

? What is your Firebase project ID?
  >> example-project
? What is the ID of the BigQuery dataset the raw changelog lives in? (The dataset and the raw changelog must already exist!)
  >> firestore_export
? What is the name of the Cloud Firestore collection for which you want to generate a schema view?
  >> users
? Where should this script look for schema definitions? (Enter a comma-separated list of, optionally globbed, paths to files or directories).
  >> ./users/view.json

上記実行後に BigQuery を見るとビューが作成されています。

(例:テーブル ID が users で スキーマ定義した JSON ファイル名が view.json の場合)

  • users_schema_view_changelog
  • users_schema_view_latest

基本的に検索は users_schema_view_latest に対して実行します。

3.クライアントライブラリを用いて BigQuery からデータを取得する

(以降は Node.js の例です)

公式が提供するライブラリを用いてデータを取得します。

BigQuery を使用するプログラムの場合、同時に Firebase の SDK も使っていることが多いと思います。BigQuery のインスタンスを初期化する際には、Firebase の SDK で使用しているサービスアカウントをそのまま読み込ませれば OK です。

import { BigQuery } from '@google-cloud/bigquery';
import serviceAccount from '***-firebase-adminsdk.json';

const bigquery = new BigQuery({
  projectId: serviceAccount.project_id, // または プロジェクト ID 'example-project' を直接入力しても可
  credentials: serviceAccount,
});

credentials として serviceAccount を渡していますが、実際には serviceAccount の中のclient_emailprivate_keyの情報しか渡っていません。これだけだとプロジェクト ID が指定されていないというエラーが出てしまうため、別途 projectId を渡さなければいけない点にご注意ください。

ドキュメントが詳しいため、実際にデータを取得する部分のコードはここでは省略しますが、基本的には SQL 文を用意して BigQuery インスタンスの query メソッドに渡すだけです。

cosnt query = async () => {
  const _query = 'SELECT * FROM `example-project.firestore_export.users_schema_view_latest`';
  const data = await bigquery.query(_query);
  const rows = data[0];
  console.log(rows);
};

BigQuery の SQL の構文は以下をご参照ください。

標準 SQL のクエリ構文 | BigQuery | Google Cloud

以上

現場からは以上です!