⚠️ Эта страница автоматически переведена, и перевод может быть несовершенным.
blog-post

Understanding Pagination in Manticore Search

Пагинация является важной функцией любого поискового движка, позволяя пользователям эффективно перемещаться по большим наборам результатов. Manticore Search предлагает несколько мощных методов пагинации, каждый со своими преимуществами и ограничениями. Эта статья исследует различные варианты пагинации, доступные в Manticore Search, с практическими примерами SQL, чтобы помочь вам реализовать наилучшее решение для вашего случая использования.

Содержание

  1. Введение в пагинацию
  2. Настройка тестовой среды
  3. Традиционная пагинация на основе смещения
  4. Окно набора результатов и max_matches
  5. Пагинация на основе скролла
  6. Пагинация через HTTP JSON
  7. Лучшие практики сортировки для пагинации
  8. Ограничения и соображения производительности
  9. Фасетный поиск с пагинацией
  10. Практические примеры использования
  11. Заключение

Введение в пагинацию

При работе с большими наборами данных возвращать все совпадающие результаты сразу непрактично. Пагинация решает эту проблему, разбивая результаты на управляемые части или «страницы». Manticore Search предоставляет несколько подходов к пагинации, чтобы удовлетворить различные сценарии использования, от простой навигации по списку до интерфейсов бесконечной прокрутки.

В этой статье мы рассмотрим три основных метода пагинации:

  • Традиционная пагинация на основе смещения ( Раздел 3 )
  • Глубокая пагинация с max_matches ( Раздел 4 )
  • Пагинация на основе скролла для эффективной навигации по большим наборам результатов ( Раздел 5 )

Мы также обсудим их реализацию как через SQL, так и через HTTP JSON интерфейсы, соображения производительности и практические применения.

Настройка тестовой среды

Прежде чем погрузиться в примеры пагинации, создадим тестовую среду с примерными данными, используя инструмент Manticore Load. Этот инструмент упрощает генерацию и загрузку тестовых данных для бенчмаркинга и экспериментов:

# Create a products table and populate it with 10,000 records containing "smartphone" in the title
manticore-load --quiet \
  --batch-size=1000 \
  --threads=4 \
  --total=10000 \
  --drop \
  --init="CREATE TABLE products(title text, description text, price int, category string, rating float, stock int)" \
  --load="INSERT INTO products(id, title, description, price, category, rating, stock) VALUES(<increment>, '<text/3/5> smartphone', '<text/20/50>', <int/10/1000>, '<string/5/10>', <float/1/5>, <int/0/100>)"

Вывод:
manticore-load output

С этой тестовой средой мы теперь можем исследовать различные методы пагинации.

Традиционная пагинация на основе смещения

Самый простой метод пагинации в Manticore Search использует оператор LIMIT с параметрами смещения и количества. Этот подход знаком каждому, кто работал с базами данных SQL.

Базовый синтаксис

SELECT ... FROM ... [LIMIT [offset,] row_count]
SELECT ... FROM ... [LIMIT row_count][ OFFSET offset]

Примеры

-- Return the first 5 results (page 1)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5;

Вывод:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | Weak spoke? fall. smartphone                 |    10 |     1272 |
| 1388 | 3; her day of. smartphone                    |    10 |     1272 |
| 1636 | Positively bad winter clean. smartphone      |    10 |     1272 |
| 5628 | Foolish from went heard low. smartphone      |    10 |     1272 |
| 8561 | Left sent. Infrequently, try lay. smartphone |    10 |     1272 |
+------+----------------------------------------------+-------+----------+
-- Return results 6-10 (page 2)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5;

Вывод:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
|  246 | Soft technically took. smartphone            |    11 |     1272 |
| 1105 | Dirty up are! temporarily called. smartphone |    11 |     1272 |
| 3293 | Instantly thick ran that hurt. smartphone    |    11 |     1272 |
| 3736 | Work her wrong locally. smartphone           |    11 |     1272 |
| 6978 | Stale loud passively sweet clean. smartphone |    11 |     1272 |
+------+----------------------------------------------+-------+----------+
-- Alternative syntax with separate LIMIT and OFFSET
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5 OFFSET 10;
-- Results 11-15 (page 3)

