web-dev-qa-db-ja.com

ファイヤーストアクラウド関数onCreate / onDeleteは、すぐに2回トリガーされることがあります

OnCreateトリガーとonDeleteトリガーの両方でこの動作を時々観察しました。 enter image description here

両方の実行は、firestoreで作成された同じドキュメントに対して行われました。ドキュメントは1つしかないため、ハンドラーを2回トリガーする方法がわかりません。ハンドラー自体は非常に単純です。

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

これは常に発生するわけではありません。何が足りないのですか?

9
xaksis

Cloud Firestoreトリガーを参照してください 制限と保証

関数呼び出しの配信は現在保証されていません。 CloudFirestoreとCloudFunctionsの統合が改善されるにつれて、「少なくとも1回」の配信を保証する予定です。ただし、ベータ版ではこれが常に当てはまるとは限りません。 これにより、1つのイベントに対して複数の呼び出しが発生する可能性もあります。したがって、最高品質の関数の場合は、関数がべき等になるように記述されていることを確認してください。

Firecastビデオ べき等を実装するためのヒントがあります。

また、2つのGoogleブログ投稿: 最初の2番目の

9
Bob Snyder

私の場合、eventIdtransactionを使用して、onCreateが2回トリガーされるのを防ぎます。

(eventIdをリストに保存し、関数が実際に頻繁にトリガーされるかどうかを確認する必要がある場合があります)

const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {

  const prize = 1000
  const eventId = context.eventId
  if (!eventId) {
    return false
  }

  // increment money
  const p1 = () => {
    const ref = db.doc('...')
    return db.runTransaction(t => {
        return t.get(ref).then(doc => {
          let money_total = 0
          if (doc.exists) {
            const eventIdLast = doc.data().event_id_last
            if (eventIdLast === eventId) {
              throw 'duplicated event'
            }
            const m0 = doc.data().money_total
            if(m0 !== undefined) {
              money_total = m0 + prize
            }
          } else {
            money_total = prize
          }
          return t.set(ref, { 
            money_total: money_total,
            event_id_last: eventId
          }, {merge: true})
        })
    })
  }

  // will execute p2 p3 p4 if p1 success
  const p2 = () => {
    ...
  }

  const p3 = () => {
    ...
  }

  const p4 = () => {
    ...
  }

  return p1().then(() => {
    return Promise.all([p2(), p3(), p4()])
  }).catch((error) => {
    console.log(error)
  })
})
1
saranpol

@saranpolの回答に基づいて、今のところ以下を使用します。ただし、実際に重複するイベントIDを取得しているかどうかはまだ確認していません。

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
}
1
Simon Bengtsson