首页 > 解决方案 > 在 Cloud Function 的一个功能中执行数千次读取和删除 Firestore 操作是否可以?

问题描述

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

在此处输入图像描述

然后还有users作为父集合的attendedEvents子集合来记录用户将访问的所有事件,如下图所示。AttendEvents 子集合事件数据。

在此处输入图像描述

我使用非规范化,所以似乎事件数据在attendedEvents这样的子集合中重复

在此处输入图像描述

然后我使用云功能做了一个 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)
    }    
})

标签: firebasegoogle-cloud-firestoregoogle-cloud-functions

解决方案


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

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

但是,还有540 秒的最大功能持续时间。我相信你所描述的应该不会花那么长时间。如果确实如此,那么如果您要提交批量删除,即使您的功能由于超过持续时间而失败,也会完成删除。

2)在 Firestore 方面,您也有一些限制。在这里,您可以了解处理读/写操作高读、写和删除率时的一些最佳实践。如果您尝试以高速率删除按字典顺序关闭的文档,则根据数据的结构和类型,您可能会遇到一些问题,例如连接错误。

另请记住,Firestore对每个付款计划的读/写操作数量有更通用的配额。

无论如何,即使有最好的计算,也总是有出错的余地。所以我的建议是尝试一个你所期望的最高峰的测试场景。如果您达到任何配额,您可以请求增加配额,或者如果您达到任何硬​​性限制,您可以联系 Google Cloud Platform 支持,提供有关您的项目和用例的具体详细信息。


推荐阅读