[SQL, API] Дизайн пагинации страниц в API (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Для API может быть сложно вернуть все результаты запроса, особенно если их тысячи. Это создаёт нагрузку на сервер, на клиент, на сеть и часто является ненужным. Поэтому и придумали пагинацию.
Обычный способ разбиения на страницы — это смещение или номер страницы. Вы делаете такой запрос:
GET /api/products?page=10
{"items": [...100 products]}
а дальше такой:
GET /api/products?page=11
{"items": [...another 100 products]}
В случае простого смещения получается ?offset=1000 и ?offset=1100 — тот же старый суп, только разогретый. Здесь мы либо переходим прямо к SQL-запросу типа OFFSET 1000 LIMIT 100, либо умножаем на размер страницы (значение LIMIT). В любом случае, это неоптимальное решение, поскольку каждая база данных должна пропустить эту 1000 строк. А чтобы их пропустить, нужно их идентифицировать. Неважно, это PostgreSQL, ElasticSearch или MongoDB, она должна их упорядочить, пересчитать и выбросить.
Это ненужная работа. Но она повторяется снова и снова, так как такой дизайн легко реализовать — вы непосредственно сопоставляете свой API с запросом к базе данных.
Что же тогда делать? Мы могли бы посмотреть, как устроены базы данных! У них есть понятие курсора — это указатель на строку. Таким образом, вы можете сказать базе данных: «Верни мне 100 строк после этой». И такой запрос гораздо удобнее для базы данных, так как высока вероятность, что вы идентифицируете строку по полю с индексом. И не нужно извлекать и пропускать эти строки, вы пройдёте прямо мимо них.
Пример:
GET /api/products
{"items": [...100 products],
"cursor": "qWe"}
API возвращает (непрозрачную) строку, которую затем можно использовать для получения следующей страницы:
GET /api/products?cursor=qWe
{"items": [...100 products],
"cursor": "qWr"}
С точки зрения реализации есть много вариантов. Как правило, у вас имеются некоторые критерии запроса, например, идентификатор товара (product id). В этом случае вы его кодируете с помощью некоторого обратимого алгоритма (скажем, хэш-идентификаторов). И при получении запроса с курсором вы декодируете его и генерируете запрос типа WHERE id > :cursor LIMIT 100.
Небольшое сравнение производительности. Вот результат смещения:
=# explain analyze select id from product offset 10000 limit 100;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1114.26..1125.40 rows=100 width=4) (actual time=39.431..39.561 rows=100 loops=1)
-> Seq Scan on product (cost=0.00..1274406.22 rows=11437243 width=4) (actual time=0.015..39.123 rows=10100 loops=1)
Planning Time: 0.117 ms
Execution Time: 39.589 ms
А вот результат операции where:
=# explain analyze select id from product where id > 10000 limit 100;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..11.40 rows=100 width=4) (actual time=0.016..0.067 rows=100 loops=1)
-> Seq Scan on product (cost=0.00..1302999.32 rows=11429082 width=4) (actual time=0.015..0.052 rows=100 loops=1)
Filter: (id > 10000)
Planning Time: 0.164 ms
Execution Time: 0.094 ms
Разница в несколько порядков! Конечно, фактические цифры зависят от размера таблицы, от фильтров и реализации хранилища. Вот отличная статья с более подробной технической информацией, см. слайд 42 со сравнением производительности.
Конечно, никто не запрашивает товары по идентификатору — их обычно запрашивают по какой-то релевантности (а затем по идентификатору в качестве решающего параметра). В реальном мире чтобы выбрать решение, нужно посмотреть на конкретные данные. Запросы можно упорядочить по идентификатору (так как он монотонно увеличивается). Товары из списка будущих покупок тоже можно упорядочить таким образом — по времени составления списка. В нашем случае товары загружаются с ElasticSearch, который, естественно, поддерживает такой курсор.
Минус в том, что с помощью stateless API невозможно создать ссылку «Предыдущая страница». В случае пагинации у пользователя невозможно обойти эту проблему. Так что если важно иметь кнопки предудущей/следующей страницы и «Перейти непосредственно на страницу 10», то придётся использовать старый метод. Но в других случаях метод по курсору может значительно повысить производительность, особенно на очень больших таблицах с очень глубокой пагинацией.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Alexander Solovyov
===========Похожие новости:
- [Python, API] Скрапинг Avito без headless-браузера
- [Высокая производительность, PostgreSQL, SQL, Администрирование баз данных] DBA: Ночной Дозор
- [Google Chrome, API, Браузеры, IT-компании] 15 марта в некоторых браузерах на Chromium сломается синхронизация: Google меняет доступ к приватным API
- [PostgreSQL, SQL, Администрирование баз данных, Управление разработкой] «PostgreSQL в кейсах» — продвинутый вебинар 28 января
- [JavaScript, API] Конструктор плейлистов для Spotify
- [Python, API, 1С-Битрикс] Как быстро получить много данных от Битрикс24 через REST API
- Google запретил использование Google API в сторонних браузерах а основе Chromium
- [Тестирование IT-систем, SQL] Что такое транзакция
- [Java, Eclipse, NoSQL, Kubernetes] Фирма «1С» приглашает вас принять участие в нашей первой конференции для системных разработчиков
- [Open source, PHP, PostgreSQL, GitHub, Laravel] Расширяем возможности миграций Laravel за счет Postgres
Теги для поиска: #_sql, #_api, #_paginatsija_stranits (пагинация страниц), #_api, #_sql, #_api
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:06
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Для API может быть сложно вернуть все результаты запроса, особенно если их тысячи. Это создаёт нагрузку на сервер, на клиент, на сеть и часто является ненужным. Поэтому и придумали пагинацию. Обычный способ разбиения на страницы — это смещение или номер страницы. Вы делаете такой запрос: GET /api/products?page=10
{"items": [...100 products]} а дальше такой: GET /api/products?page=11
{"items": [...another 100 products]} В случае простого смещения получается ?offset=1000 и ?offset=1100 — тот же старый суп, только разогретый. Здесь мы либо переходим прямо к SQL-запросу типа OFFSET 1000 LIMIT 100, либо умножаем на размер страницы (значение LIMIT). В любом случае, это неоптимальное решение, поскольку каждая база данных должна пропустить эту 1000 строк. А чтобы их пропустить, нужно их идентифицировать. Неважно, это PostgreSQL, ElasticSearch или MongoDB, она должна их упорядочить, пересчитать и выбросить. Это ненужная работа. Но она повторяется снова и снова, так как такой дизайн легко реализовать — вы непосредственно сопоставляете свой API с запросом к базе данных. Что же тогда делать? Мы могли бы посмотреть, как устроены базы данных! У них есть понятие курсора — это указатель на строку. Таким образом, вы можете сказать базе данных: «Верни мне 100 строк после этой». И такой запрос гораздо удобнее для базы данных, так как высока вероятность, что вы идентифицируете строку по полю с индексом. И не нужно извлекать и пропускать эти строки, вы пройдёте прямо мимо них. Пример: GET /api/products
{"items": [...100 products], "cursor": "qWe"} API возвращает (непрозрачную) строку, которую затем можно использовать для получения следующей страницы: GET /api/products?cursor=qWe
{"items": [...100 products], "cursor": "qWr"} С точки зрения реализации есть много вариантов. Как правило, у вас имеются некоторые критерии запроса, например, идентификатор товара (product id). В этом случае вы его кодируете с помощью некоторого обратимого алгоритма (скажем, хэш-идентификаторов). И при получении запроса с курсором вы декодируете его и генерируете запрос типа WHERE id > :cursor LIMIT 100. Небольшое сравнение производительности. Вот результат смещения: =# explain analyze select id from product offset 10000 limit 100; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------- Limit (cost=1114.26..1125.40 rows=100 width=4) (actual time=39.431..39.561 rows=100 loops=1) -> Seq Scan on product (cost=0.00..1274406.22 rows=11437243 width=4) (actual time=0.015..39.123 rows=10100 loops=1) Planning Time: 0.117 ms Execution Time: 39.589 ms А вот результат операции where: =# explain analyze select id from product where id > 10000 limit 100; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Limit (cost=0.00..11.40 rows=100 width=4) (actual time=0.016..0.067 rows=100 loops=1) -> Seq Scan on product (cost=0.00..1302999.32 rows=11429082 width=4) (actual time=0.015..0.052 rows=100 loops=1) Filter: (id > 10000) Planning Time: 0.164 ms Execution Time: 0.094 ms Разница в несколько порядков! Конечно, фактические цифры зависят от размера таблицы, от фильтров и реализации хранилища. Вот отличная статья с более подробной технической информацией, см. слайд 42 со сравнением производительности. Конечно, никто не запрашивает товары по идентификатору — их обычно запрашивают по какой-то релевантности (а затем по идентификатору в качестве решающего параметра). В реальном мире чтобы выбрать решение, нужно посмотреть на конкретные данные. Запросы можно упорядочить по идентификатору (так как он монотонно увеличивается). Товары из списка будущих покупок тоже можно упорядочить таким образом — по времени составления списка. В нашем случае товары загружаются с ElasticSearch, который, естественно, поддерживает такой курсор. Минус в том, что с помощью stateless API невозможно создать ссылку «Предыдущая страница». В случае пагинации у пользователя невозможно обойти эту проблему. Так что если важно иметь кнопки предудущей/следующей страницы и «Перейти непосредственно на страницу 10», то придётся использовать старый метод. Но в других случаях метод по курсору может значительно повысить производительность, особенно на очень больших таблицах с очень глубокой пагинацией. =========== Источник: habr.com =========== =========== Автор оригинала: Alexander Solovyov ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:06
Часовой пояс: UTC + 5