Search code examples
firebasegoogle-cloud-firestoretransactionsgoogle-cloud-functions

Firebase Cloud Function: Firestore transaction doesn't rerun on concurrent edit


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?


Solution

  • 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