如何处理一个成功的事务,当后续写入Firebase实时数据库失败时,该事务应该回滚?

tp5buhyn  于 2023-11-21  发布在  其他
关注(0)|答案(1)|浏览(110)

在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);
        });
    });

z9ju0rcb

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()”} },} }

相关问题