$Этап ПРОПУСКА содержит все предыдущие записи

#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 , MongoDB NoSQL также хорош, как и Redis. Есть ли какая-либо польза в использовании Redis для всех операций чтения в качестве слоя поверх Mongo?

3. Redis действует только как слой кэширования для хранения результатов ваших запросов. Таким образом, ваш алгоритм чем-то похож if (!redisCache) model.find(...) . Вы можете настроить redis на истечение срока действия ключа через определенное время (например, через 60 секунд), поэтому в течение этих 60 секунд ваш запрос mongodb вызывается только один раз, что приводит к более быстрому ответу, особенно для запроса, выполнение которого занимает много времени.

4. Представьте, что если у вас есть функция поиска автозаполнения, поиск слова «алгоритм» вызовет mongodb 9 раз . Если 10 человек будут искать одно и то же слово в течение короткого промежутка времени, вы будете повторно вызывать один и тот же сложный запрос 90 раз. В то время как если вы добавите слой кэширования, redis сможет ответить на запрос за более короткое время.