是否可以在Cloud Function的一项功能中执行数千次读取和删除Firestore操作?

时间:2019-12-12 07:34:11

标签: firebase google-cloud-firestore google-cloud-functions

我有一个events作为父集合,该子集合具有Attendee子集合,用于记录将参加活动的所有用户,如下图所示。 Attendee子集合包含用户数据

enter image description here

,然后将users作为父集合,该子集合具有attendedEvents子集合,以记录用户将要访问的所有事件,如下图所示。 relatedEvents的子集合事件数据。

enter image description here

我使用非规范化,因此似乎事件数据在attendedEvents子集合中是重复的

enter image description here

然后我使用云功能进行了cron作业。此Cron工作任务是评估事件是否已通过(过期)。如果事件已通过,则此函数应:

  1. 将isActive == true的事件数据的字段更新为isActive == false
  2. 读取所有过期事件中的所有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)
    }    
})

2 个答案:

答案 0 :(得分:0)

您必须在这里注意几件事。

1)云功能方面有一些限制。根据您使用的读取数据的方式,您可能会达到的配额为Outbound Socket Data,该配额为10GB / 100秒(不包括HTTP响应数据)。如果您达到此配额,可以通过转到IAM & admin >> Quotas >> Edit Quotas并选择Cloud Function API (Outgoing socket traffic for the Region you want)来请求增加配额。

但是,还有Maximum function duration(540秒)。我相信您所描述的内容不会花那么长时间。如果确实如此,那么即使您要提交批处理删除,即使您的功能由于持续时间过长而导致功能失败,也会执行删除操作。

2)在Firestore方面,您也有一些限制。在这里,您可以了解有关处理Read/Write operationsHigh read, write, and delete rates的一些最佳做法。根据数据的结构和类型,如果您尝试以高速率删除按字典顺序关闭的文档,则可能会遇到诸如连接错误之类的问题。

还请记住,每个付款计划的读/写操作次数比较通用Firestore quotas

无论如何,即使进行了最佳的计算,也总是存在错误的余地。因此,我的建议是尝试一个您期望的最高峰值的测试方案。如果您达到任何配额,则可以请求增加配额,或者如果达到任何严格限制,则可以与Google Cloud Platform支持联系,以提供有关项目和用例的特定详细信息。

答案 1 :(得分:-1)

是的,没关系,但不是您尝试的方式。通常,您不得在循环中进行读取/写入/删除操作,而必须在“事务”或“批量写入”中进行。在此处查找有关这些内容:Transactions and batched writes

此外,每笔交易限制为500个文档,因此您可以在多个交易中中断整个操作。例如,如果要删除2000个文档,请将它们添加到4个单独的事务中并执行这些事务。