Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

firestore cloud functions onCreate/onDelete sometimes immediately triggered twice

I have observed this behavior occasionally with both onCreate and onDelete triggers. enter image description here

Both the executions happened for the same document created in firestore. There's only one document there so I don't understand how it could trigger the handler twice. the handler itself is very simple:

module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
  const db = admin.firestore();
  const params = event.params;
  const data = event.data.data();
  // empty
});

this doesn't happen all the time. What am I missing?

like image 894
xaksis Avatar asked Feb 11 '18 20:02

xaksis


2 Answers

See the Cloud Firestore Triggers Limitations and Guarantees:

Delivery of function invocations is not currently guaranteed. As the Cloud Firestore and Cloud Functions integration improves, we plan to guarantee "at least once" delivery. However, this may not always be the case during beta. This may also result in multiple invocations for a single event, so for the highest quality functions ensure that the functions are written to be idempotent.

There is a Firecast video with tips for implementing idempotence.

Also two Google Blog posts: the first, the second.

like image 121
Bob Snyder Avatar answered Nov 04 '22 02:11

Bob Snyder


Based on @saranpol's answer we use the below for now. We have yet to check if we actually get any duplicate event ids though.

const alreadyTriggered = eventId => {
  // Firestore doesn't support forward slash in ids and the eventId often has it
  const validEventId = eventId.replace('/', '')

  const firestore = firebase.firestore()
  return firestore.runTransaction(async transaction => {
    const ref = firestore.doc(`eventIds/${validEventId}`)
    const doc = await transaction.get(ref)
    if (doc.exists) {
      console.error(`Already triggered function for event: ${validEventId}`)
      return true
    } else {
      transaction.set(ref, {})
      return false
    }
  })
}

// Usage
if (await alreadyTriggered(context.eventId)) {
  return
}
like image 24
Simon Bengtsson Avatar answered Nov 04 '22 03:11

Simon Bengtsson