Вывод:

+------+----------------------------------------+-------+----------+
| id   | title                                  | price | weight() |
+------+----------------------------------------+-------+----------+
| 7318 | Her spent at. smartphone               |    11 |     1272 |
| 8436 | His? felt work. Angry fast. smartphone |    11 |     1272 |
| 9699 | Feel spend funny. smartphone           |    11 |     1272 |
| 2326 | Quietly wet drew found. smartphone     |    12 |     1272 |
| 4171 | Set perhaps new send soon. smartphone  |    12 |     1272 |
+------+----------------------------------------+-------+----------+

Этот метод прост в реализации и хорошо подходит для навигации по начальным страницам результатов. Однако он становится менее эффективным для глубокой пагинации из‑за того, как Manticore обрабатывает запросы внутренне. Подробности об этих ограничениях см. в Раздел 8: Ограничения и соображения производительности .

Окно набора результатов и max_matches

По умолчанию Manticore Search ограничивает количество возвращаемых совпадений до 1000. Если попытаться выполнить пагинацию за пределами этого лимита, запрос завершится ошибкой. Это ограничение можно изменить с помощью параметра max_matches.

Понимание max_matches

Параметр max_matches определяет, сколько совпадений Manticore будет хранить в ОЗУ во время поиска. Он предназначен для ограничения использования памяти на один запрос. Значение по умолчанию — 1000, что достаточно для большинства типичных сценариев поиска, но его можно увеличить, когда требуется доступ к более глубоким страницам результатов.

-- Increase max_matches to allow deeper pagination (results 1001-1005)
SELECT id, title, price FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 1000, 5 
OPTION max_matches=1500;

Вывод:

+------+-----------------------------------------+-------+
| id   | title                                   | price |
+------+-----------------------------------------+-------+
| 3550 | Open sold being late wide. smartphone   |   111 |
| 4896 | Grew. Wrote as hungry. smartphone       |   111 |
|  647 | Went seen had. smartphone               |   112 |
|  883 | Hold lose; have thought. smartphone     |   112 |
| 1774 | Get! began sick. Gone, wild. smartphone |   112 |
+------+-----------------------------------------+-------+
-- For very deep pagination (results 5000-5005)
SELECT id, title, price FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5000, 5 
OPTION max_matches=5500;

Вывод:

+------+------------------------------------------------+-------+
| id   | title                                          | price |
+------+------------------------------------------------+-------+
| 4894 | Subtly? inside for spend evening. smartphone   |   507 |
| 5203 | Bad; artistically from yearly. smartphone      |   507 |
| 7446 | Empty probably clever! universally. smartphone |   507 |
| 8053 | In! rich daily irregularly. smartphone         |   507 |
| 8055 | Short cold fall; keep physically. smartphone   |   507 |
+------+------------------------------------------------+-------+

Имейте в виду, что увеличение max_matches приводит к росту потребления памяти. Каждое совпадение занимает память, поэтому слишком высокое значение может негативно сказаться на производительности сервера, особенно при высокой нагрузке. Для альтернативного подхода к глубокой пагинации, более экономного по памяти, см. Пагинацию на основе скролла .

Пагинация на основе скролла

Пагинация на основе скролла предоставляет эффективный способ навигации по большим наборам результатов. В отличие от традиционной пагинации на основе смещения, она использует токен для отслеживания текущей позиции в наборе результатов, что помогает эффективно перемещаться по последовательным страницам.

Как это работает

  1. Выполните начальный запрос с критериями сортировки (в ORDER BY обязательно должен присутствовать id)
  2. Получите токен скролла, который инкапсулирует текущую позицию
  3. Используйте этот токен в последующих запросах для получения следующей партии результатов

Примеры

-- Initial query with sorting criteria (must include id in ORDER BY)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5; SHOW SCROLL\G

