#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
каждый звонок-кость в горле