如何在Cloud Firestore中高效地获取集合及其子集合的所有文档

时间:2019-12-25 11:19:01

标签: javascript firebase google-cloud-firestore

TL; DR 提取少量文档需要很多时间

  

场景:
  我为每个帐户都有一个集合,每个帐户都包含一个projects子集合和tasks子集合。任务子集合中的每个文档可以进一步包含checkLists子集合中的清单

注意:

  • 项目可以包含任务,而任务又可以包含清单。
  • 可以独立创建任务,即;它不一定总是项目的一部分。
  • 项目和任务都是顶级子集合,而checkLists子集合嵌套在每个任务中。

插图:

someTopLevelDB
   |
   |____ accountId1
   |         |______projects
   |         |         |_______ projectId1
   |         |
   |         |______tasks
   |                  |________taskId1 (belongs to projectId1)
   |                  |           |
   |                  |           |________checkLists
   |                  |                         |
   |                  |                         |_____checkListId1
   |                  |
   |                  |________taskId2 (standalone)

用例: 当用户单击重复的项目(从用户界面)时,我必须创建整个项目的副本,即所有任务,清单等。

代码: 这样做的过程很慢,当我分析代码时,此代码段花了很多时间来执行。该代码段提取了所有任务及其清单

let db = admin.firestore();

function getTasks(accountId) {
    return db.collection('someTopLevelDB')
        .doc(accountId)
        .collection('tasks')
        .where('deleted', '==', false)
        .get();
}


function getCheckLists(accountId, taskId) {
    return db.collection('someTopLevelDB')
        .doc(accountId)
        .collection('tasks')
        .doc(taskId)
        .collection('checkLists')
        .where('deleted', '==', false)
        .get();
}


async function getTasksAndCheckLists(accountId) {
    try {
        let records = { tasks: [], checkLists: [] };

        // prepare tasks details
        const tasks = await getTasks(accountId);
        const tasksQueryDocumentSnapshot = tasks.docs;
        for (let taskDocumentSnapshot of tasksQueryDocumentSnapshot) {
            const taskId = taskDocumentSnapshot.id;
            const taskData = taskDocumentSnapshot.data();
            const taskDetails = {
                id: taskId,
                ...taskData
            };
            records.tasks.push(taskDetails);

            // prepare check list details
            checkListQueryDocumentSnapshot = (await getCheckLists(accountId, taskId)).docs;
            for (let checkListDocumentSnapshot of checkListQueryDocumentSnapshot) {
                const checkListId = checkListDocumentSnapshot.id;
                const checkListData = checkListDocumentSnapshot.data();
                const checkListDetails = {
                    id: checkListId,
                    ...checkListData
                };
                records.checkLists.push(checkListDetails);
            }
        }
        console.log(`successfully fetched ${records.tasks.length} tasks and ${records.checkLists.length} checklists`);
        return records;
    } catch (error) {
        console.log('Error fetching docs ====>', error);
    }
}




// Call the function to fetch records
getTasksAndCheckLists('someAccountId')
    .then(result => {
        console.log(result);
        return true;
    })
    .catch(error => {
        console.error('Error fetching docs ===>', error);
        return false;
    });

执行状态:
在220.532秒内成功获取627个任务和51个清单

我得出的结论是,检索清单会拖慢整个过程的速度,因为任务的检索相当快。

所以我的问题如下:

  • 有没有什么方法可以优化上述文件的检索 码?
  • 有什么方法可以检索子文件 通过重塑数据并使用collectionGroup查询来更快地收集 等等?

谢谢。

1 个答案:

答案 0 :(得分:3)

此问题是由于在此处的for循环中使用await引起的:

checkListQueryDocumentSnapshot = (await getCheckLists(accountId, taskId)).docs;

这会导致您的for循环停滞不前,直到获取该特定任务的检查清单为止。

避免这种情况的方法是使用Promise链异步处理检查表。遍历任务时,将为该任务的检查清单创建请求,向其结果添加侦听器,然后将其发送并立即移至下一个任务。

在您的数据结构中,检查表与服务器上的特定任务相关,但在上面的代码中它们与它们无关。如果您仅使用带有push()的标准数组,则在使用相同的数据结构异步工作时,这将意味着它们将与您的任务不协调(例如,任务B的清单提取可能会在任务A之前完成)。为了解决这个问题,在下面的代码中,我已将清单嵌套在taskDetails对象下,以便它们仍保持链接。

async function getTasksAndCheckLists(accountId) {
    try {
        let taskDetailsArray = [];

        // fetch task details
        const tasks = await getTasks(accountId);

        // init Promise holder
        const getCheckListsPromises = [];

        tasks.forEach((taskDocumentSnapshot) => {
            const taskId = taskDocumentSnapshot.id;
            const taskData = taskDocumentSnapshot.data();
            const taskDetails = {
                id: taskId,
                checkLists: [], // for storing this task's checklists
                ...taskData
            };
            taskDetailsArray.push(taskDetails);

            // asynchronously get check lists for this task
            let getCheckListPromise = getCheckLists(accountId, taskId)
                .then((checkListQuerySnapshot) => {
                    checkListQuerySnapshot.forEach((checkListDocumentSnapshot) => {
                        const checkListId = checkListDocumentSnapshot.id;
                        const checkListData = checkListDocumentSnapshot.data();
                        const checkListDetails = {
                            id: checkListId,
                            ...checkListData
                        };

                        taskDetails.checkLists.push(checkListDetails);
                    });
                });

            // add this task to the promise holder
            getCheckListsPromises.push(getCheckListPromise);
        });

        // wait for all check list fetches - this is an all-or-nothing operation
        await Promise.all(getCheckListsPromises);

        // calculate the checklist count for all tasks
        let checkListsCount = taskDetailsArray.reduce((acc, v) => acc+v.checkLists.length, 0);

        console.log(`successfully fetched ${taskDetailsArray.length} tasks and ${checkListsCount} checklists`);
        return taskDetailsArray;
    } catch (error) {
        console.log('Error fetching docs ====>', error);
    }
}

通过这些更改,您应该看到函数运行的时间大大减少了。根据您提供的时间,我估计它将下降到大约2-3秒。