Вывод:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | Weak spoke? fall. smartphone                 |    10 |     1272 |
| 1388 | 3; her day of. smartphone                    |    10 |     1272 |
| 1636 | Positively bad winter clean. smartphone      |    10 |     1272 |
| 5628 | Foolish from went heard low. smartphone      |    10 |     1272 |
| 8561 | Left sent. Infrequently, try lay. smartphone |    10 |     1272 |
+------+----------------------------------------------+-------+----------+

*************************** 1. row ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ==
-- Subsequent paginated query using the scroll token
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5 
OPTION scroll='eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MywidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo0NCwidHlwZSI6ImludCJ9XX0=';

Вывод:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | Weak spoke? fall. smartphone                 |    10 |     1272 |
| 1388 | 3; her day of. smartphone                    |    10 |     1272 |
| 1636 | Positively bad winter clean. smartphone      |    10 |     1272 |
| 5628 | Foolish from went heard low. smartphone      |    10 |     1272 |
| 8561 | Left sent. Infrequently, try lay. smartphone |    10 |     1272 |
+------+----------------------------------------------+-------+----------+

Пагинация на основе скролла особенно подходит для реализации функции «Загрузить ещё» или бесконечной прокрутки в веб‑приложениях. Для полного примера реализации см. Практические примеры использования .

Пагинация через HTTP JSON

Помимо SQL, Manticore Search предлагает пагинацию через HTTP JSON. Это особенно полезно для веб‑приложений или микросервисных архитектур. Как традиционная пагинация на основе смещения, так и пагинация на основе скролла поддерживаются через JSON API.

Традиционная пагинация на основе смещения через HTTP JSON

HTTP JSON API использует параметры offset и limit для управления пагинацией. Это эквивалентно синтаксису SQL LIMIT offset, count.

Базовый синтаксис

{
  "table": "table_name",
  "query": { "match": { ... } },
  "limit": number_of_results,
  "offset": starting_position
}

Пример

# First page (results 1-5)
curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 5,
  "offset": 0,
  "sort": [
    { "_score": "desc" },
    { "price": "asc" },
    { "id": "asc" }
  ]
}'|jq .

Вывод:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 1179,
        "_score": 1272,
        "_source": {
          "title": "Weak spoke? fall. smartphone",
          "description": "Made down never long 3 angry are went asked closed! cold historically got take can to commonly emotionally; socially asked get tame frequently think nowhere? carefully? scientifically small.",
          "price": 10,
          "category": "hkadwpwwk",
          "rating": 2.300000,
          "stock": 63
        }
      },
      // ... more results ...
    ]
  }
}
# Second page (results 6-10)
curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 5,
  "offset": 5,
  "sort": [
    { "_score": "desc" },
    { "price": "asc" },
    { "id": "asc" }
  ]
}' | jq .

Вывод:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 246,
        "_score": 1272,
        "_source": {
          "title": "Soft technically took. smartphone",
          "description": "This sometimes empty short normally! been send. Locally kind! quickly might infrequently culturally softly became tried quietly good gradually; inside dead loud.",
          "price": 11,
          "category": "wuiymrjdp",
          "rating": 1.600000,
          "stock": 87
        }
      },
      // ... more results ...
    ]
  }
}

Альтернативный синтаксис

HTTP JSON API также поддерживает альтернативный синтаксис с использованием from и size вместо offset и limit:

{
  "table": "table_name",
  "query": { "match": { ... } },
  "size": number_of_results,
  "from": starting_position
}

Это эквивалентно предыдущему формату, но следует конвенции Elasticsearch, которая может быть более знакома некоторым пользователям.

Пагинация с прокруткой через HTTP JSON

HTTP JSON API также поддерживает пагинацию с прокруткой для более эффективной глубокой пагинации. Это требует настройки правильных критериев сортировки и использования токена прокрутки.

Начальный запрос

curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 5,
  "track_scores": true,
  "sort": [
    { "_score": "desc" },
    { "id": "asc" }
  ],
  "options": {
    "scroll": true
  }
}' | jq .

