Открыть список
Как стать автором
Обновить

Комментарии 19

Слабо себе представляю пагинацию только в одну сторону.

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

Лично для меня, бесконечный скролл — очень не удобно. Мне перемещение по нумерованным страницам намного удобнее. А тут пройдет только старый метод.
А почему только в одну сторону? Запросы вида
select id from product where id > 10000 limit 100
И
select id from product where id < 10000 limit 100 order by id desc
Не сильно будут различаться по сложности

Потому что данные из таблицы могут быть удалены или отфильтрованы,

А если нет порядкового id в таблице?

Именно такую пагинацию использует Shopify в своем GraphQL Storefront API. Кстати, у них тоже есть подробная статья: shopify.engineering/pagination-relative-cursors
Мы у себя в компании тоже решили использовать эту пагинацию в GraphQL API на одном из проектов. В целом работает все прекрасно, но есть некоторые специфичные для нас нюансы.
с точки зрения базы данных все замечательно!
но с точки зрения итогового продукта есть ряд вопросов:
— получается для нагруженного проекта нам надо хранить авоську курсоров и следить за тем кто каким курсором пользуется?
— сколько должен жить курсор чтобы пользователь не нарвался на неожиданное поведение пагинации?
— передавать ссылку становится бессмысленно, можно только говорить вот тебе ссылка и промотай там первые 20 страниц?
Первое что приходит в голову — это отдавать self-contained link на предыдущую или следующую страницу.

Например:
{
  "next": "http://example.com/resourse/next/NTAwOzEwMA=="
}


Где base64 — параметр для where и limit.
Или можно вместо base64 шифровать.
Зачем хранить курсоры? какой неожиданное? Курсор знает фронт и передает бэку
Иногда это id, но чаще я видел createdBefore (таймстамп). Клиент просто берет таймстамп последней сущности в списке и запрашивает очередную страницу сущностей
С ссылками да, не передать, но редко вижу чтобы ссылку передавали на список, а не на выбранный айтем
Это действительно больше скроллинг, чем пагинация. отсутствует сортировка по полям которые не являются частью индекса или которые не уникальны. Ещё в ваших запросах нет жесткой сортировки -без неё пагинация не будет работать корректно
«Неважно, это PostgreSQL, ElasticSearch или MongoDB,»
важно! в монго есть бакеты!
А что там насчёт прямой ссылки на нужную страницу, например, в багтрекере?

А что вы подразумеваете под "страницей"? Абстрактный номер страницы, некий offset? Или конкретное содержимое этой страницы?
Если только первое, то да "курсорная" листалка не справится. Она заточена на свойства контента, а не на способ его деления на страницы.
Если же второе, то всё просто:
http://mysite.com/items/?last_key=abcd
где last_key — значение поля документа (реальное или искусственное), которое участвует в запросе к базе данных в качестве "курсора". Например это может быть дата создания документа и запрос в базу будет примерно такой:


SELECT * FROM table WHERE created > {last_key} ORDER BY created LIMIT 100

Есть много случаев, когда такой вариант ссылки будет предпочтительнее. Т.к. меньше вероятность, что ссылка потеряет актуальность, из-за того что подвезли новый контент и теперь на странице номер 10 (в случае листалки по номерам страниц) уже совсем другое содержимое, а не то которое вы хотели показать.

> тот же старый суп, только разогретый

Это перевод на английский идиомы «те же яйца, только в профиль». :)))
Впервые столкнулся с такими курсорами в API Твича, когда писал для него клиента (точнее плагин для плеера). Скажу откровенно, что очень хотелось найти того дизайнера апи и поломать ему что-нибудь. С курсорами работать крайне неудобно, вместо простых арифметических операций с оффсетами для составления запросов — с собой приходится таскать условные массивы курсоров, которые нужно всюду в коде пробрасывать. Причем нужно таскать сразу три вида курсоров: текущий, следующий, предыдущий и в коде учитывать, что предыдущего может не быть, но он может появиться. Но это ещё терпимо хоть и неудобно, настоящая же боль начинается, когда ты хочешь добавить кэширование на своей стороне, чтобы при появлении новых данных не перезапрашивать все сто страниц опять, т.к. они все прибиты к курсорам, которые непонятно при каких условиях устаревают и сервер тебе начинает возвращать страницы с курсорами, которые у тебя уже есть в кэше, но которые теперь соответствуют другим страницам с другими данными. А если ты хочешь эти данные сохранять в БД, чтобы потом пользоваться всей мощью выборки и фильтрации — то лучше забудь, я по крайней мере не смог решить ребус как затолкать в таблицу эти курсоры (три вида курсоров!), которые ещё нужно обновлять. Да, я всё понимаю, что отличаюсь от целевого пользователя, который дальше второй страницы не ходит и которому не нужен поиск и т.д. и т.п., но блин, товарищи, мы так скоро до мышей дойдём…
Возможно, что там есть еще какое-то приватное АПИ, которое они дают своим официальным клиентам и там все удобно для построения сторонних приложух.
А то апи, что вы юзаете заточено под их фронтенд
Я знаю, что у них есть приватное API на GraphQL (который сам по себе тоже неудобен, если ты пишешь не на Java/Python). И сдаётся мне, что его везде и используют, думаю оно отлично годится для примитивных клиентов («а большего мне и не надо» (с)). Сам же я использую вперемешку и официальное (старое kraken) и неофициальное, т.к. и то и другое по отдельности не дают исчерпывающей информации (хочешь превью по таймкоду — бери неофициальное, хочешь узнать статус видео recording/recorded — бери официальное). Я уже говорил, у Твича очень ужасное API, нет подробной документации с описанием возвращаемых полей (половина полей системные, опираться на них или нет?), более того одни и те же поля в разных методах API могут называются по разному, в helix вроде порядка больше, но оно неудобнее старого, т.к. там опять курсоры, а ещё требуется обязательно авторизация пользователя, которую в приложение нельзя встроить (молчу про то, что код заточенный под старое API на новое не перенести, только выкинуть). Также во всех этих вариациях API сохраняется одна и та же проблема, то что можно получить о видео за один запрос — заставляют делать за два (хочешь закладки с таймкодами в какие играли игры? сделай сто отдельных запросов). Я прекрасно понимаю, что половина всех проблем растёт из архитектуры бэкэнда, но складывается впечатление, что сегодня, мягко говоря, на разработчиков удобных функциональных клиентов под десктоп — всем наложить…

P.S.
Вообще я всё больше и больше убеждаюсь, что самое идеальное API и самая лучшая документация (да ещё с полигоном!) есть только у VK, у других по крайней мере ещё не встречал. Ну у iTunes тоже хорошо продуманное (по сравнению с Last.FM) и как раз заточенное под кэширование, хотя оно и немного урезанное по сравнению с приватным API для оф. приложения.
Хм… Странно. Я думал, что тут уже всё изобретено…
Реализация примерно такая:
— фильтр запоминается и ему присваивается идентификатор
— делается выборка по пользовательскому фильтру
— в отдельную табличку складываются id строк, можно вместе с порядковым номером в выборке, + идентификатор поискового запроса
— пагинация делается по этой табличке, остальные данные тянутся из основной таблицы

Вокруг этого, конечно, есть ещё куча оптимизаций, но этот способ решает главную проблему — медленную относительную выборку.
Главная проблема такого способа — устаревание поисковой выдачи. Но это общая проблема всех кеширующих ускорялок.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.