34

Looking at https://firebase.google.com/docs/reference/js/firebase.firestore.Transaction I see four methods: delete, set, get, update.

I was about to construct a lovely little collection query and pass it to .get, but I see the docs say that .get "Reads the document referenced by the provided DocumentReference."

It appears this means we cannot get a collection, or query a collection, with a Transaction object.

I could query those with the query's .get() method instead of the transaction's .get() method, but if the collection changes out from under me, the transaction will end up in an inconsistent state without retrying.

It seems I am hitting a wall here. Is my understanding correct? Can we not access collections inside a transaction in a consistent way?

Verdagon
  • 2,456
  • 3
  • 22
  • 36

2 Answers2

29

Your understanding is correct. When using the web and mobile SDKs, you have to identify the individual documents that you would like to ensure will not change before your transaction is complete. If those documents come from a collection query ahead of time, fine. But think for a moment about how not-scalable it would be if you had to track every document in a (very large) collection in order to complete your transaction.

However, for backend SDKs, you can perform a query inside a transacction and effectively transact on all the documents that were returned by the query, up to the limit of number of documents in a transaction (500).

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Makes sense! Looks like I'll be using a series of distributed counters to do what I was trying to do instead. Thanks for the quick answer! – Verdagon Apr 28 '18 at 00:59
  • It's not inherently un-scaleable, you can do things like table locks in databases. Firestore is also likely to have lots of small collections, so it could be performant in many cases. It's just a limitation of Firestore. – Wil Gieseler Mar 22 '20 at 00:16
  • 1
    @WilGieseler A SQL table lock is **one** lock, and all queries can consult that one lock easily. Firestore isn't really at all like SQL. Locking 5 million documents individually (which is the only way that Firestore deals with data - at the document level, for scalability purposes) would be critically bad for a database that also needs to scale to support 1mil concurrent connections. – Doug Stevenson Mar 22 '20 at 00:27
  • 3
    My point is that it isn't a fair characterization to imply the user OP is asking for a feature that is inherently unscalable. Firestore could, if they wanted to, allow transactions to lock subcollections, and this would be a useful, performant feature and work great for many use cases. I don't see why they couldn't implement a collection lock as **one** lock. – Wil Gieseler Mar 22 '20 at 21:25
  • 3
    @WilGieseler Because a collection isn't an "entity" that can be locked like a table - it's entirely different in nature. If an arbitrary collection was simply lockable, then the product wouldn't scale as advertised, as misuses of that lock would bring down production systems (which is obviously not an option for cloud products like this). That said, feel free to submit a feature request if you think this is low-hanging fruit, but I honestly don't think anything will happen. https://support.google.com/firebase/contact/support – Doug Stevenson Mar 22 '20 at 21:35
  • I didn't say it was low-hanging fruit, just a frustrating limitation! – Wil Gieseler Mar 23 '20 at 20:21
  • @WilGieseler Yeah, that's the way all databases work - every advantage comes with necessary disadvantages. I outlined a nearly-complete set of advantages (design goals, if you will) along with the necessary disadvantages required to make it work as designed, along with workarounds [in this post](https://medium.com/firebase-developers/the-top-10-things-to-know-about-firestore-when-choosing-a-database-for-your-app-a3b71b80d979). Firestore limitations are also [well-documented](https://firebase.google.com/docs/firestore/quotas). – Doug Stevenson Mar 23 '20 at 20:25
  • That's unfortunate! I want to assure my collection has one document only. Since I can't query all documents in a transaction, I can't assure and fix this on the fly. :/ – Michel Feinstein Sep 22 '20 at 19:44
  • @MichelFeinstein This sounds like an XY problem. If you want a collection to only have one document, then you probably don't want a collection at all. – forresthopkinsa Aug 09 '21 at 02:10
  • Perhaps I'm missing something here, but I'm having the same problem as the OP using firebase admin for backend firestore access. There's a get method that takes a query...but I'm not clear how I query a specific collection with that (maybe this is just my lack of knowledge on firestore). It looks like you can query based on doc fields...but I want to limit that to only a single collection in the DB. It seemed like that's what the user was asking for...but I don't see how you can even do that with firebase admin as you mentioned in your answer. – stuckj Jan 26 '22 at 20:36
  • @stuckj Build a Query object just like you would normally without a transaction. Then pass it to the transation get() that takes a Query. It is no more complex than that. – Doug Stevenson Jan 27 '22 at 01:05
  • @DougStevenson, this again may just be my unfamiliarity with firestore. But, the way I query without a transaction if I want to get basically all docs in a collection is `db.collection('').get()` (this is using the admin SDK). In the query docs (https://googleapis.dev/nodejs/firestore/latest/Query.html) I don't see how I can narrow it to just a specific collection. Unless perhaps it's just the syntax for the fieldpath that allows you to specify the collection? All examples just look like it's the field name for a doc. – stuckj Jan 27 '22 at 03:45
  • @stuckj Your query is `db.collection('')`. A CollectionReference is a Query that gets all documents in that collection. – Doug Stevenson Jan 27 '22 at 04:17
  • @DougStevenson, yes, but that's against the DB directly, not the transaction. `collection` isn't a valid function on a transaction object. E.g., for a `Transaction`, `t`, you can't do `t.collection('')`. :) I believe that's the point the OP, @Verdagon, was making. You can read directly from the DB, but nothing prevents those docs being, e.g., removed during the transaction. Currently, I'm querying the DB directly and then re-getting all of the docs by ref with `getAll` on the transaction object. Is this really the only way? Seems odd. – stuckj Jan 27 '22 at 19:01
  • 1
    @stuckj No, I'm saying that `db.collection('')` returns a Query object that you can pass to the transaction's `get()`. The Query object doesn't have to be derived from the transaction object. A Query object is just a description of how to find data that you'd like from Firestore (What collection do you want? What filters do you want on that?). A query object doesn't actually perform any actions until something tries to get() it, and the transaction will know how to do that correctly. – Doug Stevenson Jan 27 '22 at 19:28
  • @DougStevenson Ooohhhh, that makes a lot more sense. That clarifies quite a bit. Thanks for sticking with me. ;-) – stuckj Jan 27 '22 at 21:22
9

You can run queries (not just fetch single documents) in a transaction's get() method, but that's only for server execution. So if you really need to do that (say for maintaining denormalized data's consistency), you can put that code in a cloud function and make use of server-side transactions

JayCodist
  • 2,424
  • 2
  • 12
  • 28