Pagination сложна, когда ваши рейтинги контента могут быстро меняться и даже сложнее, если эти рейтинги отличаются для каждого пользователя. (Позвольте рассматривать бесконечную прокрутку как тип разбивки на страницы, где ссылки невидимы.) Есть две трудные проблемы: недавно добавленный контент вверху и переименованный контент.
Забудьте о недавно добавленном контенте и примите, что вам нужно будет обновить страницу 1, чтобы увидеть ее. Пусть также притворяются, что мы делаем чистую ORDER BY position
; если вы заказываете что-то еще, вам, возможно, придется использовать функции окна. На наших страницах есть 4 ряда животных на страницу. Они начинаются:
+----+----------+-----------+
| id | position^| animal |
+----+----------+-----------+
| 1 | 1 | Alpacas |
| 2 | 2 | Bats |
| 3 | 3 | Cows |
| 4 | 4 | Dogs |
| 5 | 5 | Elephants |
| 6 | 6 | Foxes |
| 7 | 7 | Giraffes |
| 8 | 8 | Horses |
+----+----------+-----------+
После того, как мы выберем страницу 1, и до того, как мы выберем страницу 2, множество предметов перемещается. Теперь БД:
+----+----------+-----------+
| id | position^| animal |
+----+----------+-----------+
| 4 | 1 | Dogs |
| 2 | 2 | Bats |
| 1 | 3 | Alpacas |
| 5 | 4 | Elephants |
| 6 | 5 | Foxes |
| 7 | 6 | Giraffes |
| 3 | 7 | Cows |
| 8 | 8 | Horses |
+----+----------+-----------+
Существует три общих подхода:
Смещение/предельный подход
Это типичный наивный подход; в Rails, это как will_paginate и Kaminari. Если я хочу получить страницу 2, я сделаю
SELECT * FROM animals
ORDER BY animals.position
OFFSET ((:page_num - 1) * :page_size)
LIMIT :page_size;
который получает строки 5-8. Я никогда не увижу Слонов, и я увижу коровы дважды.
Последний увиденный ID-подход
Reddit использует другой подход. Вместо вычисления первой строки на основе размера страницы клиент отслеживает идентификатор последнего элемента, который вы видели, как закладка. Когда вы нажмете "next", они начнут искать из этой закладки:
SELECT * FROM animals
WHERE position > (
SELECT position FROM animals
WHERE id = :last_seen_id
)
ORDER BY position
LIMIT :page_size;
В некоторых случаях это работает лучше, чем страница/смещение. Но в нашем случае "Собаки", последний увиденный пост, увеличены до 1. Таким образом, клиент отправляет ?last_seen_id=4
, а моя страница 2 - летучие мыши, альпаки, слоны и лисы. Я не пропустил животных, но дважды увидел Летучих мышей и Альпака.
Состояние на стороне сервера
HackerNews (и наш сайт, прямо сейчас) решает это с продолжением на стороне сервера; они сохраняют весь набор результатов для вас (или, по крайней мере, несколько страниц заранее?), а ссылки ссылок "Больше" - это продолжение. Когда я забираю страницу 2, я прошу "страницу 2 моего исходного запроса". Он использует один и тот же расчет смещения/лимита, но поскольку он против исходного запроса, мне просто все равно, что теперь все перемещено. Я вижу слонов, лисиц, жирафов и лошадей. Нет дубликатов, пропущенных предметов.
Недостатком является то, что мы должны хранить много состояний на сервере. На HN, хранящиеся в ОЗУ, и в действительности эти продолжения часто заканчиваются, прежде чем вы сможете нажать кнопку "Дополнительно", заставив вас вернуться к странице 1, чтобы найти правильную ссылку. В большинстве приложений вы можете хранить это в memcached или даже в самой базе данных (используя собственную таблицу или Oracle или PostgreSQL, используя удерживаемые курсоры). В зависимости от вашего приложения может произойти удар производительности; в PostgreSQL, по крайней мере, вам нужно найти способ снова подключиться к правильному соединению с базой данных, что требует много липкого состояния или некоторой умной внутренней маршрутизации.
Это только три возможных подхода? Если нет, существуют ли концепции компьютерной науки, которые дадут мне сок Google, чтобы прочитать об этом? Существуют ли способы аппроксимации подхода продолжения без сохранения всего набора результатов? В долгосрочной перспективе существуют сложные системы потоковой передачи событий/времени в момент, когда "результат, установленный на момент, когда я набрал страницу 1", навсегда выводится. Коротко это...?