0

I am developping an app to order food online. As backend service I am using firestore to store the data and files. The user can order dishes and there are limited stocks. So every time a user order a dish and create a basket I update the stock of the corresponding ordered dishes. I am using a firebase function in order to perform this action. To be honest it is the first I am creating firebase function. Into the Basket object, there is a list of ordered Dishes with the corresponding database DishID. When the basket is created, I go through the DishID list and I update the Quantity in the firestore database. On my local emulator it works perfectly and very fast. But online it takes minutes to perform the first update. I can deal with some seconds. Even if it takes a few seconds (like for cold restart) it's okay. But sometimes it can take 3 minutes and someone else can order a dish during this time.

Here is my code:

//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {

      try{
        //Get the created basket
        const originalBasket = snap.data();

        originalBasket.OrderedDishes.forEach(async dish => {
          const doc = await db.collection('Dishes').doc(dish.DishID);
          console.log('Doc created');

          return docRef = doc.get()
          .then((result) =>{
            console.log('DocRef created');
            if(result.exists){
              console.log('Result exists');
              const dishAvailableOnDataBase = result.data().Available;
              console.log('Data created');
              const newQuantity = { Available: Math.max(dishAvailableOnDataBase - dish.Quantity, 0)};
              console.log('Online doc updated');
              return result.ref.set(newQuantity, { merge: true });
            }else{
              console.log("doc doesnt exist");
            }
            
          })
          .catch(error =>{
            console.log(error);
            return null;
          });       
        });

      }catch(error){
        console.log(error);
      }

});

I have a couple of logs output to debug the outputs on the server. It's the doc.get() function that takes 2 minutes to execute as you can see on the logger below: Firebase logger

Thanks for your help,

  • Does this answer your question? [Firebase cloud functions is very slow](https://stackoverflow.com/questions/42726870/firebase-cloud-functions-is-very-slow) – Konrad Nov 13 '22 at 21:30
  • Hello, I checked some other issue regading the cold restart. I see my function takes a bit more time to execute after a cold restart (1500 ms instead of 100ms). In my case a few seconds don't matter. But to perform the "update" document it takes minutes and I d'ont understand why and how to solve it. – Florent Pausé Nov 13 '22 at 23:50
  • I updated my post and my code with some debug console.log and I added a logger too. I realize that is the get() which is slow. If I try to get 2 times in a row the same document, then the second time is faster (1 sec instead of 2 minutes). But if I try to read 2 differents documents in a row, then it is slow the 2 times. I don't understanf because I can read easily and quickly a document from API (with postman for instance) but within the function inside the server it is slow. – Florent Pausé Nov 16 '22 at 13:55

2 Answers2

1

Thansk for your help. I just edited a little bit your code to make it work. I post my edited code. Thanks a lot, now it takes just 4 seconds to update the quantities. Kid regards

//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {

  try {
      //Get the created basket
      const originalBasket = snap.data();

      const promises = [];
      const quantities = [];
      originalBasket.OrderedDishes.forEach(dish => {
          promises.push(db.collection('Dishes').doc(dish.DishID).get());
          quantities.push(dish.Quantity);
      });
      const docSnapshotsArray = await Promise.all(promises);
      console.log("Promises", promises);

      const promises1 = [];
      var i = 0;
      docSnapshotsArray.forEach(result => {
          if (result.exists) {
              const dishAvailableOnDataBase = result.data().Available;
              const newQuantity = { Available: Math.max(dishAvailableOnDataBase - quantities[i], 0) };
              promises1.push(result.ref.set(newQuantity, { merge: true }));
          }
          i++;
      })

      return Promise.all(promises1)

  } catch (error) {
      console.log(error);
      return null;
  }

});
0

You should not use async/await within a forEach() loop, see "JavaScript: async/await with forEach()" and "Using async/await with a forEach loop".

And since your code executes, in parallel, a variable number of calls to the asynchronous Firebase get() and set() methods, you should use Promise.all().

You should refactor your Cloud Function along the following lines:

//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {

    try {
        //Get the created basket
        const originalBasket = snap.data();

        const promises = [];
        originalBasket.OrderedDishes.forEach(dish => {
            promises.push(db.collection('Dishes').doc(dish.DishID).get());
        });
        const docSnapshotsArray = await Promise.all(promises);

        const promises1 = [];
        docSnapshotsArray.forEach(snap => {
            if (result.exists) {
                const dishAvailableOnDataBase = result.data().Available;
                const newQuantity = { Available: Math.max(dishAvailableOnDataBase - dish.Quantity, 0) };
                promises1.push(result.ref.set(newQuantity, { merge: true }));
            }
        })

        return Promise.all(promises1)

    } catch (error) {
        console.log(error);
        return null;
    }

});

Note that instead of looping and calling push() you could use the map() method for a much concise code. However, for SO answers, I like the clarity brought by creating an empty array, populating it with a forEach() loop and passing it to Promise.all()...

Also note that since you are updating quantities in a basket you may need to use a Transaction.

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121