Reputation: 62686
I have school object with classrooms and students. Students are each family's sub-collection. When creating a school, I write several hundred families in several write batches.
In creating each student, I want to do a few things:
That code looks like this...
exports.didCreateStudent = functions.firestore.document(studentPath).onCreate((snap, context) => {
return Promise.all([
updateFamilyNames(snap.ref.parent.parent),
updateClassroomStudent(snap, null, snap.data().classroom),
updateStudentCollection(null, snap)
])
})
Each of those functions performs a transaction. The first gets the student's family and writes kids names, the second does something like the same on the student's classroom, and so on. (I do these on triggers to get the behavior on updates and deletes, also).
For example, the salient part of the updateClassroomStudent
looks like this...
return transaction.get(classroomDocRef).then(snapshot => {
const student = Object.assign({}, _.pick(studentSnapshot.data(), 'firstName', 'lastName', 'grade'), { ref: studentSnapshot.ref })
const addition = { students: { [studentSnapshot.id]: student } }
return transaction.set(snapshot.ref, addition, { merge: true })
})
This works fine for small schools (10s of families, with about 2 students apiece), but on larger schools (100s of families), I begin to see Error: 10 ABORTED: Too much contention on these documents. Please try again.
I can avoid the errors by skipping both updateClassroomStudent
and updateStudentCollection
, but not if I allow either one (or both) to run.
Will it help to not use transactions? When a few hundred docs are created with a write batch (like the student's families), will the onCreate
triggers run sequentially? If so, maybe I can skip the transaction and save some overhead, maybe just pausing between batches.
Upvotes: 0
Views: 705
Reputation: 317372
Cloud Functions triggers do not have a guaranteed execution order, nor are they necessarily executed in serial. If your code kicks off hundreds of events that trigger a function, that function could very well run hundreds in parallel. That is creation a lot of contention for the document at snapshot.ref
.
I suggest also reading this, which covers the same error, with a different use case: Error: 10 ABORTED: Too much contention on these documents. Please try again
By "contention", I mean that there are lots of concurrent access to a document via a transaction. The transaction handler is being retried too many time due to all of the writes. This behavior is stated in the documentation:
In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
Later, it says the transaction can fail when:
The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.
Without seeing all your code, it's hard to recommend a course of action, but I think the key will be to try to change the way your scheme works by applying all of the updates to the document in a single transaction rather than allowing lots of transactions to collide over many updates.
Another much more scalable option is to store the list of students as individual documents in a subcollection rather than as a list field in a single document.
Upvotes: 1