Исправление пагинации MongoDB $search: searchBefore возвращает одинаковые результаты
Решение проблемы пагинации MongoDB $search, когда searchBefore и searchAfter возвращают идентичные результаты. Узнайте о правильном использовании токенов, настройке сортировки и лучших практиках для пагинации Atlas Search.
Проблема с пагинацией в MongoDB $search: searchBefore и searchAfter возвращают идентичные результаты
Я столкнулся с неожиданным поведением функциональности пагинации MongoDB $search. При использовании следующего агрегационного запроса:
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
searchBefore: "<paginationToken>"
}
},
{
$project: {
name: "name",
paginationToken: {$meta: "searchSequenceToken"},
score: { $meta: "searchScore" }
}
}
]
Первая страница работает корректно, а использование searchAfter правильно возвращает следующую страницу. Однако, когда я использую searchBefore для получения предыдущей страницы, он возвращает те же документы, что и searchAfter, вместо результатов предыдущей страницы.
Я использую правильные токены:
- Токен первого результата для
searchBefore - Токен последнего результата для
searchAfter
Проблема также воспроизводится простым заменой searchAfter на searchBefore при намерении перейти к следующей странице.
Что может вызывать такое поведение, и что я, возможно, делаю неправильно в своей реализации?
Проблема, с которой вы сталкиваетесь при пагинации MongoDB $search, когда searchBefore и searchAfter возвращают идентичные результаты, обычно вызвана неправильной обработкой токенов или непониманием того, как работают токены пагинации. searchSequenceToken, генерируемый MongoDB, имеет направление - токены с конца набора результатов следует использовать с searchAfter для навигации вперед, а токены с начала - с searchBefore для навигации назад. Чаще всего это происходит, когда пользователи пытаются использовать один и тот же токен для обоих направлений или когда конфигурация сортировки создает неоднозначность в упорядочивании.
Содержание
- Основы пагинации поиска MongoDB
- Основные причины проблемы
- Правильное использование токенов и реализация
- Шаги по устранению неполадок
- Лучшие практики для пагинации поиска
- Продвинутые техники пагинации
Основы пагинации поиска MongoDB
Пагинация в MongoDB Atlas Search работает иначе, чем традиционная пагинация MongoDB. Вместо использования skip() и limit(), которые могут быть неэффективны при работе с большими наборами данных, Atlas Search использует пагинацию по набору ключей (keyset pagination) с опциями searchAfter и searchBefore.
Основные компоненты:
searchSequenceToken: Уникальный токен, генерируемый для каждого документа, который представляет его позицию в отсортированном наборе результатовsearchAfter: Используется для получения результатов после определенного токена (следующая страница)searchBefore: Используется для получения результатов перед определенным токеном (предыдущая страница)
Как объясняется в документации MongoDB, “Чтобы искать перед контрольной точкой, вы должны указать контрольную точку в вашем запросе $search с помощью опции searchBefore и токена, сгенерированного searchSequenceToken.”
Основные причины проблемы
На основе результатов исследований и обсуждений сообщества, вот наиболее распространенные причины, по которым searchBefore и searchAfter возвращают идентичные результаты:
1. Неправильное использование направления токенов
Наиболее частая ошибка - использование токенов в неправильном направлении. Когда вы получаете страницу результатов:
- Для
searchAfter(следующая страница): Используйте последний токен с вашей текущей страницы - Для
searchBefore(предыдущая страница): Используйте первый токен с вашей текущей страницы
Если вы используете один и тот же токен (например, первый токен) как для searchAfter, так и для searchBefore, вы получите пересекающиеся или идентичные результаты.
2. Неоднозначная конфигурация сортировки
Ваша текущая конфигурация сортировки:
sort: { score: { order: -1 }, _id: 1 }
Это может вызвать проблемы, потому что:
- Несколько документов могут иметь одинаковые оценки
- Когда оценки одинаковы, MongoDB переходит к сортировке по
_id: 1 - Это создает неуникальное упорядочивание, делая токены пагинации неоднозначными
Как отмечается в обсуждениях Stack Overflow, “у всего этого одинаковый ‘textScore’, но это порядок, в котором MongoDB вернет эти документы.”
3. Отсутствующая или неправильная конфигурация отслеживания
Для последовательной пагинации необходима правильная конфигурация отслеживания. Согласно документации MongoDB, вы должны включать опции отслеживания:
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
tracking: {
searchSequenceToken: {}
}
}
}
Правильное использование токенов и реализация
Правильный поток пагинации
Вот как правильно реализовать пагинацию:
Первый запрос (без пагинации)
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
tracking: {
searchSequenceToken: {}
}
}
},
{
$project: {
name: 1,
paginationToken: {$meta: "searchSequenceToken"},
score: { $meta: "searchScore" }
}
},
{
$limit: 10
}
]
Запрос следующей страницы
Используйте последний токен с предыдущей страницы:
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
searchAfter: "<последний_токен_с_предыдущей_страницы>",
tracking: {
searchSequenceToken: {}
}
}
},
{
$project: {
name: 1,
paginationToken: {$meta: "searchSequenceToken"},
score: { $meta: "searchScore" }
}
},
{
$limit: 10
}
]
Запрос предыдущей страницы
Используйте первый токен с текущей страницы:
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
searchBefore: "<первый_токен_с_текущей_страницы>",
tracking: {
searchSequenceToken: {}
}
}
},
{
$project: {
name: 1,
paginationToken: {$meta: "searchSequenceToken"},
score: { $meta: "searchScore" }
}
},
{
$limit: 10
}
]
Улучшенная сортировка для лучшей пагинации
Чтобы избежать неоднозначности в сортировке, рассмотрите возможность использования более конкретных критериев сортировки:
sort: {
score: { order: -1 },
_id: 1,
name: 1,
createdAt: -1
}
Это создает более детерминированное упорядочивание, которое снижает вероятность связей.
Шаги по устранению неполадок
Шаг 1: Проверьте извлечение токенов
Убедитесь, что вы извлекаете правильные токены:
- Для
searchAfter: Используйте токен последнего документа в вашем текущем наборе результатов - Для
searchBefore: Используйте токен первого документа в вашем текущем наборе результатов
Шаг 2: Проверьте последовательность сортировки
Добавьте отладку для проверки, что ваша сортировка работает как ожидается:
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
tracking: {
searchSequenceToken: {}
}
}
},
{
$project: {
name: 1,
paginationToken: {$meta: "searchSequenceToken"},
score: { $meta: "searchScore" },
debugSort: { $concat: [ { $toString: "$score" }, "_", { $toString: "$_id" } ] }
}
},
{
$limit: 10
}
]
Шаг 3: Протестируйте с детерминированной сортировкой
Временно используйте более детерминированную сортировку для изоляции проблемы:
sort: {
score: { order: -1 },
name: 1,
_id: 1
}
Шаг 4: Проверьте формат токена
Убедитесь, что токены правильно хранятся и извлекаются. Токены представляют собой строки в кодировке base64 и должны обрабатываться как таковые в коде вашего приложения.
Лучшие практики для пагинации поиска
1. Всегда включайте отслеживание
Всегда включайте конфигурацию tracking с searchSequenceToken:
tracking: {
searchSequenceToken: {}
}
2. Используйте детерминированную сортировку
Проектируйте свои критерии сортировки для минимизации связей:
sort: {
score: { order: -1 },
relevanceField: -1,
_id: 1,
timestamp: -1
}
3. Обрабатывайте крайние случаи
- Корректно обрабатывайте пустые наборы результатов
- Кэшируйте токены для избежания проблем согласованности
- Реализуйте правильную обработку ошибок для недействительных токенов
4. Учитывайте производительность
Для больших наборов данных имейте в виду, что “пагинация по набору ключей работает значительно хуже, чем skip-limit” в определенных сценариях, как отмечено в результатах исследований.
Продвинутые техники пагинации
Двунаправленная пагинация с отслеживанием курсора
Для более надежной реализации отслеживайте как первый, так и последний токены каждой страницы:
// В вашей логике приложения
const currentPage = {
items: results,
firstToken: results[0]?.paginationToken,
lastToken: results[results.length - 1]?.paginationToken,
hasNext: results.length === limit,
hasPrevious: currentPageNumber > 1
};
Гибридный подход к пагинации
Для очень больших наборов данных рассмотрите возможность объединения пагинации поиска с традиционной пагинацией MongoDB:
[
{
$search: {
index: "search-index",
text: { query: "20", path: "name" },
sort: { score: { order: -1 }, _id: 1 },
searchAfter: "<токен>",
tracking: {
searchSequenceToken: {}
}
}
},
{
$skip: 0
},
{
$limit: 20
}
]
Заключение
Проблема с пагинацией MongoDB $search, когда searchBefore и searchAfter возвращают идентичные результаты, обычно возникает из-за неправильного использования токенов или неоднозначных конфигураций сортировки. Следуя этим ключевым практикам, вы можете решить проблему:
- Используйте правильные токены: Последний токен для
searchAfter, первый токен дляsearchBefore - Реализуйте детерминированную сортировку: Добавьте несколько критериев сортировки для минимизации связей
- Включайте конфигурацию отслеживания: Всегда используйте
tracking: { searchSequenceToken: {} } - Проверяйте вашу реализацию: Тестируйте с упрощенной сортировкой сначала, затем постепенно улучшайте
Наиболее частая ошибка - попытка использовать один и тот же токен пагинации для навигации как вперед, так и назад. Помните, что пагинация по набору ключей в MongoDB relies на упорядочивание, установленное вашей конфигурацией сортировки, и токены по своей природе имеют направление.
Для сложных сценариев пагинации рассмотрите возможность реализации надежного управления токенами на уровне вашего приложения и корректной обработки крайних случаев, таких как пустые наборы результатов или недействительные токены.
Источники
- Пагинация результатов - Документация MongoDB Atlas
- Atlas search - searchBefore возвращает те же документы, что и searchAfter - Форумы сообщества MongoDB
- Как пагинировать результаты запроса - Учебное пособие MongoDB Atlas
- Как реализовать searchAfter и searchBefore в MongoDb? - Stack Overflow
- Документация этапа $search поиска MongoDB
- Пагинация в MongoDB: правильный способ VS распространенные ошибки - Форумы сообщества MongoDB