Вывод:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 1,
        "_score": 1272,
        "_source": {
          "title": "Incorrectly heavy soft. smartphone",
          "description": "Carefully late sit; put draw afraid; gave hardly been vaguely 1000 this at understood gave always east fake, dull internationally sent. At cold; hot.",
          "price": 540,
          "category": "ogqejby",
          "rating": 2.800000,
          "stock": 52
        }
      },
      // ... more results ...
    ]
  },
  "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1LCJ0eXBlIjoiaW50In1dfQ=="
}

Обратите внимание на значение scroll в ответе, которое представляет собой токен в формате base64, указывающий ваше текущее положение в наборе результатов.

Последующие запросы

curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 5,
  "track_scores": true,
  "options": {
    "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1LCJ0eXBlIjoiaW50In1dfQ=="
  }
}' | jq .

Вывод:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 9995,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 6,
        "_score": 1272,
        "_source": {
          "title": "Negatively! it become. smartphone",
          "description": "Be quiet cold nightly poor hourly had thin got dry, informally work, 100 50 needed specifically serious do bring below began! rich loud.",
          "price": 602,
          "category": "hjpwt",
          "rating": 2.500000,
          "stock": 47
        }
      },
      // ... more results ...
    ]
  },
  "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9XX0="
}

Каждый ответ включает новый токен прокрутки для следующей страницы, позволяя продолжать пагинацию.

Лучшие практики пагинации HTTP JSON

  1. Включайте критерии сортировки: Для предсказуемых результатов пагинации всегда указывайте явную сортировку, например:
    "sort": [
      { "_score": "desc" },
      { "price": "asc" },
      { "id": "asc" }
    ]
    
  2. Правильно обрабатывайте токены прокрутки: Сохраняйте токен прокрутки из каждого ответа и используйте его для следующего запроса.
  3. Используйте соответствующую обработку ошибок: Проверяйте ответы на наличие ошибок, особенно при глубокой пагинации.
  4. Учитывайте размер ответа: Управляйте объёмом возвращаемых данных, выбирая только необходимые поля с помощью параметра _source:
    "_source": ["title", "price", "rating"]
    
  5. Отслеживайте производительность: Для очень больших наборов результатов следите за производительностью сервера и рассматривайте использование пагинации с прокруткой.

Методы пагинации HTTP JSON следуют тем же принципам, что и их SQL-аналоги (описанные в Sections 3 и 5 ), но используют синтаксис JSON. Это делает их идеальными для современных веб‑приложений и REST API.

Лучшие практики сортировки для пагинации

Правильная сортировка имеет решающее значение для эффективной пагинации, особенно при пагинации с прокруткой. Ниже приведены основные соображения:

1. Включайте уникальный идентификатор

Для получения согласованных результатов пагинации всегда включайте уникальный идентификатор (обычно поле id) в предложение ORDER BY. Это гарантирует, что даже если несколько документов имеют одинаковое значение по другим критериям сортировки, пагинация всё равно будет давать согласованные результаты.

-- Good: Includes id as the tie-breaker
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- Bad: No unique identifier, may lead to inconsistent pagination
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY price ASC;

2. Включайте weight() для полнотекстового поиска

При использовании MATCH() в запросе всегда включайте weight() в критерии сортировки, чтобы наиболее релевантные результаты отображались первыми:

-- Good: Includes weight() for relevance sorting
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- Less optimal: Missing relevance sorting
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY price ASC, id ASC;

3. Сохраняйте согласованность порядка сортировки

При использовании традиционной пагинации с offset всегда сохраняйте одинаковый порядок сортировки во всех запросах пагинации. Изменение порядка сортировки между страницами может привести к пропущенным или дублированным результатам.

-- First page (correct)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC LIMIT 10;

-- Second page (correct - same sort order)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC LIMIT 10, 10;

-- Second page (incorrect - different sort order)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY rating DESC, id ASC LIMIT 10, 10;

4. Используйте детерминированную сортировку

Для пагинации с прокруткой всегда используйте детерминированные критерии сортировки. Недифференцированные функции, такие как RAND(), следует избегать, так как они приводят к несогласованным результатам между запросами.

