I am attempting to update the average rating of a restaurant
document in my Firestore database with a transaction when a new review
is posted in the sub-collection reviews
. The following code works for when reviews come in slowly but if I run some stress testing by inserting 10 reviews within a few seconds it will no longer update the average rating properly (i.e. it may only count the first 5 reviews and not update based on the rest). My understanding of transactions was that it will re-run automatically when the data has changed beneath its feet to ensure it is properly updating the values.
Here is the cloud function used.
exports.reviewCreated = functions.firestore.document('/restaurants/{restaurantid}/reviews/{reviewid}').onCreate((snapshot, context) => {
const rest_id = context.params.restaurantid;
const rest_ref = admin.firestore().collection('restaurants').doc(rest_id)
const rest_rating = snapshot.data().rest_rating;
console.log('The new rest rating is: ' + rest_rating);
try {
admin.firestore().runTransaction((t) => {
return t.get(rest_ref).then((rest_doc) => {
const new_total_reviews = rest_doc.data().totalReviews + 1;
const current_avg_rating = rest_doc.data().averageRating
const new_avg_rating = current_avg_rating + ((rest_rating - current_avg_rating)/new_total_reviews);
t.update(rest_ref, {totalReviews: new_total_reviews,
averageRating: new_avg_rating});
});
})
} catch (e) {
console.log('[Review Created] Transaction Failed', e);
return null;
}
});
To test this. I have a button on a debug screen of my app to insert a fake review, the code is as follows
<Button title = 'Add Review! (Test)'
onPress = {() =>
{
console.log(this.state.uid);
var new_uuid = uuid.v4();
firestore()
.collection('restaurants')
.doc(this.state.restid)
.collection('reviews')
.doc(new_uuid)
.set({
rest_rating: Math.floor(Math.random() * 5) + 1,
review_name: 'test'
});
}}
/>
I am fairly new to TS and am trying my best to learn the ropes so any readings/videos to watch would also be helpful! :)
You are not correctly managing the life cycle of your Cloud Function. As explained in the doc, you need to "resolve functions that perform asynchronous processing (also known as "background functions") by returning a JavaScript promise".
In your case you should return the Promise returned by the runTransaction()
method, as follows:
exports.reviewCreated = functions.firestore.document('/restaurants/{restaurantid}/reviews/{reviewid}').onCreate((snapshot, context) => {
const rest_id = context.params.restaurantid;
const rest_ref = admin.firestore().collection('restaurants').doc(rest_id)
const rest_rating = snapshot.data().rest_rating;
console.log('The new rest rating is: ' + rest_rating);
return admin.firestore().runTransaction((t) => { // !!! See the return here
return t.get(rest_ref).then((rest_doc) => {
const new_total_reviews = rest_doc.data().totalReviews + 1;
const current_avg_rating = rest_doc.data().averageRating
const new_avg_rating = current_avg_rating + ((rest_rating - current_avg_rating) / new_total_reviews);
t.update(rest_ref, {
totalReviews: new_total_reviews,
averageRating: new_avg_rating
});
});
})
.catch(e => {
console.log('[Review Created] Transaction Failed', e);
return null;
});
});
By uncorrectly managing the life cycle of your Cloud Function you are potentially generating some "erratic" behavior of the Cloud Function ("transactions not always finish" as you mention).
Returning a Promise (or a value) in a background triggered Cloud Function indicates to the Cloud Function platform that it should not terminate the Function until the Promise has fulfilled and avoids it is terminated before the asynchronous operations are done.
So the reason why "transactions not always finish" is most probably the following: it happens sometimes that your Cloud Function is terminated before the asynchronous Transaction is completed, for the reason explained above. And the other times, the Cloud Function platform does not terminate the Function immediately and the asynchronous Transaction can be completed (i.e. has the possibility to complete before the Cloud Function is terminated).