According to Firebase's docs on Firestore transactions:
In the case of a concurrent edit, Cloud Firestore runs the entire transaction again.
Is this also true when running a transaction inside Firebase Cloud Functions using the admin SDK? Based on my tests, it doesn't seem to do so.
I tested this with this fictitious example. If all of the cars
have been deleted, I'm deleting the carsSummary/index
document. To ensure that there are no race conditions, I'm wrapping this in a transaction.
try {
await db.runTransaction(async transaction => {
const results = await transaction.get(db.collection(`cars`));
console.log('Running transaction');
await sleep(10000); // during these 10 seconds, a new car gets added
if (results.size === 0)
transaction.delete(db.doc(`carsSummary/index`));
});
} catch (error) {
console.error(error);
}
With the test above, the delete operation correctly doesn't execute if a car
is added during the sleep(10000)
, thereby invalidating the results
query. However, the transaction doesn't re-run (i.e. Running transaction
console.log only gets called once). Is this the correct behavior? Is the Firebase documentation wrong?
The answer lies in the Firestore documentation here: https://googleapis.dev/nodejs/firestore/latest/Transaction.html#get
get(refOrQuery) → {Promise} Retrieve a document or a query result from the database. Holds a pessimistic lock on all returned documents.
The database holds a pessimistic lock on the returned documents, so it won't try to rerun the transaction on a concurrent edit.
Note that the behavior is different if you're using the emulator (at least in Firebase CLI v9.16.0), and the transaction will rerun on a concurrent edit even when using the Admin SDK: https://github.com/firebase/firebase-tools/issues/3928