在Firebase云函数中,我有以下批处理写入,我认为这不足以避免竞争条件,我应该使用事务来代替:
const updates = {};
updates[`rooms/${request.data.roomId}/memberTotal`] = admin.database.ServerValue.increment(1); // Increment member total
updates[`members/${request.data.roomId}_${request.auth.uid}`] = {
dateJoined: admin.database.ServerValue.TIMESTAMP,
userId: request.auth.uid,
};
// Perform the batch update
try {
await admin.database().ref().update(updates);
// success
return {
success: true,
}
} catch (error) {
throw new functions.https.HttpsError("internal", "member set error", {details: error});
}
字符串
然而,据我所知,在实时数据库的事务中,我只能更新数据库中的一个位置。
考虑到这一点,在下面修改的代码中,我只在事务成功完成后的另一个位置插入新的member
,但如果后续写入失败,这会引入一个问题,如下所示:
try {
const transactionResult = await admin.database().ref(`rooms/${request.data.roomId}`).transaction((currentRoomData) => {
if (currentRoomData && currentRoomData.memberTotal !== undefined) {
// Increment member total
currentRoomData.memberTotal = (currentRoomData.memberTotal || 0) + 1;
return currentRoomData;
} else {
// If the room doesn't exist or memberTotal is undefined, abort the transaction
return undefined;
}
});
if (transactionResult.committed) {
// Perform the update for the member after the transaction
const memberUpdate = {};
memberUpdate[`members/${request.data.roomId}_${request.auth.uid}`] = {
dateJoined: admin.database.ServerValue.TIMESTAMP,
userId: request.auth.uid,
};
try {
await admin.database().ref().update(memberUpdate);
return { success: true };
} catch (error) {
// DO I ROLLBACK THE INCREMENTED FIELD HERE USING ANOTER TRANSACTION??
throw new functions.https.HttpsError("internal", "member set error", { details: error });
}
} else {
throw new functions.https.HttpsError("internal", "member set transaction aborted");
}
} catch (error) {
throw new functions.https.HttpsError("internal", "member set transaction error", { details: error });
}
型
如果后续写入members/${request.data.roomId}_${request.auth.uid}
失败,我该怎么办?
在这种错误情况下,需要回滚前一个事务,因为这个总数表示写入/members
位置的成员总数,但这似乎不是一种实用的方法。
从我的main函数中删除事务并使用onValueWritten
触发器来增加总数是否足以避免以下竞争条件?
onValueWritten("/members/{memberId}", (event) => {
const roomId = context.params.roomId;
// Reference to the counter
const counterRef = admin.database().ref(`rooms/${roomId}/memberTotal`);
// Run a transaction to increment the counter
return counterRef.transaction(currentValue => {
return (currentValue || 0) + 1;
}).catch(error => {
console.error('Incrementing memberTotal failed:', error);
});
});
型
1条答案
按热度按时间z9ju0rcb1#
根据上面的评论,根据Frank van Puffelen,应该可以将安全规则与我的云函数结合使用,以减轻竞争条件,我相信我可能有一个有效的实现。
有人能确认以下实现是否可以作为解决方案吗?
1.在一个用于插入新成员的云函数中,在插入新成员之前,id查询数据库中位于
rooms/$roomId/memberTotal
的现有成员总数。1.我将现有的
memberTotal
作为额外的键值对添加到新成员数据中,并使用事务将新成员插入到members/$roomId_$uid
中。1.我将实现一个单独的
onValueWritten
触发器函数,该函数仅在新成员成功写入数据库后执行。该触发器将使用一个事务以rooms/$roomId/memberTotal
递增成员总数。1.最后,* 这是关键,* id实现以下安全规则,以防止在现有
memberTotal
已更改时插入新成员:{“rules”:{“成员”:{“$memberId”:{ //确保提供的memberTotal与数据库“.validate”中的实际memberTotal匹配:“newData.child('memberTotal').val()= root.child('rooms').child(newData.child('roomId').val()).child('memberTotal').val()”} },} }