-- Good: Deterministic sorting
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- Bad: Non-deterministic sorting, will break scroll pagination
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY RAND();

5. Направление сортировки с ID

При использовании поля id в качестве дополнительного критерия сортировки можно использовать порядок ASC или DESC, но он должен применяться последовательно:

-- These are both valid for scroll pagination
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id DESC;

6. Оптимизация производительности сортировки

Для повышения производительности сортировки рассмотрите возможность использования опции sort_method:

-- For pre-sorted data (e.g., by id)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY id ASC 
LIMIT 5 OPTION sort_method=kbuffer;

-- For other sorting criteria
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5 OPTION sort_method=pq;

Выбранный подход к сортировке может существенно влиять на производительность и согласованность пагинации. Например, детерминированная сортировка необходима для пагинации с прокруткой, а включение уникального идентификатора критично для согласованных результатов во всех методах пагинации. Подробнее о влиянии сортировки на производительность см. в разделе Limitations and Performance Considerations .

Ограничения и соображения по производительности

Понимание ограничений различных методов пагинации поможет выбрать правильный подход для вашего конкретного случая.

Ограничения пагинации на основе offset

  1. Снижение производительности при больших offset: По мере увеличения значения offset производительность запроса снижается. Это происходит потому, что Manticore всё равно должен обработать все записи до значения offset, прежде чем вернуть запрошенную страницу.
  2. Ограничение max_matches: По умолчанию можно пагинировать только первые 1000 результатов. Чтобы выйти за эти пределы, необходимо увеличить max_matches, что повышает использование памяти.
  3. Использование памяти: Большие значения max_matches требуют больше ОЗУ на запрос, что может влиять на производительность сервера при высокой нагрузке.
  4. Отсутствие гарантии согласованных результатов: Если документы добавляются или удаляются между запросами страниц, вы можете увидеть дублирующиеся или пропущенные результаты.

Для сценариев глубокой пагинации или реализации функции "Load More" рассмотрите использование Scroll-Based Pagination , как описано в Section 5 .

Ограничения пагинации с прокруткой

  1. Требуется ID в критериях сортировки: Вы должны включать поле id в предложение ORDER BY, что не всегда может соответствовать желаемой логике сортировки.
  2. Управление токенами: Необходимо хранить и управлять токенами прокрутки между запросами, что усложняет приложение.
  3. Отсутствие гарантии согласованности: Пагинация с прокруткой не гарантирует согласованность на определённый момент времени. Если документы добавляются, удаляются или изменяются между запросами, результаты всё равно могут измениться.
  4. Отсутствие произвольного доступа: В отличие от пагинации на основе offset, невозможно перейти напрямую к конкретной странице; необходимо перемещаться последовательно.

Фасетный поиск с пагинацией

Фасетный поиск позволяет пользователям фильтровать и перемещаться по результатам поиска, используя несколько измерений. При сочетании с пагинацией важно понимать, как пагинация влияет на результаты фасетов.

-- Faceted search with pagination (first page)
SELECT id, title, price, weight()
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 0, 5
FACET category ORDER BY COUNT(*) DESC;

Вывод:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | Weak spoke? fall. smartphone                 |    10 |     1272 |
| 1388 | 3; her day of. smartphone                    |    10 |     1272 |
| 1636 | Positively bad winter clean. smartphone      |    10 |     1272 |
| 5628 | Foolish from went heard low. smartphone      |    10 |     1272 |
| 8561 | Left sent. Infrequently, try lay. smartphone |    10 |     1272 |
+------+----------------------------------------------+-------+----------+
+-----------+----------+
| category  | count(*) |
+-----------+----------+
| ogqejby   |        1 |
| unmfujgqr |        1 |
| ttefm     |        1 |
| hceihdy   |        1 |
| sicjr     |        1 |
| hjpwt     |        1 |
| tvfqyj    |        1 |
| mvdjhbexo |        1 |
| scayuo    |        1 |
| esmlh     |        1 |
| fvbhplj   |        1 |
| lcphmiqmv |        1 |
| lnjfhb    |        1 |
| qexfdulub |        1 |
| tbswa     |        1 |
| eekarf    |        1 |
| airjuod   |        1 |
| ozkbuvgj  |        1 |
| yafbhr    |        1 |
| duccr     |        1 |
+-----------+----------+
-- Faceted search with pagination (second page)
SELECT id, title, price, weight()
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5
FACET category ORDER BY COUNT(*) DESC;

