Разбивка на страницы затруднена, когда ваши рейтинги контента могут быстро меняться, и еще сложнее, когда эти оценки различаются для каждого пользователя.(Давайте рассмотрим бесконечную прокрутку как тип разбиения на страницы, где ссылки невидимы.) Есть две сложные проблемы: недавно добавленный контент вверху и переотмеченный контент.
Давайте забудем о недавно добавленном контенте и примем, что вам придется обновить страницу 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.Я никогда не увижу Слонов, и я увижу Коров дважды.
Последний подход к идентификации
Reddit использует другой подход.Вместо того, чтобы вычислять первую строку на основе размера страницы, клиент отслеживает идентификатор последнего элемента, который вы видели, например, закладку.Когда вы нажимаете «Далее», они начинают смотреть с этой закладки вперед:
SELECT * FROM animals
WHERE position > (
SELECT position FROM animals
WHERE id = :last_seen_id
)
ORDER BY position
LIMIT :page_size;
В некоторых случаях это работает лучше, чем страница / смещение.Но в нашем случае, Dogs, последний увиденный пост, увеличен до # 1.Таким образом, клиент отправляет ?last_seen_id=4
, и моя страница 2 - Летучие мыши, Альпаки, Слоны и Лисы.Я не пропустил ни одного животного, но дважды видел Летучих мышей и Альпак.
Состояние на стороне сервера
HackerNews (и наш сайт, прямо сейчас) решает это с серверомбоковые продолжения;они хранят для вас весь набор результатов (или хотя бы на несколько страниц заранее?), а ссылка "Дополнительно" ссылается на это продолжение.Когда я получаю страницу 2, я запрашиваю «страницу 2 моего исходного запроса».Он использует тот же расчет смещения / лимита, но, поскольку он не соответствует исходному запросу, мне просто все равно, что сейчас все изменилось.Я вижу слонов, лис, жирафов и лошадей.Ни дупс, ни пропущенных элементов.
Недостатком является то, что мы должны хранить большое количество состояний на сервере.В HN это хранится в ОЗУ, и на самом деле эти продолжения часто заканчиваются, прежде чем вы можете нажать кнопку «Дополнительно», заставляя вас вернуться обратно на страницу 1, чтобы найти действительную ссылку.В большинстве приложений вы можете хранить это в memcached или даже в самой базе данных (используя свою собственную таблицу, или в Oracle или PostgreSQL, используя переносимые курсоры).В зависимости от вашего приложения, производительность может снизиться;в PostgreSQL, по крайней мере, вам нужно найти способ снова подключиться к нужному соединению с базой данных, что требует большого количества залипающих состояний или некоторой умной внутренней маршрутизации.
Это единственные три возможных подхода?Если нет, то есть ли какие-нибудь компьютерные концепции, которые дадут мне сок Google, чтобы прочитать об этом?Есть ли способы аппроксимировать подход продолжения без сохранения всего набора результатов?В долгосрочной перспективе существуют сложные системы потоковой передачи событий / на определенный момент времени, в которых «результат, полученный на момент, когда я получил страницу 1», всегда может быть получен.Если не считать этого ...?