Как улучшить агрегатный конвейер

#mongodb

Вопрос:

У меня есть трубопровод

 [  {'$match':{templateId:ObjectId('blabla')}},  {  "$sort" : {  "_id" : 1  }  },  {  "$facet" : {  "paginatedResult" : [  {  "$skip" : 0  },  {  "$limit" : 100  }  ],  "totalCount" : [  {  "$count" : "count"  }  ]  }  }    ])  

Указатель:

 "key" : {  "templateId" : 1,  "_id" : 1  }  

Коллекция насчитывает 10,6 млн документов, 500 тыс. из которых имеют необходимый идентификатор шаблона. Совокупный индекс использования

 "planSummary" : "IXSCAN { templateId: 1, _id: 1 }",  

Но запрос занимает 16 секунд. Что я сделал не так? Как это ускорить?

Ответ №1:

Для начала вам следует избавиться от $sort оператора. Документы уже отсортированы по _id, так как документы уже гарантированно отсортированы по { templateId: 1, _id: 1 } индексу. Результатом является сортировка 500 тысяч, которые в любом случае уже отсортированы.

Далее, вы не должны использовать этот $skip подход. При большом количестве страниц вы пропустите большое количество документов, почти до 500 тысяч (скорее, индексные записи, но все же).

Я предлагаю альтернативный подход:

Для первой страницы вычислите идентификатор, который, как вы точно знаете, выпадает из левой части индекса. Скажем, если вы знаете, что у вас нет записей, датированных 2019 годом и ранее, вы можете использовать оператор сопоставления, аналогичный этому:

 var pageStart = ObjectId.fromDate(new Date("2020/01/01"))   

Тогда ваш оператор сопоставления должен выглядеть следующим образом:

 {'$match' : {templateId:ObjectId('blabla'), _id: {$gt: pageStart}}}   

Для следующих страниц следите за последним документом предыдущей страницы: если самый правый документ _id находится x на определенной странице, то pageStart он должен быть x для следующей страницы.

Таким образом, ваш конвейер может выглядеть следующим образом:

 [   {'$match' : {templateId:ObjectId('blabla'), _id: {$gt: pageStart}}},  {  "$facet" : {  "paginatedResult" : [  {  "$limit" : 100  }  ]    }  } ]  

Обратите внимание, что теперь $skip это $facet также отсутствует у оператора.

Комментарии:

1. Спасибо вам за ответ. Да, я знаю, что мне не следует использовать skip, но моему переднему нужен пейджинатор… Так что… Мне нужно $пропустить и $посчитать в одном агрегате

2. Что ж, в этом случае я бы предложил внедрить пользовательский механизм разбиения на страницы, отслеживая последний прочитанный документ для каждого клиента, как описано выше. Вы можете подсчитать и кэшировать количество документов в отдельном запросе. Это будет не совсем точно, я знаю. Но что-то должно быть поставлено под угрозу, производительность, возможные устаревшие данные, дополнительные строки кода, если не использовать абстракцию разбиения драйверов на страницы. Ты сам это сказал. Вы не можете этого допустить: 15 секунд для одного вызова. Избыточность $sort может немного сэкономить, но $count каждый звонок-кость в горле