#mongodb #mongoose #aggregation-framework
Вопрос:
У меня есть коллекция 1.2M
документов, и я составляю их список, используя pagination
(25 на странице). Для этого я использую Aggregation Framework
$sort->$skip->$limit->$project
соответственно с этапами. Все работает нормально, но странное поведение $skip
вызывает nReturned
высокую ценность документов, когда я продолжаю изучать страницы.
Например, предположим $limit => 25
, Страница 1 => > nReturned 25, Skip 0, Display 1-25
, на странице 2 => > nReturned 50, Skip 25, Display 26-50
, на странице 3 => > nReturned 75, Skip 50, Display 51-75
и так далее… и на ПОСЛЕДНЕЙ странице => nReturned SOME_MILLION, Skip MILLION, Display LAST_MILLIONS
Если я посещу последние страницы, nReturned, totalDocsExamined and totalKeysExamined
будет около 1,2 М, так как у меня много записей.
Запрос
db.collection.aggregate([
{
$sort : {
date_added : -1
}
},
{
$skip : 50
},
{
$limit : 25
},
{
$project : {
luid : 1,
name : 1
}
}
])
Я делаю что-то не так или есть какой-то способ оптимизировать этот запрос?
Комментарии:
1. Я забыл упомянуть, что я использую индекс на
date_added : -1
Ответ №1:
Использование $skip
очень неэффективно для памяти, поэтому, если вы перейдете на последнюю страницу, вы пропустите 1,2 млн записей.
Вы действительно будете отображать страницу 10000 под своей нумерацией страниц или там будут <
и >
стрелки?
Лучшим способом было бы использовать последний ключ, например, записи следующего.
{
_id: 1
_data: ...
},
{
_id: 2
data: ...
}....
На первой странице мой последний ключ будет равен 25, на второй странице мой последний ключ будет равен 50 или, например, 62, если мои ключи не в порядке (удалены между ними).
Тогда моя разбивка на страницы будет такой же простой, как
Model.find({ _id: { $gt: last }}).limit(25).sort({ _id: 1 })
Где last
последний идентификатор, полученный на странице 1.
вызов предыдущей страницы так же прост, как переключение $gt
на $lt
сортировку и обратная сортировка
Совет: в большинстве случаев никто не будет переходить на страницу выбора, например, 100204, у нас обычно будет функция поиска, если у нас действительно так много записей.
Если нам действительно нужно использовать $skip
, слой кэширования должен быть для кэширования извлеченных записей (например, с помощью redis), чтобы предотвратить повторный вызов одного и того же запроса.
Комментарии:
1. Большое спасибо за предложение. Я уже реализовал функцию ПОИСКА, и обычно ppl использует только ее.
2. Как вы и предположили
Redis
, MongoDBNoSQL
также хорош, как и Redis. Есть ли какая-либо польза в использовании Redis для всех операций чтения в качестве слоя поверх Mongo?3. Redis действует только как слой кэширования для хранения результатов ваших запросов. Таким образом, ваш алгоритм чем-то похож
if (!redisCache) model.find(...)
. Вы можете настроить redis на истечение срока действия ключа через определенное время (например, через 60 секунд), поэтому в течение этих 60 секунд ваш запрос mongodb вызывается только один раз, что приводит к более быстрому ответу, особенно для запроса, выполнение которого занимает много времени.4. Представьте, что если у вас есть функция поиска автозаполнения, поиск слова «алгоритм» вызовет mongodb 9 раз . Если 10 человек будут искать одно и то же слово в течение короткого промежутка времени, вы будете повторно вызывать один и тот же сложный запрос 90 раз. В то время как если вы добавите слой кэширования, redis сможет ответить на запрос за более короткое время.