Вывод:

+------+-----------------------------------------------------------+-------+----------+
| id   | title                                                     | price | weight() |
+------+-----------------------------------------------------------+-------+----------+
|  246 | Soft technically took. smartphone                         |    11 |     1272 |
| 1105 | Dirty up are! temporarily called. smartphone              |    11 |     1272 |
| 3293 | Instantly thick ran that hurt. smartphone                 |    11 |     1272 |
| 3736 | Work her wrong locally. smartphone                        |    11 |     1272 |
| 6978 | Stale loud passively sweet clean. smartphone              |    11 |     1272 |
+------+-----------------------------------------------------------+-------+----------+
+-----------+----------+
| category  | count(*) |
+-----------+----------+
| ogqejby   |        1 |
| unmfujgqr |        1 |
| ttefm     |        1 |
| hceihdy   |        1 |
| sicjr     |        1 |
| hjpwt     |        1 |
| tvfqyj    |        1 |
| mvdjhbexo |        1 |
| scayuo    |        1 |
| esmlh     |        1 |
| fvbhplj   |        1 |
| lcphmiqmv |        1 |
| lnjfhb    |        1 |
| qexfdulub |        1 |
| tbswa     |        1 |
| eekarf    |        1 |
| airjuod   |        1 |
| ozkbuvgj  |        1 |
| yafbhr    |        1 |
| duccr     |        1 |
+-----------+----------+

Важное замечание: Результаты фасетов рассчитываются на основе полного набора результатов, соответствующего запросу, а не только на части, отображаемой постранично. Это означает, что независимо от того, какую страницу вы просматриваете, подсчёты фасетов представляют распределение по всем соответствующим документам. Оператор LIMIT влияет только на то, какие документы возвращаются в основном наборе результатов, а не на расчёт фасетов. Как видно из приведённых выше примеров, результаты фасетов одинаковы для обеих страниц, подтверждая, что они вычисляются из полного набора результатов.

Пагинация внутри результатов фасетов

В дополнение к постраничному отображению основных результатов поиска, Manticore Search также позволяет постранично выводить сами результаты фасетов. Это особенно полезно при работе с большим количеством значений фасетов, когда отображать их все может быть непрактично.

Вы можете использовать оператор LIMIT внутри инструкции FACET, чтобы контролировать количество возвращаемых значений фасета.

-- Get only the first 5 categories ordered by count
SELECT id, title FROM products 
WHERE MATCH('smartphone') 
LIMIT 3 
FACET category ORDER BY COUNT(*) DESC, category asc LIMIT 0,5;

Вывод:

+------+---------------------------------------------+
| id   | title                                       |
+------+---------------------------------------------+
|    1 | Incorrectly heavy soft. smartphone          |
|    2 | Know of make afraid foolish. smartphone     |
|    3 | Poor spring evening drove young. smartphone |
+------+---------------------------------------------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaaabzwfzn |        1 |
| aabswb     |        1 |
| aacla      |        1 |
| aaejmtubv  |        1 |
| aaethytj   |        1 |
+------------+----------+

Вы можете задавать разные параметры пагинации для разных фасетов в одном запросе:

-- Multiple facets with different pagination parameters
SELECT id, title FROM products 
WHERE MATCH('smartphone') 
LIMIT 3 
FACET category ORDER BY COUNT(*) DESC, category asc LIMIT 0,5 
FACET category ORDER BY category ASC LIMIT 3,5 
FACET price ORDER BY FACET() ASC LIMIT 5;

Вывод:

