Firebase cloud functions incrementing queue number - firebase

I'm working on a Flutter Restaurant application where each restaurant has a cloud firestore document and it in a field called queueNumber this value starts at 1 and with every order it increases by 1.
I'm trying to make sure each order has a unique queue number. I have a cloud function that triggers whenever a new document created in the orders collection. Here is the following code.
.onCreate(async (snapshot, context) => {
const orderData = snapshot.data();
const id = orderData.id;
if (orderData && orderData.restaurantId != null) {
return restDoc.update({
queueNumber: admin.firestore.FieldValue.increment(1)
})
}
});
So the user places an order with the existing queueNumber in the restaurant document. Than the cloud function increments the queueNumber so the next request has a queueNumber that is 1 higher than the previous.
Here is the problem: Sometimes when two orders are placed one after another they get the same queueNumber. The end result in restaurant document is correct but the individual orders get the wrong number (ex: Order 1 has 51 Order 2 has 51 Restaurant document has 53)
Is there a way to fix this method or a better approach to handle the queue numbers
Thanks.

You're running into a race condition between each of the clients that's adding a document. Firestore doesn't offer a built-in way to ensure that a field is unique, nor does it offer a way to automatically and safely set a value of a field based on the contents of other documents. This wouldn't scale in the way that Firestore requires.
You should first find a way to implment your app without increasing numbers like this. Check if maybe a timestamp is a better way to track the time order in which documents are added. That will scale much better.
If you absolutely need increasing numbers like this, you will have to involve a whole new document just to track the latest number assigned, and use that document in a transaction when adding new documents. The transaction will have to:
Read the counter document
Increment the count value in memory
Create the new document with this value
Also update the counter document with this value
All of this must be done within the transaction, or will not be safe.

Related

Firebase snapshot listener filter

I have a Firestore DB. Is it possible to filter snapshots coming from it based on some field if add add a listener? What I need is: “send me an updated document only if this field equals this value”
What I surely can do is just check manually each new snapshot and return/propagate document if it passes the filter but I was thinking about sparing some transferred data and hit less limits
You can make a query by filtering something then adding a listener for this like the below code:
// get all document in collection "cities" that has attribute "state" equal to "CA"
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
let cities = documents.map { $0["name"]! }
print("Current cities in CA: \(cities)")
}
Ref: https://firebase.google.com/docs/firestore/query-data/listen#listen_to_multiple_documents_in_a_collection
Cloud Firestore listeners fire on the document level. So if you have multiple fields in a document and the value of the fields in the document changes, your listener will fire. So you'll have to pay a read operation, each time something in the document changes.
It's true that you can attach a listener and get only the documents that have set a field to a particular value, but that doesn't mean you can restrict the SDK to read the value only when that field is changed to a value of your choice.
There is no way you can read a document, only when a field gets a particular value. You're always charged with a read operation, each time a value changes, no matter what the value is. So if the new value is the value that passes your filters, then you can go ahead with your logic.
I was thinking about sparing some transferred data and hitting less limits.
Everything in Firestore it's about the number of reads, writes, and deletes you perform. And the amount of bandwidth you are using. But unfortunately, you cannot reduce the costs that way.

Specify format for autogenerated Firestore document Ids

I'm creating a small game where people are able to join a room using a six-digit pin. Every room is represented by a document in a Firestore collection, where the room pin is the id of a room document.
My initial idea was to randomly generate a six-digit pin and check if a document with that id exists. If it does, create a new document with the generated pin, if not, generate a new pin and check if that id is free. This method will work, however, with a bit of bad luck it might cause a lot of unnecessary requests to the database.
My question is, therefore: is it possible to specify a format of the autogenerated id's? Or, if it is not possible, is there a way to fetch all documents id's to locally check whether or not the id exists?
You cannot specify a format for the auto-generated IDs but you can check if a room with same ID exists. If yes, then try new ID else create a room with same ID.
async function addRoom(roomId) {
const roomRef = admin.firestore().collection("rooms").doc(roomId)
if ((await roomRef.get()).exists) {
return addRoom(generatePin())
} else {
await roomRef.set({ ... })
}
return `${roomId} created`
}
function generatePin() {
return String(Math.floor(Math.random() * 999999) + 100000)
}
return addRoom(generatePin())
.then(() => console.log("Room created"))
.catch((e) => console.error(e))
PS: This might end up in some recursive state so I'd recommend using Firestore's IDs or uuid-int is you need numerical IDs only.
There is no way for you to specify the format of automatic IDs generated by the Firestore SDKs. They are purely random, and have an amount of entropy that statistically guarantees there won't be collisions (the chance where two clients generate the same ID is infinitesimally small), and minimizes the chance of hotspots (created when writing lots of documents or index values to the same area on a disk).
You can generate whatever auto-ID format you want however. You'll just have to accept a higher chance of collisions, as you already did, and the fact that you may experience hotspots when the documents are in the same area on a disk.

Preventing timestamp overlaps in Firestore collection

