当后续写入 Firebase 实时数据库失败时,如何解决应回滚的成功事务?

问题描述 投票:0回答:1

在 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);
        });
    });
javascript firebase firebase-realtime-database transactions batch-processing
1个回答
0
投票

根据上面的评论,根据 Frank van Puffelen,应该可以将安全规则与我的云功能结合使用来减轻竞争条件,并且我相信我可能有一个有效的实现。

有人能够确认对我的实施进行以下更改是否可以作为解决方案吗?

  1. 在第一个云函数中,在插入新成员之前,id 查询数据库以获取位于

    rooms/$roomId/memberTotal
    的现有成员总数。

  2. 在第一个云函数中,我会删除一起增加

    memberTotal
    字段的交易。

  3. Id 将现有

    memberTotal
    作为附加键值对添加到新成员数据中,并使用事务将新成员插入到
    members/$roomId_$uid

  4. 我会实现一个单独的

    onValueWritten
    触发器,仅当成员成功写入数据库后,该触发器才会使用事务来增加
    rooms/$roomId/memberTotal
    处的成员总数。

  5. 最后,id 实施以下安全规则,以防止在现有 memberTotal 发生更改时插入新成员:

    { “规则”:{ “成员”:{ “$memberId”:{ // 确保提供的memberTotal与数据库中的实际memberTotal匹配 ".validate": "newData.child('memberTotal').val() === root.child('rooms').child(newData.child('roomId').val()).child('memberTotal ').val()" } }, // 附加规则... } }

© www.soinside.com 2019 - 2024. All rights reserved.