TL; DR Получение большого количества документов занимает много времени

Scenerio:
У меня есть коллекция для каждой учетной записи, и каждая учетная запись содержит коллекцию projects и коллекцию tasks . Каждый документ во checkLists коллекции задач может дополнительно содержать контрольные списки во checkLists коллекции контрольных 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 ===={amp}gt;', error); } } // Call the function to fetch records getTasksAndCheckLists('someAccountId') .then(result ={amp}gt; { console.log(result); return true; }) .catch(error ={amp}gt; { console.error('Error fetching docs ==={amp}gt;', error); return false; }); 

Статистика выполнения:
за 220,532 секунды успешно получили 627 заданий и 51 контрольный список

Я пришел к выводу, что получение контрольных списков замедляло весь процесс, так как поиск задач был довольно быстрым.

Итак, мой вопрос заключается в следующем:

  • Есть ли способ оптимизировать получение документов в приведенном выше коде?
  • Есть ли способ быстрее извлечь документы из подколлекции, перемоделируя данные, используя запросы collectionGroup и т. Д.?

Благодарю.

Проблема вызвана использованием await внутри вашего цикла for:

 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) ={amp}gt; { 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) ={amp}gt; { checkListQuerySnapshot.forEach((checkListDocumentSnapshot) ={amp}gt; { 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) ={amp}gt; acc v.checkLists.length, 0); console.log(`successfully fetched ${taskDetailsArray.length} tasks and ${checkListsCount} checklists`); return taskDetailsArray; } catch (error) { console.log('Error fetching docs ===={amp}gt;', error); } } 

С этими изменениями вы должны увидеть, как значительно сокращается продолжительность работы вашей функции. Исходя из предоставленного вами времени, я бы предположил, что оно упадет примерно до 2-3 секунд.