在 Cloud Function 的一个功能中执行数千次读取和删除 Firestore 操作是不是可以?
Posted
技术标签:
【中文标题】在 Cloud Function 的一个功能中执行数千次读取和删除 Firestore 操作是不是可以?【英文标题】:Is it OK to perform thousand of read and delete Firestore operations in one function of Cloud Function?在 Cloud Function 的一个功能中执行数千次读取和删除 Firestore 操作是否可以? 【发布时间】:2019-12-12 07:34:11 【问题描述】:我有一个events
作为父集合,它有Attendee
子集合来记录所有将参加活动的用户,如下图所示。 Attendee
子集合包含用户数据
然后还有users
作为父集合,它有attendedEvents
子集合来记录用户将访问的所有事件,如下图所示。 AttendedEvents 子集合事件数据。
我使用非规范化,因此事件数据似乎在 attendedEvents
子集合中重复,就像这样
然后我使用云功能完成一项 cron 任务。此 cron 作业任务是评估事件是否已通过(过期)。如果事件已经通过,那么这个函数应该:
-
将事件数据的字段从 isActive == true 更新为 isActive == false
在所有过期事件中读取其所有
Attendee
文档,获取所有参加者ID,然后删除用户集合的attendedEvents
子集合中的所有事件数据。
如您所见,我的 cron 作业功能的第二个任务可能需要读取大约 50.000 - 100.000 个文档,然后还需要删除大约 50.000 - 100.000 个文档作为最坏的情况(峰值)。
所以我的问题是,像这样在 Cloud Function 的一个函数中执行数千次读取和删除操作是否可以?
我担心存在我不知道的限制。我不确定,有没有我没有考虑过的事情?有没有更好的方法呢?
这是我的云功能代码:
exports.cronDeactivatingExpiredEvents = functions.https.onRequest(async (request,response) =>
const now = new Date()
const oneMonthAgo = moment().subtract(1,"month").toDate()
try
const expiredEventsSnapshot = await eventRef
.where("isActive","==",true)
.where("hasBeenApproved","==",true)
.where("dateTimeStart",">",oneMonthAgo)
.where("dateTimeStart","<",now)
.get()
const eventDocumentsFromFirestore = expiredEventsSnapshot.docs
const updateEventPromises = []
eventDocumentsFromFirestore.forEach(eventSnapshot =>
const event = eventSnapshot.data()
const p = admin.firestore()
.doc(`events/$event.eventID`)
.update(isActive: false)
updateEventPromises.push(p)
)
// 1. update isActive to be false in firestore document
await Promise.all(updateEventPromises)
console.log(`Successfully deactivating $expiredEventsSnapshot.size expired events in Firestore`)
// getting all attendeeIDs.
// this may need to read around 50.000 documents
const eventAttendeeSnapshot = await db.collection("events").doc(eventID).collection("Attendee").get()
const attendeeDocuments = eventAttendeeSnapshot.docs
const attendeeIDs = []
attendeeDocuments.forEach( attendeeSnapshot =>
const attendee = attendeeSnapshot.data()
attendeeIDs.push(attendee.uid)
)
// 3. then delete expired event in users subcollection.
// this may need to delete 50.000 documents
const deletePromises = []
attendeeIDs.forEach( attendeeID =>
const p = db.collection("users").doc(attendeeID).collection("attendedEvents").doc(eventID).delete()
deletePromises.push(p)
)
await Promise.all(deletePromises)
console.log(`successfully delete all events data in user subcollection`)
response.status(200).send(`Successfully deactivating $expiredEventsSnapshot.size expired events and delete events data in attendee subcollection`)
catch (error)
response.status(500).send(error)
)
【问题讨论】:
这里是您的云函数配额值...cloud.google.com/functions/quotas ...从这些您将看到函数的持续时间和函数的最大内存有上限。测试时,请尝试确定执行的持续时间和内存需求。 @Kolban 非常感谢您提供的信息。问题是,我不知道如何估计最坏情况下删除和读取 100.000 个文档的执行时间,我仍处于开发阶段。所以我不确定我是否已经达到了 540 秒的执行时间/events/someEventId/Attendee
的数据结构是什么?文档中存储的uid
是不是和文档id一样?
参加者的文档实际上是一个用户数据,其中包含userUID作为文档的字段。所以 eventID 和 userUID 不一样
【参考方案1】:
这里有几点需要注意。
1) Cloud Function 方面存在一些限制。根据您使用正在读取的数据的方式,您可能会达到的配额是Outbound Socket Data,即 10GB/100 秒,不包括 HTTP 响应数据。如果您达到此配额,您可以通过转到IAM & admin >> Quotas >> Edit Quotas
并选择Cloud Function API (Outgoing socket traffic for the Region you want)
来请求增加配额。
不过,也有 540 秒的Maximum function duration。我相信你所描述的应该不会花那么长时间。如果确实如此,那么如果您要提交批量删除,即使您的函数由于超过持续时间而失败,也会执行删除。
2) 在 Firestore 方面,您也有一些限制。在这里,您可以了解处理Read/Write operations 和High read, write, and delete rates 时的一些最佳实践。如果您尝试快速删除按字典顺序关闭的文档,可能会遇到一些问题,例如连接错误,具体取决于数据的结构和类型。
还要记住更通用的Firestore quotas 对每个付款计划的读/写操作次数。
无论如何,即使有最好的计算,也总会有出错的余地。所以我的建议是尝试一个你所期望的最高峰的测试场景。如果您达到任何配额,您可以请求增加配额,或者如果您达到任何硬性限制,您可以联系 Google Cloud Platform 支持,提供有关您的项目和用例的具体详细信息。
【讨论】:
以上是关于在 Cloud Function 的一个功能中执行数千次读取和删除 Firestore 操作是不是可以?的主要内容,如果未能解决你的问题,请参考以下文章
无法在安全的Google Cloud Function上获得CORS错误
从 Cloud Function (python) 写入 Google Cloud Storage
如何在 Angular 组件中正确使用 Cloud Functions?