1
votes

Description:

I believe the work my Cloud Firestore Triggers are supposed to perform is being overwritten by each other. I am attempting to use a Cloud Firestore Trigger to handle adding data to firestore after specific webhooks from a 3rd party API are processed. The webhook is first handled by creating a new document in an events collection i.e. events/{event}. The incoming webhooks are processed correctly and an event document is created for each webhook received.

I then use an onCreate trigger to add data to a single document within a collection of documents called "courses" and each document has a subcollection of "chapters". Each event document (which triggers the onCreate) may point to either the course document or a chapter document. My function within the trigger (setMetadata) checks which of these two documents the event document is referencing and updates the required fields. Each of my tests consists of 1 course and 2 chapter (which are nested in the course document) documents to be updated (3 webhooks received and in turn 3 onCreate triggers) almost at the same exact time.

Problem:

The work, after the Cloud Firestore Triggers are completed, produces inconsistent results. Some attempts add the metadata to the course document but not the chapter documents, other times to the chapter documents but not the course. And finally very few times all documents are updated correctly. No errors are produced. I have attempted to use onWrite instead as well as update instead of set. How do I ensure all documents are updated accordingly? Is this a race condition with the triggers overwriting the previous work from another trigger?

Goal

The goal is to have every initiated trigger to correctly update the corresponding document's fields. Please let me know if I need to provide any further info or context.

Code:

onCreate Trigger:

exports.trigger = functions.firestore
.document('events/{event}')
.onCreate((snap, context) => {
    const data = snap.data();
        return setMetadata(data);
})

setMetadata function:

 const getUploadId = async(data) => {
    return db.collection('events')
    .where('data.asset_id', '==', data.object.id)
    .get()
    .then((snapshot) => {
        let uploadId = '';
        snapshot.forEach((doc) => {
            uploadId = doc.data().object.id;
        })
        return uploadId;
    })
}

const chapterCheck = async(data, uploadId) => {

    return db.collectionGroup('chapters')
    .where('video', '==', uploadId)
    .get()
    .then((snapshot) => {
        if (snapshot.empty) {
            return null;
        }
        snapshot.forEach((doc) => {
            return doc.ref.set({
                //set metadata for chapter here
            }, {merge: true});
        })
    })
}
const courseCheck = async(data, uploadId) => {

    return db.collection('courses')
    .where('preview', '==', uploadId)
    .get()
    .then((snapshot) => {
        if (snapshot.empty) {
            return null;
        }
        snapshot.forEach((doc) => {
            return doc.ref.set({
                //sets metadata for course
            }, {merge: true});
        })
    })
}

exports.setMetadata = async (data) => {

    try {
        const uploadResponse = await getUploadId(data).then((value) => {
            return value;
        });
        const courseResponse = await courseCheck(data, uploadResponse);
        const chapterResponse = await chapterCheck(data, uploadResponse);

        return courseResponse, chapterResponse;

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

Example of db schema:

*events*
  -event01
  -event02
  -event03

*courses*
  -course01
    --*chapters*
    ------chapter01
    ------chapter02
1

1 Answers

2
votes

Is this a race condition with the triggers overwriting the previous work from another trigger?

Yes, there is always the chance of a race condition. Cloud Functions does not guarantee an order of execution, and they could be executed in parallel. If you think that two invocations of a function have the possibility of interfering with each other, you should use a transaction to manage the results of that atomically.