Firestore - 删除大量文档而没有并发问题

Firestore - Deleting lot of documents without concurrency issues

我使用 Firestore 开发了一款游戏,但我注意到我的预定云功能存在一些问题,该功能会删除 5 分钟前创建但未满的房间完成.

为此,我运行下面的代码。

async function deleteExpiredRooms() {
  // Delete all rooms that are expired and not full
  deleteExpiredSingleRooms();

  // Also, delete all rooms that are finished
  deleteFinishedRooms();
}

删除已完成的房间似乎可以正常工作:

async function deleteFinishedRooms() {
  const query = firestore
    .collection("gameRooms")
    .where("finished", "==", true);

  const querySnapshot = await query.get();

  console.log(`Deleting ${querySnapshot.size} expired rooms`);

  // Delete the matched documents
  querySnapshot.forEach((doc) => {
    doc.ref.delete();
  });
}

但是我在删除5分钟前创建的未满的房间时遇到并发问题(一个房间满了当2个用户在房间里,这样游戏才能开始)。

async function deleteExpiredSingleRooms() {
  const currentDate = new Date();

  // Calculate the target date
  const targetDate = // ... 5 minutes ago

  const query = firestore
    .collection("gameRooms")
    .where("full", "==", false)
    .where("createdAt", "<=", targetDate);

  const querySnapshot = await query.get();

  console.log(`Deleting ${querySnapshot.size} expired rooms`);

  // Delete the matched documents
  querySnapshot.forEach((doc) => {
    doc.ref.delete();
  });
}

因为在删除房间的过程中,用户可以在房间完全删除之前进入。

有什么想法吗?

注意:搜索房间我使用的是交易

firestore.runTransaction(async (transaction) => {
  ...

  const query = firestore
    .collection("gameRooms")
    .where("full", "==", false);

  return transaction.get(query.limit(1));
});

您可以使用 BatchWrites:

const query = firestore
    .collection("gameRooms")
    .where("full", "==", false)
    .where("createdAt", "<=", targetDate);

const querySnapshot = await query.get();

console.log(`Deleting ${querySnapshot.size} expired rooms`);

const batch = db.batch();

querySnapshot.forEach((doc) => {
  batch.delete(doc.ref);
});

// Commit the batch
batch.commit().then(() => {
    // ...
});

A batched write can contain up to 500 operations. Each operation in the batch counts separately towards your Cloud Firestore usage.

这应该立即删除符合该条件的所有房间。使用循环删除它们可能需要一段时间,因为它会一个接一个地发生。

如果您担心批量写入中 500 个文档的限制,请考虑使用 Promise.all,如下所示:

const deleteOps = []
querySnapshot.forEach((doc) => {
  deleteOps.push(doc.ref.delete());
});

await Promise.all(deleteOps)

现在为了防止用户加入正在删除的房间,在 Cloud Functions 中这样做有点困难,因为所有实例 运行 独立并且可能存在竞争条件。

为避免这种情况,您必须手动检查用户尝试加入的房间是否超过 5 分钟并且玩家数量是否较少。这只是一个检查,以确保房间正在被删除或将被立即删除。

function joinRoom() {
 // isOlderThanMin()
 // hasLessNumOfPlayers()
 // return 'Room suspended'
}

因为过滤哪些房间应该被删除的逻辑是相同的,所以这应该不是问题。

也许您正在寻找 交易 请在此处查看文档:https://firebase.google.com/docs/firestore/manage-data/transactions

或观看解释并发问题以及 批量写入 事务 之间差异的 YouTube 视频:https://youtu.be/dOVSr0OsAoU