[英]How can I avoid a race condition when updating a field that's dependent multiple documents in a subcollection in Google cloud firestore?
Here's a simplified representation of the data models for my firestore collections这是我的 Firestore collections 数据模型的简化表示
// /posts/{postId}
interface Post {
id: string;
lastCommentedAt: Timestamp | null
}
// /posts/{postId}/comments/{commentId}
interface Comment {
id: string;
createdAt: Timestamp;
}
So there's a collection of posts, and within each post is a subcollection of comments.所以有一个帖子集合,每个帖子中都有评论的子集合。
If a post has no comments, the lastCommentedAt
field should be null. Otherwise, lastCommmentedAt
should match the createdAt
value for the most recent comment on that post.如果帖子没有评论,则lastCommentedAt
字段应为 null。否则, lastCommmentedAt
应与该帖子的最新评论的createdAt
值匹配。 The utility of this field is to enable me to query for posts and sort them by ones that have recent comments.该字段的用途是使我能够查询帖子并根据最近评论的帖子对它们进行排序。
I'm having trouble thinking through the scenario of deleting comments.我在考虑删除评论的场景时遇到了麻烦。
When a comment is deleted, the value of lastCommentedAt
will become stale if the comment being deleted is the most recent comment on that post, and so will need to be updated.删除评论时,如果被删除的评论是该帖子的最新评论,则lastCommentedAt
的值将变得陈旧,因此需要更新。 This is where I'm struggling to come up with a safe solution.这是我努力想出一个安全解决方案的地方。
My first thought was to query for the most recent comment on the post that doesn't match the comment to be deleted, and then do a batched write where I delete the comment and update lastCommentedAt
on the post.我的第一个想法是查询与要删除的评论不匹配的帖子的最新评论,然后进行批量写入,删除评论并更新帖子的lastCommentedAt
。 Example Javscript code using the Firebase web SDK here:此处使用 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();
}
However, I believe the code above would be vulnerable to a race condition if new comments are created after the query but before the writes, or if the recent comment was deleted after the query but before the writes.但是,我相信如果在查询之后但在写入之前创建了新评论,或者如果在查询之后但在写入之前删除了最近的评论,那么上面的代码将容易受到竞争条件的影响。
So I think I need a transaction, but you can't query for documents in a transaction, so I'm not really sure where to go from here.所以我想我需要一个交易,但你不能在交易中查询文件,所以我不太确定从这里到 go 的哪里。
You could use Firestore Getting real-time updates which listens to changes between snapshots.您可以使用Firestore Getting real-time updates来监听快照之间的变化。 Here's an example from the documentation:这是文档中的示例:
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());
}
});
});
In here you can listen to any changes to your documents.在这里,您可以收听对文档的任何更改。
Check the code that I created which is triggered when the document is deleted from firestore and automatically update the posts.lastCommentedAt
:检查我创建的代码,该代码在从 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());
}
});
});
You can even add a condition when adding or modifying documents that will also trigger to update the posts.lastCommentedAt
.您甚至可以在添加或修改也会触发更新posts.lastCommentedAt
的文档时添加条件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.