はじめに
出典: https://medium.com/google-cloud-jp/firestore-bigquery-3b887a5bc27e
firestore はリアルタイム性に優れている一方で、過去まで遡ったで大量のデータに対して複雑なクエリーを実行することができません。
自分が担当しているサービス規模だと、今まではギリギリ対応できるデータ量だったので直接 firestore で全件取得した後集計処理を書いていたのですが、サービスが成長するにつれてこれではまかないきれなくなってきてしまいました。
そこで今回は、firestore の集計に関するコレクションを BigQuery にインポートするまでに行った手順をまとめてみます。
手順
Cloud Storage のバケットを作成
バケットの作成
ライフサイクル設定
BigQuery のデータセットを作成
今回は firestore.~ としたかったので firestore
で作成
https://cloud.google.com/bigquery/docs/datasets?hl=ja
↓
Cloud Function の関数作成
Export 用関数の作成
対象コレクションを指定の Bucket へエクスポートします。
実際は引数を取って好きな日付で実行できるようにしておくとベターかと思います。
import { google } from 'googleapis';
import { Config } from 'src/config';
import * as Moment from 'moment';
import * as Functions from 'firebase-functions';
export const firestoreExportToBigQueryCron = Functions.pubsub
.topic('topicName')
.onPublish(async message => {
const targetYmd = Moment().subtract(1, 'day').format('YYYYMMDD');
const auth = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/datastore', 'https://www.googleapis.com/auth/cloud-platform'],
});
await google.firestore('v1').projects.databases.exportDocuments({
name: `projects/${Config.projectId}/databases/${Config.projectId}`,
requestBody: {
collectionIds: ['collectionId'],
outputUriPrefix: `gs://${Config.gcs.firestoreExportBucket}/${targetYmd}`,
},
auth,
});
});
Import 用関数の作成
対象 Bucket のアップロードに反応し、エクスポート結果を BigQuery にインポートします。
import { google } from 'googleapis';
import { Config } from 'src/config';
import * as Functions from 'firebase-functions';
export const bigQueryImportStorageTrigger = Functions.storage
.bucket(Config.gcs.firestoreExportBucket)
.object()
.onFinalize(async object => {
const name = object.name!;
const matched = name.match(/all_namespaces_kind_(.+)\.export_metadata/);
if (!matched) {
return console.log(`invalid object: ${name}`);
}
const collectionName = matched[1];
const auth = await google.auth.getClient({ scopes: ['https://www.googleapis.com/auth/bigquery'] });
const result = await google.bigquery('v2').jobs.insert({
auth,
projectId: Config.projectId,
requestBody: {
configuration: {
load: {
destinationTable: {
tableId: collectionName,
datasetId: 'firestore',
projectId: Config.projectId,
},
sourceFormat: 'DATASTORE_BACKUP',
writeDisposition: 'WRITE_TRUNCATE',
sourceUris: [`gs://${Config.gcs.firestoreExportBucket}/${name}`],
},
},
},
});
console.log(result);
});
IAM の設定
どうやら GAE デフォルトアカウントにはエクスポートやインポートの権限がないようなので IAM から追加します
https://cloud.google.com/iam/docs/quickstart?hl=ja
スケジューリング
GAE か Cloud Scheduler で先程の関数を定期実行できるように設定します。
https://cloud.google.com/scheduler/docs/quickstart
ハマったこと
cloud storage のエクスポートからのインポートを手動で確認する工程にて...
エクスポート実行直後、パッと見ディレクトリとかは生成されているのでエクスポートが終了したと勘違いし、その中途半端なディレクトリの再インポートに失敗してハマっていました...
データ量の多いプロジェクトの場合は下記コマンドでジョブの終了をちゃんと確認しましょう...
gcloud beta firestore operations list
また、現在の BigQuery は以前までよく使われていた日付別のテーブル分割が非推奨になっているようですが、大量のデータがあるスコープにおいては上書きインポートではなく、追加で管理したいものです。
そうしないと全データを firestore 側にも保持しないといけなくなるため、DB 復元時間やコスト面に支障が出てきます。
しかし firestore からエクスポートしたファイルからは BigQuery への追加インポートができないため、大量のデータを扱う必要のあるスコープにおいては日付別テーブル分割で妥協することにしました...
おわりに
今回の対応で 10 分近くかかっていた集計処理を 1 分未満に短縮できました。
また、firebase と BigQuery はどちらも GCP に属するサービスなので、IAM を介して簡単安全に連携できる点も素晴らしいです。(今回作成したスクリプトはどちらも認証情報の記述が不要)
今後も引き続き BigQuery を活用していきたいと思います!