0
votes

I am recently experiencing regular errors for my firebase cloud functions. Expecially it is a function which is triggered when a user makes a post, and copies the post into the feed collections of all followers of the user.

My Code looks like this:

exports.onCreatePost = functions.firestore
    .document("/Posts/{userId}/UserPosts/{postId}")
    .onCreate(async (snapshot, context) => {
    const postCreated = snapshot.data();
    const userId = context.params.userId;
    const postId = context.params.postId;

    // 1) Get all the followers of the user who made the post
    const followerRef = admin
        .firestore()
        .collection("Followers")
        .doc(userId)
        .collection("FollowerIds");

    return followerRef.get().then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
                const followerId = doc.id;

                admin
                    .firestore()
                    .collection("Feed")
                    .doc(followerId)
                    .collection("FeedPosts")
                    .doc(postId)
                    .set(postCreated);
                });
                return null;
    });

});

The functions has worked well until recently, but now some people have 100+ followers and the functions fails regularily with 2 errors:

Error: Process exited with code 16

Error: 4 DEADLINE_EXCEEDED: Deadline exceeded

I know the first Error means something like Headers already sent but I can not figure out what the problem with my function is. I also know that there are some write limits for firebase but the function is basically just adding one doc into each followers Feed-collections so in my understanding the write limit should not be affected.

To summarize my database structure:

  1. Feed (collection) userId (document) FeedPosts (subcollection) postId (document)

  2. Followers (collection) userId (document) FollowerIds (subcollection) followerId (document)

  3. Posts (collection) userId (document) UserPosts (subcollection) postId (document)

2

2 Answers

2
votes

You incorrectly manage the Cloud Function life cycle. Returning a Promise in background Cloud Functions calling asynchronous methods tells the Cloud Functions platform to wait until the promise is fulfilled or rejected before cleaning up the function. See https://firebase.google.com/docs/functions/terminate-functions for more detail.

Since, in the forEach() loop, you are executing a variable number of calls to the asynchronous set() method, you need to use Promise.all() in order to get a unique Promise (which resolves when all of the promises returned by the calls to the set() method have resolved) that you can then return, as follows:

exports.onCreatePost = functions.firestore.document("/Posts/{userId}/UserPosts/{postId}").onCreate(async (snapshot, context) => {
    const postCreated = snapshot.data();
    const userId = context.params.userId;
    const postId = context.params.postId;

    const followersQuerySnapshot = await admin.firestore().collection("Followers").doc(userId).collection("FollowerIds").get();
    const promises = followersQuerySnapshot.docs.map(  // followersQuerySnapshot.docs is an Array of QueryDocumentSnapshots
      doc => admin
       .firestore()
       .collection("Feed")
       .doc(doc.id)
       .collection("FeedPosts")
       .doc(postId)
       .set(postCreated)
      );

    return Promise.all(promises);  // <= Important, here return the promise returned by Promise.all()
    });

});
1
votes
exports.onCreatePost = functions.firestore.document("/Posts/{userId}/UserPosts/{postId}").onCreate(async (snapshot, context) => {
    const postCreated = snapshot.data();
    const userId = context.params.userId;
    const postId = context.params.postId;
    // 1) Get all the followers of the user who made the post
    const followerRef = await admin.firestore().collection("Followers").doc(userId).collection("FollowerIds").get();
    const promises = [];
    followerRef.forEach((doc) => {
         const followerId = doc.id;
         promises.push(admin.firestore().collection("Feed").doc(followerId).collection("FeedPosts").doc(postId).set(postCreated)}));
    });
    await Promise.all(promises);
    });

});

cloud function retracts resources once the request is served. So you have to use await to retrieve data first. please read more about async/await