Pagination: вы все делаете неправильно!

| Категории: FAQ
Анна Аминева

Иллюстрация блокнота

###Pagination? Без проблем! SELECT … LIMIT X, Y Ведь так?

Аха! Не совсем!
Знаете ли, ваша логика разбивки на страницы не блещет стабильностью; а это важно в мире, когда мы постоянно используем клиентские приложения, ajax и бесконечную прокрутку.

ТЛ;ДР

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

Никогда не думайте, что сервер на самом деле знает чего хочет пользователь.

О чем вы вообще говорите?

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

SELECT * FROM comments ORDER BY date DESC LIMIT 0, 10

Просто. Это дает вам 10 самых последних комментариев. Вроде все нормально. Теперь, пользователь прокручивает страницу вниз и вы выполняете тот же запрос, но с LIMIT 10, 10 и вы возвращаете следующие 10 комментариев…

…иногда.

Учтите этот случай:

  • Пользователь А просматривает страницу с первыми 10 загруженными комментариями.
  • Пользователь B создает комментарий.
  • Пользователь А загружает следующую страницу комментариев.

Так, плохо. Теперь пользователь А видит дубликат комментария.
Добавился новый комментарий и в итоге последний опустился вниз и стал 11-ым и мы его опять получили. Сейчас ваша страница выглядит как минимум неаккуратно.

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

Постоянные результаты разбивки на страницы

Не беспокойтесь, такие проблемы достаточно легко решить. Вместо разбивки на страницы на основе изменчивого отступа (номера строки), найдите значение, которое остается постоянным в течении времени. В примере c комментариями таким параметром является дата публикации.

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

SELECT * FROM comments WHERE date < prevous_date LIMIT 10

Превосходно! Сейчас вы должны быть уверены, что пользователь всегда будет видеть следующий массив сообщений, которые следуют сразу за теми, на которые пользователь уже смотрит.

Иногда…

Базинга!

А что если у вас 2 комментария с одной датой публикации?
Разбивка на страницы сложна.

У вас две возможности:

  1. Если вы используете автоматически увеличивающийся идентификатор для своего контента, используйте его вместо даты; он гарантированно увеличивается во времени и ведет себя отлично для наших потребностей. В этом случае, по сути, вы идете по пути создания курсоров.

  2. Если же у вас нет автоматически увеличивающегося id ( например вы используете распределенное хранилище данных), тогда дела усложняются:

Вместо того, чтобы найти комментарии, которые находились перед датой, найдите комментарии, которые были созданы в то же время или после этой даты. Обязательно почистите дубликаты комментариев перед показом их пользователю.
или:

Передайте дополнительный информацию, которую вы можете использовать, чтобы игнорировать комментарии, которые пользователь уже видел ( может быть id возвращенных комментариев с их датой)

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

Что делать с данной проблемой?

Что-то напоминает данный часто-встречающийся элемент управления?

[<<] [<] [1] 2 [3] ... [>] [>>]

Как сразу перейти на определенную страницу если вы используете разбивку на основе постоянных отступов как дата в комментарии? Не используйте этот контрол.

Перед тем как я пойду дальше, ответьте на это: как часто ваши пользователи переходят на конкретную страницу кроме как на следующую/предыдущую/первую/последнюю?

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

Перейти в конец списка!

С этим миром разбивки страниц, как я позволю пользователю перейти в конец списка? Разбивка страниц сложна.

Итак, если вам требуется поддержка перехода в конец списка, есть одна возможность: поменяйте порядок сортировки и получите первые 10 элементов с другого направления. Не так плохо!

Подобным образом, как вы дадите пользователю вернуться на страницу назад? Ух! Разбивка страниц поистине сложна.
К счастью, вы можете адаптировать подход для получения последней страницы. В конце концов это обычный список, который вы отсортировали по-другому: возьмите дату элемента вверху списка, на который смотрит пользователь и получите страницу с элементами, которые идут до этого сообщения! Отлично!

Убедитесь, что данное решение подходит для вас. Иногда возникают ситуации когда вы не можете просто изменить порядок сортировки, и тогда вам придется найти собственные оригинальные подходы (бинарный поиск! фрагментация! кто знает?)

Изменяющаяся разбивка на страницы

Постоянная разбивка на страницы - это не всегда верное решение з��дачи. Рассмотрим сайты типа Reddit или Hacker News. Их разбивка на страницы по существу не стабильна: элементы отсортированы,с использованием волатильного алгоритма, в котором они часто двигаются вниз или вверх по списку в течении времени. Как же они с этим справляются? Никак.

Оба сайта часто демонстрируют насколько плохо могут выглядеть дубликаты элементов во время пролистывания страниц. Не все так плохо, потому что ни один из них не поддерживает бесконечную прокрутку (пока что?).

Несмотря на это, есть большое количество клиентских приложений, которые отображают Reddit и Hacker News в виде списка с бесконечной прокруткой; некоторые из них показывают дубликаты новостей, которые к томе же наезжают друг на друга. Все что нужно сделать - это устранить половину проблем, фильтруя дубликатов элементов, когда добавляется следующая страница.
Все же есть риск пропустить элемент, когда новости падают или поднимаются в рейтинге.

Решить эту проблему сложнее: наиболее простой путь - отдавать пользователю полный список элементов, до того момента куда он дошел каждый раз, но это сумасшествие! Возможно, лучше просто проигнорировать этот случай…