This is a follow-up/elaboration to a previous question of mine.
In the case of a collection of documents containing a time range represented by two timestamp fields (start and end), how does one go about guaranteeing that two documents don't get added with overlapping time ranges?
Say I had the following JavaScript on form submit:
var bookingsRef = db.collection('bookings')
.where('start', '<', booking.end)
.where('end', '>', booking.start);
bookingsRef.get().then(snapshot => {
// if a booking is found (hence there is an overlap), display error
// if booking is not found (hence there is no overlap), create booking
});
Now if two people were to submit overlapping bookings at the same time, could transactions be used (either on the client or the server) to guarantee that in between the get and add calls no other documents were created that would invalidate the original collection get query where clauses.
Or would my option be using some sort of security create rule that checks for other document time overlaps prior to allowing a new write (if this is at all possible)? One approach to guarantee document uniqueness via security rules seems to be exposing field values in the document ID, but I'm not entirely sure how exposing the start and end timestamp values in the ID would allow a rule to check for overlapping time ranges.
I think transaction is proper approach. According to the documentation:
..., if a transaction reads documents and another client
modifies any of those documents, Cloud Firestore retries the
transaction. This feature ensures that the transaction runs on
up-to-date and consistent data.
This seems to be an answer to your problem. All reads will be retried, if anything will change in the meantime. I think transaction mechanism is exactly for that reason.

Update all documents in a Firestore collection except for a pre-defined number which is based off date created

I am working on a project that when a user cancels their plan, all their documents should be updated to deactivated except for a pre-defined number of documents that are allowed to stay active. The pre-defined number amount determines the projects allowed to stay active along with the date they were created.
For example, if customer A has 1,000 documents and cancels their plan, all their documents except for the first 100 created should be updated to be deactivated.
My first attempt was to get all document ids with .listDocuments() but I noticed the created date is not part of Firestore's DocumentReference. Therefore I can't exclude the pre-defined number of documents allowed to stay active.
I could use .get() and use the created value, but I'm afraid that getting all the documents at once (which could be a million) would cause my cloud function to run out of memory, even if I have it set to the maximum allowed configuration.
Another option that I thought of, I could use .listDocuments() and write each document id to a temp collection in Firestore, which could kick off a cloud function for each document. This function would only have to work with one document, so it wouldn't have to worry about running out of resources. I am unsure how to determine if the document I'm working on should be marked as deactivated or is allowed to stay active.
I am not that worried about the reads to write as this workflow should not happen very often. Any help would be appreciated.
Thank you
One possible approach would be to mark the documents to be excluded.
I don't know what is your exact algorithm, but if you want to mark the first 100 documents that were created in a collection you can use a Cloud Function that runs for each new document and checks if there are already 100 docs in the collection.
If not, you update a field in this new document with its rank (using a Transaction to get the highest existing rank and increment it). If there are already 100 documents previously created in the collection, you just update the field to 0, for example, in such a way that later on you can query with where("rank", "==", 0).
Then, when you want to delete all the docs but the 100 first ones, just use where("rank", "==", 0) query.
So, concretely:
The first doc is created: you set the rank field to 1.
The Nth doc (N != 1) is created: you fetch all the docs with a query ordered by rank and limited to 1 doc (collecRef.orderBy("rank", "desc").limit(1)) in a Transaction. Since you are in a Cloud Function, you can use a Query in the Transaction (which you cannot with the Client SDKs). Then, still in the Transaction:
If the value of rank for the single doc returned by the Query is < 100 you set the rank value of the newly created do to [single doc value + 1]
If the value of rank for the single doc returned by the Query = 100 you set the rank value to 0
If I didn't make any mistake (I didn't test it! :-)) you end with 100 docs with a value of rank between 1 and 100 (the 100 first created docs) and the rest of the docs with a value of rank equal to 0.
Then, as said above you can use the where("rank", "==", 0) query to select all the docs to be deleted.

Firebase - Generate a user see-able unique account/document number

I want to assign a unique but incremental document/record(a data entry, not Firebase document) number when user generates his/her document in the app.
The document number should be unique integer/long and will be visible on the generated PDF document of the user as Your document number : 1100xxxxxx. This last generate document's number will be stored separately so when a new user generated his/her document, this number can be easily picked, incremented and assigned to user.
This way I won't have to query the database again for the last generated number using sorting as
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query query = rootRef.collection("Users")
.orderBy("DocumentNo", Query.Direction.DESCENDING)
.limit(1);
Right now, I generate a user by assigning the user.uid to the Firebase document. The reason I want to ensure the uniqueness of generated certificate number is that it will be visible to user and multiple users will be hitting the server at the same time(same millisecond even).
Although, I've seen almost every similar answer but the answer I've found similar to what I was thinking is this. Also, this unanswered question is what I should do but it has problems too.
So, is there a way by which I can generate a unique document/record number to the user? Answer need not be in Flutter, I want the logic mainly.
If you want to increment a number every time something happens do:
document.ref.update({unique_value: FieldValue.increment(1)});
That number will be unique. It will work with multiple users hitting the server at the same time.

Resources