+------+---------------------------------------------+
| id   | title                                       |
+------+---------------------------------------------+
|    1 | Incorrectly heavy soft. smartphone          |
|    2 | Know of make afraid foolish. smartphone     |
|    3 | Poor spring evening drove young. smartphone |
+------+---------------------------------------------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaaabzwfzn |        1 |
| aabswb     |        1 |
| aacla      |        1 |
| aaejmtubv  |        1 |
| aaethytj   |        1 |
+------------+----------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaejmtubv  |        1 |
| aaethytj   |        1 |
| aaktjgaa   |        1 |
| aalfwcvwil |        1 |
| aaqmumofe  |        1 |
+------------+----------+
+-------+----------+
| price | count(*) |
+-------+----------+
|    10 |        5 |
|    11 |        8 |
|    12 |       10 |
|    13 |        9 |
|    14 |        8 |
+-------+----------+

Этот пример демонстрирует несколько техник пагинации фасетов:

  1. Первый фасет возвращает топ‑5 категорий по количеству (смещение 0, лимит 5)
  2. Второй фасет возвращает категории в алфавитном порядке, пропуская первые 3 (смещение 3, лимит 5)
  3. Третий фасет возвращает первые 5 ценовых точек, упорядоченных по возрастанию значения

Пагинация фасетов особенно полезна для:

  • Обработки большого количества значений фасетов (например, тысячи категорий продуктов)
  • Реализации функции «Показать ещё» для значений фасетов
  • Создания динамических интерфейсов фильтрации, где сначала показываются самые популярные фильтры
  • Оптимизации производительности за счёт ограничения количества возвращаемых значений фасета

Примечание: В отличие от пагинации основных результатов, которая имеет последствия для производительности при больших смещениях, пагинация фасетов обычно эффективна даже при больших смещениях, поскольку расчёт фасетов происходит после этапа сопоставления документов.

Пагинация фасетов через HTTP JSON

Пагинация фасетов также доступна при использовании HTTP JSON интерфейса. Вы можете ограничить количество возвращаемых значений фасета, используя параметр size в спецификации агрегации:

{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 3,
  "aggs": {
    "category_counts": {
      "terms": {
        "field": "category",
        "size": 5
      }
    }
  }
}

В этом примере результаты фасета категории ограничены топ‑5 значениями.

Важно: В отличие от SQL‑фасетов, HTTP JSON интерфейс в текущий момент поддерживает только ограничение количества значений фасета параметром size. Параметр OFFSET для пагинации внутри фасетов не поддерживается. Можно указать только количество возвращаемых результатов, а не какие из них пропустить.

Для получения более подробной информации о пагинации фасетов через HTTP JSON см. документацию Manticore Search .

Практические примеры использования

Пагинация с подсветкой

-- Pagination with highlighting
SELECT id, title, price, weight(),
       HIGHLIGHT({limit=100}) as highlight
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5;

Вывод:

+------+----------------------------------------------+-------+----------+-----------------------------------------------------+
| id   | title                                        | price | weight() | highlight                                           |
+------+----------------------------------------------+-------+----------+-----------------------------------------------------+
|  246 | Soft technically took. smartphone            |    11 |     1272 | Soft technically took. <b>smartphone</b>            |
| 1105 | Dirty up are! temporarily called. smartphone |    11 |     1272 | Dirty up are! temporarily called. <b>smartphone</b> |
| 3293 | Instantly thick ran that hurt. smartphone    |    11 |     1272 | Instantly thick ran that hurt. <b>smartphone</b>    |
| 3736 | Work her wrong locally. smartphone           |    11 |     1272 | Work her wrong locally. <b>smartphone</b>           |
| 6978 | Stale loud passively sweet clean. smartphone |    11 |     1272 | Stale loud passively sweet clean. <b>smartphone</b> |
+------+----------------------------------------------+-------+----------+-----------------------------------------------------+

Реализация кнопки «Load More» с прокруткой пагинации

Пагинация на основе прокрутки идеальна для реализации функции «Load More», так как обеспечивает эффективную навигацию по большим наборам результатов. Ниже показано, как это реализовать:

