Firestore transaction based on non existent document (Collection level locking not available)

781 views
Skip to first unread message

Rahul Priyadarsi

unread,
Aug 4, 2020, 11:51:30 AM8/4/20
to Firebase Google Group

Based on this SO answer I came to know that firestore does not have collection level locking in a transaction. In my case, I have to ensure that the username field in users collection is unique before I write to a collection. For that, I write a transaction that does this:

  1. Executes a query on users collection to check if a document exists where username=something
  2. If it does exist, fail and return error from transaction
  3. If it does not exist, just run the write operation for the userId I want to update/create.

Now the issue here is that if two clients simultaneously try to run this transaction, both might query the collection and since the collection is not locked, one client might insert/update a document in collection while other won't see it.

Is my assumption correct? And if yes, then how to deal with such scenarios?


Kato Richardson

unread,
Aug 4, 2020, 12:59:41 PM8/4/20
to Firebase Google Group
Hi Rahul,

If you do the read ops as part of the transaction, then it's atomic and will fail / retry if the data changed between read and write.

Note that there are probably much simpler ways to implement this. One that comes immediately to mind is just creating the collections based on the user's authenticated uid. Guaranteed unique and only writable by the uid in question as long as you add a security rule.

Assuming your goal here is to "claim" a username and it may not be unique, you could do that by maintaining a map of username => uid. When someone attempts to claim it, they write their uid into the mapping for that username. If it has already been written, a rule prevents this. Transaction entirely optional in this case.

☼, Kato


--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/7063528c-3f3e-4f08-81e0-3c1b26fd14d6o%40googlegroups.com.


--

Kato Richardson | Developer Programs Eng | kato...@google.com | 775-235-8398

Rahul Priyadarsi

unread,
Aug 5, 2020, 12:08:12 AM8/5/20
to Firebase Google Group
Hi Kato!
Thanks for the response.
"If you do the read ops as part of the transaction, then it's atomic and will fail / retry if the data changed between read and write."

Are you sure about this if the documents read in transactions do not exist? I am not sure how can this be implemented without locking the whole collection because in my case my transaction should go ahead if I do not get any document as a result of the query for username. 
I posted on SO and got this answer which seems to make sense. https://stackoverflow.com/a/63250678/1291122 

Could you point to some documentation that might support your assumption of atomic operations for documents not being read/not existing in the transaction?


On Tuesday, August 4, 2020 at 10:29:41 PM UTC+5:30, Kato Richardson wrote:
Hi Rahul,

If you do the read ops as part of the transaction, then it's atomic and will fail / retry if the data changed between read and write.

Note that there are probably much simpler ways to implement this. One that comes immediately to mind is just creating the collections based on the user's authenticated uid. Guaranteed unique and only writable by the uid in question as long as you add a security rule.

Assuming your goal here is to "claim" a username and it may not be unique, you could do that by maintaining a map of username => uid. When someone attempts to claim it, they write their uid into the mapping for that username. If it has already been written, a rule prevents this. Transaction entirely optional in this case.

☼, Kato


On Tue, Aug 4, 2020 at 8:51 AM Rahul Priyadarsi <rahul...@gmail.com> wrote:

Based on this SO answer I came to know that firestore does not have collection level locking in a transaction. In my case, I have to ensure that the username field in users collection is unique before I write to a collection. For that, I write a transaction that does this:

  1. Executes a query on users collection to check if a document exists where username=something
  2. If it does exist, fail and return error from transaction
  3. If it does not exist, just run the write operation for the userId I want to update/create.

Now the issue here is that if two clients simultaneously try to run this transaction, both might query the collection and since the collection is not locked, one client might insert/update a document in collection while other won't see it.

Is my assumption correct? And if yes, then how to deal with such scenarios?


--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fireba...@googlegroups.com.

Sam Stern

unread,
Aug 5, 2020, 6:59:31 AM8/5/20
to Firebase Google Group
Hi Rahul,

You (and Doug on StackOverflow) are right.  Firestore transactions only retry if one of the documents read or written inside the transaction changes before the transaction ends.  So a document that does not exist is not involved in the constraint.

As Doug suggested on SO (and similar to what Kato said above) you'll need to find some collection where you can keep your unique usernames in document IDs.

- Sam

To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/f9c586bb-7566-4606-899a-b31e5270fda4o%40googlegroups.com.

Rahul Priyadarsi

unread,
Aug 5, 2020, 11:31:54 AM8/5/20
to Firebase Google Group
Thanks Sam for the response.

So assuming that 
So a document that does not exist is not involved in the constraint.

It implies that if at the start of a transaction, the document which I read did not exist. So if it happens to exist midway thru the transaction, the constraint(=retry transaction) should not happen.
In this case, it would again not ensure the uniqueness of the username. Because if clientA and clientB  both read the username collection (containing just usernames as id and the userId as values), at the start of a transaction and both find no matching docs. 
Then clientA's transaction happens and an entry is created in the username collection. But clientB also runs transaction simultaneously and it also goes ahead with the operation assuming username did not exist. Both of these transactions execute and at the end both have same username.

This does not seem to solve the problem if my assumption is correct.

Sam Stern

unread,
Aug 5, 2020, 11:35:16 AM8/5/20
to Firebase Google Group
Hi Rahul,

The constraint is on documents read and written.  So if Transaction A modifies the username while Transaction B is running, only one of them will succeed.

- Sam

To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/0c8f1414-1285-452f-a0b3-1f980b63dbf4o%40googlegroups.com.

Kato Richardson

unread,
Aug 5, 2020, 5:42:37 PM8/5/20
to Firebase Google Group
If it helps, here's an example demonstrating the behavior if you try to write to a null document and it is updated between the read and write events. It should demonstrate nicely how the transactions work.


Reply all
Reply to author
Forward
0 new messages