In a Firebase cloud function, i have the following batch write which i assume is not enough to avoid race conditions, and that i should be using a transaction instead:
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});
}
It is my understanding however, that in a transaction for the realtime database, i can only update one location in the database.
With that in mind, in the following revised code, i only insert the new member at another location subsequent to the transaction completing successfully, but this introduces an issue if the subsequent write fails, noted below:
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 });
}
What should i do if the subsequent write to members/${request.data.roomId}_${request.auth.uid} fails?
In that error case, the previous transaction would need to be rolled back given that this total represents the total number of members written to the /members location.
Does this suggest that i should initiate yet another transaction to rollback the total if the subsequent write fails? That does not seem practical to me and signals that i am approaching this all wrong.
What is the best approach for handling this scenario in the realtime database?
Is a batch write the only option i have if i need both locations to update atomically?

