繁体   English   中英

在 Google Cloud Firestore 的子集合中更新依赖于多个文档的字段时,如何避免竞争条件?

[英]How can I avoid a race condition when updating a field that's dependent multiple documents in a subcollection in Google cloud firestore?

这是我的 Firestore collections 数据模型的简化表示

// /posts/{postId}
interface Post {
  id: string; 
  lastCommentedAt: Timestamp | null
}

// /posts/{postId}/comments/{commentId}
interface Comment {
  id: string;
  createdAt: Timestamp;
}

所以有一个帖子集合,每个帖子中都有评论的子集合。

如果帖子没有评论,则lastCommentedAt字段应为 null。否则, lastCommmentedAt应与该帖子的最新评论的createdAt值匹配。 该字段的用途是使我能够查询帖子并根据最近评论的帖子对它们进行排序。

我在考虑删除评论的场景时遇到了麻烦。

删除评论时,如果被删除的评论是该帖子的最新评论,则lastCommentedAt的值将变得陈旧,因此需要更新。 这是我努力想出一个安全解决方案的地方。

我的第一个想法是查询与要删除的评论不匹配的帖子的最新评论,然后进行批量写入,删除评论并更新帖子的lastCommentedAt 此处使用 Firebase web SDK 的示例 Javscript 代码:

async function deleteComment(post, comment) {
  const batch = writeBatch(firestore);
  const postRef = doc("posts", post.id);
  const commentRef = doc("posts", post.id, "comments", comment.id);
  const commentsCollection = collection("posts", post.id, "comments");

  const recentCommentSnapshot = await getDocs(
    query(
      commentsCollection,
      where("id", "!=", comment.id),
      orderBy("createdAt", "desc"),
      limit(1)
    )
  );

  let lastCommentedAt = null;
  if (recentCommentSnapshot.docs.length > 0) {
    lastCommentedAt = recentCommentSnapshot.docs[0].data().createdAt;
  }

  batch.delete(commentRef);
  batch.update(postRef, { lastCommentedAt });
  await batch.commit();
}

但是,我相信如果在查询之后但在写入之前创建了新评论,或者如果在查询之后但在写入之前删除了最近的评论,那么上面的代码将容易受到竞争条件的影响。

所以我想我需要一个交易,但你不能在交易中查询文件,所以我不太确定从这里到 go 的哪里。

您可以使用Firestore Getting real-time updates来监听快照之间的变化。 这是文档中的示例:

import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
        console.log("New city: ", change.doc.data());
    }
    if (change.type === "modified") {
        console.log("Modified city: ", change.doc.data());
    }
    if (change.type === "removed") {
        console.log("Removed city: ", change.doc.data());
    }
  });
});

在这里,您可以收听对文档的任何更改。


检查我创建的代码,该代码在从 firestore 中删除文档时触发并自动更新posts.lastCommentedAt

    var latestCreatedAt = null;
    const q = query(
        collection(db, "posts", "post.id", "comments")
    );
    const recentCommentQuery = query(
        collection(db, "posts", "post.id", "comments"),
        orderBy("createdAt", "desc"),
        limit(1)
    );
    const postsRef = query(collection(db, "posts"));
    const postRef = doc(db, "posts", "post.id"); 

    async function deletedComment (createdAt) {
        const querySnapshot = await getDocs(recentCommentQuery);

        querySnapshot.forEach((doc) => {
            latestCreatedAt = doc.data().createdAt.toDate();
        });

        await updateDoc(postRef, {
            lastCommentedAt: latestCreatedAt
        })
    }

    const unsubscribe = onSnapshot(q, (snapshot, querySnapshot) => {
        snapshot.docChanges().forEach((change) => {
            if (change.type === "removed") {
                deletedComment(change.doc.data().createdAt.toDate());
            }
        });
    });

您甚至可以在添加或修改也会触发更新posts.lastCommentedAt的文档时添加条件

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM