在 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
位置的成员总数,需要回滚之前的事务,但这似乎不是一个实用的方法。
从我的主函数中删除事务并使用
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);
});
});
根据上面的评论,根据 Frank van Puffelen,应该可以将安全规则与我的云功能结合使用来减轻竞争条件,并且我相信我可能有一个有效的实现。
有人能够确认对我的实施进行以下更改是否可以作为解决方案吗?
在第一个云函数中,在插入新成员之前,id 查询数据库以获取位于
rooms/$roomId/memberTotal
的现有成员总数。
在第一个云函数中,我会删除一起增加
memberTotal
字段的交易。
Id 将现有
memberTotal
作为附加键值对添加到新成员数据中,并使用事务将新成员插入到 members/$roomId_$uid
我会实现一个单独的
onValueWritten
触发器,仅当成员成功写入数据库后,该触发器才会使用事务来增加 rooms/$roomId/memberTotal
处的成员总数。
最后,id 实施以下安全规则,以防止在现有 memberTotal 发生更改时插入新成员:
{ “规则”:{ “成员”:{ “$memberId”:{ // 确保提供的memberTotal与数据库中的实际memberTotal匹配 ".validate": "newData.child('memberTotal').val() === root.child('rooms').child(newData.child('roomId').val()).child('memberTotal ').val()" } }, // 附加规则... } }