GameWith Developer Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

firestore で物理削除を使うか、論理削除を使うか考える #GameWith #TechWith #firebase

こんばんは、peka3 と申します!

最近はポケモンSVでランクマッチに潜っています

GameWithのポケモン交換掲示板も大変賑わっております!!

今回は firebase/firestore を使った投稿系サービスにおける「削除」の話になります

運用してきて思ったこと、気づいたことを書きます

firestoreで作られた掲示板における「削除」を考える

掲示板を作るとき、違反申告や削除申請があった投稿を削除するという運用がもれなく発生すると思うのですが

その際に、1.論理削除を使うか、2.物理削除を使うか、について考えてみます

firestoreのコレクションとしては以下のような構成になると思います

- posts
  - documentId: xxxx
  - name: xxxx
  - comment: xxxx
  - createdAt: xxxx
  - ipAddress: xxxx

このような構成にすると ipAddress だけをセキュリティルール上非表示にするのが難しくなるため、

サブコレクションに格納するのが良いのですが、今回は話を簡単にするためにこれで行きます

firestore で 論理削除 を選ぶか 物理削除 を選ぶか

結論を言ってしまうとケースバイケースだと思いますし、物理削除でもログは保存しておくのが好ましいと思います

個人的には、

  • 複雑なクエリを要するドキュメント構成、UI設計になっている場合は物理削除
  • シンプルなクエリですむ場合は論理削除でも○

で使い分ければ良いかなと思ってます

理由としては2つあります

一つはfirestoreのインデックス数に上限が設定されているからです

インデックスの概要  |  Firestore  |  Google Cloud

isDeletedのような削除フラグを設けた場合、複合インデックスの数はその分増えることになります

また、ジグザグマージ結合という方式を使えば、複合インデックスはそこまで増やさなくても複雑な検索クエリに対応できたりするのですが

以下のページに有る通り、クエリが遅くなる原因になったりします

Google Developers Japan: Cloud Firestore のクエリが遅くなる理由

この懸念があるようなドキュメント構成、UI設計になっている場合は、物理削除を使ったほうがパフォーマンス上も運用上も良いと思います

では物理削除の場合、実際 firestore ではどのような対応が必要になるか考えてみます

投稿ドキュメントを 物理削除 する場合

前述の通り物理削除と言ってもただ消してしまっては、あとからログを掘り返すことが出来なくなるため不安が残ります

そこで、投稿削除をトリガーに、別の「削除済み投稿コレクション(deletedPosts)」にドキュメントをコピーするような仕組みを作ります

これには firebase の functions を使います

以下はコードの一例になります

このままだと動かないので参考程度に見てください

まずこのようにfunctionsを定義しておき、moveDeletedPostsに削除する実装を書いておきます

exports.deletedPosts= functions
  .region("asia-northeast1")
  .runWith(runtimeOpts)
  .firestore.document(
    "/posts/{docId}"
  )
  .onDelete(moveDeletedPosts);

moveDeletedPosts では、削除済み投稿コレクションdeletedPosts にsetするようにします

const moveDeletedPosts = async (
  postsSnapshot: functions.firestore.QueryDocumentSnapshot
): Promise<void> => {
  const postData = postsSnapshot.data();
  const postRef = postsSnapshot.ref;
  const postId = postsSnapshot.id;

  const db = admin.firestore();

  // ログ保管庫
  const deletedPostRef = db.doc(`/deletedPosts/${postId}`);

  // 削除済み投稿コレクションに保存する
  // 削除実行日時も保存するならこのようにします
  postData.deletedAt = admin.firestore.FieldValue.serverTimestamp();
  await deletedPostRef.set(postData);
};

上記のfunctionsをdeployしておくだけで、Postsのドキュメントが削除されるたびに、以下のコレクションにドキュメントが保存されていきます

- deletedPosts
  - documentId: xxxx
  - name: xxxx
  - comment: xxxx
  - createdAt: xxxx
  - deletedAt: xxxx
  - ipAddress: xxxx

このように、物理削除の懸念であるログ保存も、functionsを使えばわずかなコードで実装ができます

論理削除を使った際に、複雑なインデックス管理、状態管理が発生しそうであれば、物理削除を使ったほうがよいと自分は考えています