Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to update multiple documents from cloud function in firestore database?

I am new to firebase cloud functions and I want to update username field of some documents from posts collection when the users collection changes it username field of a particular document. I use the following code to do that:

exports.updateProfileUsername = functions.firestore
  .document('users/{userId}')
  .onUpdate((change, context) => 
  {
    const {userId} = context.params;

    var newUsername = change.after.data().username;
    var previousUsername = change.before.data().username;

    if (newUsername.localeCompare(previousUsername) !== 0)
    {
      let postCollectionRef = db.collection('posts');
      let postQuery = postCollectionRef.where('userId', '==', `${userId}`);

      return new Promise((resolve, reject) => 
      {
        updateUsernameDocuments(postQuery, reject, newUsername);
      });
    }
  });

function updateUsernameDocuments(query, reject, newValue) 
  {
    query.get()
      .then((snapshot) => 
      {
        if (snapshot.size === 0) 
        {
          return 0;
        }

        return snapshot.docs.forEach((doc) =>
        {
          doc.ref.update({username : `${newValue}`});
        });
      }).catch(reject);
  }

This code works fine. usernames in posts collection are changing correctly. But, after some time, the cloud functions log shows this log : Function execution took 60002 ms, finished with status: 'timeout'. How to solve that? And will this function be a problem if i have to update millions of docs in posts collection?

like image 697
eegooDeveloper Avatar asked Dec 03 '22 10:12

eegooDeveloper


1 Answers

The problem comes from the fact that you are not returning the Promise returned by the update() method, therefore the Cloud Function is not informed that the work is done and runs up to the timeout.

What may also happen, if you have to update "millions of docs in posts collection", is that the Cloud Function ends before your updates are all done. This is more annoying!

I would suggest you watch the 3 videos titled "Learn JavaScript Promises" from the Firebase video series which explain this key point of returning Promises for background triggered functions.

The following code should work. Note that I have used a batched write, which is especially dedicated to multiple write operations.

exports.updateProfileUsername = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
        const { userId } = context.params;

        var newUsername = change.after.data().username;
        var previousUsername = change.before.data().username;

        if (newUsername.localeCompare(previousUsername) !== 0) {
            const postCollectionRef = db.collection('posts');
            const postQuery = postCollectionRef.where('userId', '==', `${userId}`);

            return postQuery.get()
                .then(querySnapshot => {

                    if (querySnapshot.empty) {
                        return null;
                    } else {
                        let batch = db.batch();

                        querySnapshot.forEach(doc => {
                            batch.update(doc.ref, { username: `${newUsername}` });
                        });

                        return batch.commit();

                    }
                });
        } else {
            return null;
        }
    });

Note that a batched write can contain up to 500 operations. If you plan to update more than 500 documents, you may use Promise.all() instead, as follows:

exports.updateProfileUsername = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
        const { userId } = context.params;

        var newUsername = change.after.data().username;
        var previousUsername = change.before.data().username;

        if (newUsername.localeCompare(previousUsername) !== 0) {
            const postCollectionRef = db.collection('posts');
            const postQuery = postCollectionRef.where('userId', '==', `${userId}`);

            return postQuery.get()
                .then(querySnapshot => {

                    if (querySnapshot.empty) {
                        return null;
                    } else {
                        const promises = []

                        querySnapshot.forEach(doc => {
                            promises.push(doc.ref.update({ username: `${newUsername}` }));
                        });

                        return Promise.all(promises);
                    }
                });
        } else {
            return null;
        }
    });
like image 134
Renaud Tarnec Avatar answered Dec 23 '22 04:12

Renaud Tarnec