-- Initial query with SHOW SCROLL to get the scroll token
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5; SHOW SCROLL\G

Вывод:

+------+------------------------------------------------+-------+----------+
| id   | title                                          | price | weight() |
+------+------------------------------------------------+-------+----------+
| 1179 | Weak spoke? fall. smartphone                   |    10 |     1272 |
| 1388 | 3; her day of. smartphone                      |    10 |     1272 |
| 1636 | Positively bad winter clean. smartphone        |    10 |     1272 |
| 5628 | Foolish from went heard low. smartphone        |    10 |     1272 |
| 8561 | Left sent. Infrequently, try lay. smartphone   |    10 |     1272 |
+------+------------------------------------------------+-------+----------+

*************************** 1. row ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ==
1 row in set (0.00 sec)
-- When user clicks "Load More", use the scroll token to get the next batch
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
LIMIT 5 
OPTION scroll='eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ=='; SHOW SCROLL\G

Вывод:

+------+---------------------------------------------+-------+----------+
| id   | title                                       | price | weight() |
+------+---------------------------------------------+-------+----------+
|  246 | Soft technically took. smartphone           |    11 |     1272 |
| 1105 | Dirty up are! temporarily called. smartphone|    11 |     1272 |
| 3293 | Instantly thick ran that hurt. smartphone   |    11 |     1272 |
| 3736 | Work her wrong locally. smartphone          |    11 |     1272 |
| 6978 | Stale loud passively sweet clean. smartphone|    11 |     1272 |
+------+---------------------------------------------+-------+----------+

*************************** 1. row ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMSwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo2OTc4LCJ0eXBlIjoiaW50In1dfQ==

Для каждого последующего клика «Load More» вы будете использовать новый токен прокрутки, возвращённый предыдущим запросом. Этот подход имеет несколько преимуществ перед традиционной пагинацией на основе смещения:

  1. Лучшее производительность: отсутствие деградации производительности при глубокой пагинации
  2. Отсутствие ограничения max_matches: возможность эффективно постранично выводить миллионы результатов
  3. Последовательный доступ: эффективная навигация по последовательным страницам результатов

Советы по реализации:

  • Сохраняйте токен прокрутки в состоянии вашего приложения (на клиенте или сервере)
  • Включайте токен в каждый последующий запрос, когда пользователь нажимает «Load More»
  • Всегда используйте самый последний токен, так как каждый токен представляет текущую позицию в наборе результатов
  • Помните, что токены прокрутки временные и со временем истекают

Этот подход к реализации решает многие ограничения традиционной пагинации на основе смещения, описанные в Section 8 , особенно для сценариев глубокой пагинации.

Заключение

Manticore Search предлагает гибкие варианты пагинации, подходящие для разных сценариев использования. Традиционная пагинация на основе смещения хорошо работает для простых интерфейсов с ограниченной навигацией по страницам, тогда как пагинация на основе прокрутки обеспечивает лучшую производительность для сценариев глубокой пагинации, таких как бесконечная прокрутка или экспорт данных.

При реализации пагинации в Manticore Search:

  1. Для поверхностной пагинации (первые несколько страниц): используйте традиционную пагинацию на основе смещения с согласованной сортировкой.
  2. Для глубокой пагинации (много страниц или большие наборы данных): используйте пагинацию на основе прокрутки с правильными критериями сортировки по id.
  3. Для согласованных результатов: всегда включайте поле id в предложение ORDER BY и поддерживайте согласованные критерии сортировки во всех запросах.
  4. Для полнотекстового поиска: включайте weight() или _score в критерии сортировки, чтобы наиболее релевантные результаты отображались первыми.

Понимая сильные стороны, ограничения и лучшие практики каждого подхода, вы сможете внедрить наиболее эффективную стратегию пагинации для конкретных потребностей вашего приложения. Помните, что лучший метод пагинации зависит от вашего сценария использования, объёма данных и требований к пользовательскому опыту.

Установить Manticore Search

Установить Manticore Search