'Firebase cloud function: error in running transactions

I am building an app where users can create lists and posts of their own, subscribe to/recommend other users' lists/posts, and make comments.

Here is how my database is structured:

  1. User creates a post/list => A document is created under 'lists' and 'posts' collection, respectively.

  2. User recommends a post => A document is created under 'post_reco' collection & the user's UID is stored in the array of users who recommended the post.

  3. User recommends a list => A document is created under 'list_reco' collection & the user's UID is stored in the array of users who recommended the list.

  4. User subscribes to a list => A document is created under 'list_subs' collection, and the user's UID is stored in the array of users who are subscribing.

  5. User creates a comment => A document is created under 'comments' collection.

I am trying to build a functionality to block users so that the interactions from the blocked user get deleted. i.e. all the corresponding data from 'post_reco','list_reco','list_subs', and 'comments' should be deleted. The record of blocking should be kept in the 'blocked' collection, and the creation of a new document triggers the function to delete the corresponding data.

When I run the below code, it runs into the following problems:

  1. I get the error message: 'Error: 4 DEADLINE_EXCEEDED: Deadline exceeded'
  2. All the documents in the 'comments' collection are deleted successfully, but none from 'post_reco' and 'list_reco' are deleted.

I guess this has to do sth with the write limits, but the issue happens when there are only dozens of documents to delete, so I am a little bit confused.

Can someone take a look at the following function and advise me on where I went wrong?

exports.blockUsers = functions
.firestore.document("blocks/{uid}")
.onWrite((change, context) => {
    /*
    Logic to extract uid (the blocker) and targetuid (the blocked). Omitted for brevity
    */

    //Get the lists and posts the blocker created and comments that the blocked created on the blocker's posts
    const q1 = db.collection("lists").where("uid", "==", uid);
    const q2 = db.collection("posts").where("uid", "==", uid);
    const q3 = db
    .collection("comments")
    .where("commentuid", "==", targetuid)
    .where("postuid", "==", uid);

    return Promise.all([q1.get(), q2.get(), q3.get()]).then((results) => {
    refs = [];
    results.forEach((querySnapshot) => {
        querySnapshot.forEach((documentSnapshot) => {
        refs.push(documentSnapshot.ref);
        });
    });

    if (refs.length === 0) {
        console.log("no changes needed");
        return null;
    }

    /*
    For post_reco, list_reco, and list_subs, the corresponding documents can either 
    exist or not based on whether there has been an interaction. So I wrote the code 
    to get the entire chunk of lists and posts the blocker created and then go one-by-one 
    to check whether the corresponding document exists, and remove the blocked if there is one. 
    I guess this is causing the issue, but don't know what exactly the problem is.
    */
    return db.runTransaction((t) => {
        return t.getAll(...refs).then((docs) => {
        docs.forEach(doc => {
        const area = doc.ref.path.split('/')[0];
        if (area==='lists') {
            const promises = [];
            const listid=doc.ref.path.split('/')[1];
            db.collection('list_reco').doc(listid).get()
            .then(doc2=>{
                if (doc2.exists) {                                        
                    promises.push (
                        doc2.ref.update({
                        reco_users_uid: admin.firestore.FieldValue.arrayRemove(targetuid)
                        })
                    )
                }  else {
                    return null;
                }
            })

            db.collection('list_subs').doc(listid).get()
            .then(doc3=>{
                if (doc3.exists) {
                    promises.push(
                        doc3.ref.update({
                            subs_by_users_uid: admin.firestore.FieldValue.arrayRemove(targetuid)
                        })
                    ) 
                } else {
                    return null;
                }
            })
            
            return Promise.all(promises);                        
        } else if (area==='posts') {
            const promises = [];
            db.collection('post_reco').doc(doc.ref.path.split('/')[1]).get()
            .then(doc2=>{
                if (doc2.exists) {
                    promises.push (
                        doc2.ref.update({
                        post_reco_uid: admin.firestore.FieldValue.arrayRemove(targetuid)
                        })
                    )
                } else {
                    return null;
                }
            })
            return Promise.all(promises);
        } else {
            t = t.delete(doc.ref)
        }
        });
        });
    });
    });
});

Thanks!



Solution 1:[1]

There are three different scenarios when the issue might occur:

  1. The requests that you are making are taking too long. To avoid this you can either modify your logic to write a smaller batch of data each time your function is triggered or you can try increasing the timeout value.

To increase the timeout value checkout this document

  1. The error can be caused by writing too much data to Firestore at once. Try to reduce your writing to no more than 500 elements or even less.

You can find more about limits in this document

  1. Cloud Functions needs to know when it is convenient to terminate the function but if you do not return anything, it may cause this error. You can solve it by returning the total promise as explained here.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zeenath S N