Search code examples
databasefirebasetransactionsgoogle-cloud-firestoreconsistency

Update values in separate Collections with Firestore in a denormalised DB


I have two collections:

Collection c1
-- doc1
-- -- name: "doc1Name"
-- -- position: 1
-- doc2
-- -- name: "doc2Name"
-- -- position: 2

Collection c2
-- doc1
-- -- name: "name1"
-- -- reference: "doc1Name"
-- -- position: 1
-- doc2
-- -- name: "name2"
-- -- reference: "doc2Name"
-- -- position: 2
-- doc3
-- -- name: "name3"
-- -- reference: "doc2Name"
-- -- position: 2

If someone sets c1.doc2.position = 3, I need to update c2.doc2.position and c2.doc3.position to 3 as well.

However, Firestore does not allow to update fields based on a given condition, so I need to iterate all docs belonging to c2 and update the position field when reference = "doc2Name". If I use a batch write or a transaction, I can't query C2 within their body, because if I do, Firestore returns an error like "Transaction has already completed.", due to the asynchronous nature of the listeners. And I can't update one of the two collections in the completion handler because that would leave the DB inconsistent if the second batch/transaction fails.

This a typical scenario when dealing with denormalised DBs, and denormalization is also quite common when dealing with Firestore. Is there any solution with Firestore that does not imply cloud functions?


Solution

  • You can't atomically transact or batch on the dynamic results of some query. You have to be able to identify all the documents before the transaction or batch begins. You will have this issue no matter if you are writing from a mobile client or a server (including Cloud Functions, which might also process events out of your intended order).

    Your only option might be to transaction on the entire contents of both collections, assuming they don't change size. If the collection can change size at any time, then I don't think this particular model is going to work for your use case, as it's not possible to lock the entire database in order to prevent conflicting changes from occurring.