背景
この記事の続きっぽい感じ。
背景
Firestoreのデータ操作には、一括で多くのドキュメントを扱うことが問われることが多い。
バッチ処理は、複数の操作を一つのトランザクションとして処理し、データ整合性を保ちながら効率的にタスクを完了させることができるため、特にサブコレクションを含む大量のドキュメントを扱う際に使えそうと考えた。
この記事の対象者
この記事は、Firestoreを使用しているが、サブコレクションの削除処理に頭を悩ませている開発者、またはFirebaseを学習中の方々に少しでも参考になればと思いメモを残しておく。
問題点
あるドキュメントが削除された際に、そのドキュメントに紐づくサブコレクションが自動的に削除されない。
例えば、あるデータに関連する多数のコレクションやサブコレクションがある場合、そのデータのドキュメントを削除しても、これらのコレクションやサブコレクションは残り続け、不要なデータが蓄積される可能性がある。
このような状況は、データベースのパフォーマンスを低下させるだけでなく、データ管理の複雑化を招き、最終的にはアプリケーションのメンテナンス性にも悪影響を及ぼす。
やること
Firebase Functionsを活用して、特定のドキュメントが削除された際に、それに紐づくサブコレクションも自動的に削除する処理を実装する。
その際、バッチ処理などを使ってメモリリミットを回避できるように気を配る。
解決案
Firebase Admin SDKを使用して、ドキュメントとそのサブコレクションを一括で削除する方法を採用。
Admin SDKにはrecursiveDeleteメソッドがあり、これを使用することで指定したドキュメントとそのサブコレクションを再帰的に削除することができる。
完成したコード
import * as functions from "firebase-functions/v1";
import * as FirebaseAdmin from "firebase-admin";
import * as logger from "firebase-functions/logger";
import { QueryDocumentSnapshot } from "@google-cloud/firestore";
FirebaseAdmin.initializeApp();
const db = FirebaseAdmin.firestore();
// バッチ処理の関数
async function processBatch(
collectionId: string,
subcollectionId: string,
lastDoc?: QueryDocumentSnapshot | null,
) {
const BATCH_SIZE = 10;
let query = db.collection(`collections/{collectionId}/subcollections`)
.where("subcollectionId", "==", subcollectionId)
.limit(BATCH_SIZE);
if (lastDoc) {
query = query.startAfter(lastDoc);
}
const snapshot = await query.get();
if (snapshot.empty) {
return null; // タスクがなければ処理終了
}
const batch = db.batch();
for (const doc of snapshot.docs) {
batch.delete(doc.ref);
}
// バッチ処理を実行
await batch.commit();
// 次のバッチのために最後のドキュメントを返す
return snapshot.docs[snapshot.docs.length - 1];
}
export const deleteDocuments = functions
.region("asia-northeast1")
.firestore.document("collections/{collectionId}/subcollections/{subcollectionId}")
.onDelete(async (snapshot, context) => {
const collectionId = context.params.collectionId;
const subcollectionId = context.params.subcollectionId;
let lastDoc = null;
do {
lastDoc = await processBatch(collectionId, subcollectionId, lastDoc);
} while (lastDoc);
logger.log(`Successfully deleted all subcollection: ${subcollectionId}`);
});
deleteDocument
は、Firestoreドキュメントが削除されると自動的にトリガーされるCloud Functionである。
この関数はprocessBatch
を繰り返し呼び出し、関連するすべてのサブコレクションが削除されるまで処理を続ける。
processBatch
は、指定されたサブコレクションを持つドキュメントをバッチサイズの制限内で検索し、見つかったドキュメントとそれに関連するサブコレクションを削除する。バッチ処理により、これらの削除操作が一つのトランザクションとして扱われ、処理の効率が大幅に向上する。
お疲れ様でした。
この方法により、Firestoreのデータ構造における親子関係を持つデータの整合性を維持しつつ、不要になったデータを効率的に削除できると思う。
また、この処理はサーバーサイドで実行されるため、クライアント側の負荷を軽減できる利点もある。
わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。