Pull to refresh

Comments 112

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

Т.е. пользователь, листая страницы, новые сообщения не увидит?
Он так и так их не увидит, ибо они добавляются на первую страницу. Но да, чтобы увидеть новые сообщения нужно будет не просто перейти на первую страницу или промотать виртуальный скролл на начало, а явно попросить освежить выдачу.
Кажется, если пользователю важны обновления, то он будет просто постоянно тыкать кнопку «обновить», и вся схема деградирует до самой обычной пагинации со всеми её недостатками и добавленным ещё одним — раздражением от того, что приложение автоматически не обновляет данные. Потому что в общем случае проблемы пагинации не решить, они должны решаться в рамках прикладной области, для которой мы проектируем отображение данных. Где-то можно пренебречь пропущенными или задвоенными сообщениями, где-то нужно делить информацию по более вложенным разделам, где-то оформлять подписку пользователя на уведомления об изменении просматриваемых данных и т.п. Серебряной пули, увы, не отлить.
мало того, на ресурсе с частыми обновлениями, такое поведение пользователя наиболее типично

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

новости, вобще, больная тема.
в первую очередь почти всегда неверный формат подачи
Ну не знаю, мы выдаем в ленту новости за текущий день. Собственно «страницей» и является список новостей за сутки
Это кстати хорошее поведение, меня вот бесит когда переходя на следующую страницу я вижу большую часть с только что просмотренной, потому что в начало уже насыпалось новинок…
Хорошо сделано в некоторых случаях, когда результаты фиксированные, о есть уведомление о новом.

Ещё из подгорающего — когда кнопки работы с выдачей работают с выдачей актуальной на момент нажатия кнопки а не той что отображается.
Ситуации «Посмотрел уведомления, нажал очистить всё — не увидел свежее важное уведомление» встречаются довольно часто к сожалению :(
И вот такое поведение мне кажется не очевидным на меняющихся данных, сюда просится отдельный механизм уведомлений (о уже чем упоминали в соседних комментариях), который помимо того, что нетривиальный, еще неизвестно как повлияет на производительность. А если же данные меняются нечасто, то кэширование на стороне сервера может решить проблему (но тут надо предметную область смотреть).
Можно новые сообщения хранить в еще одной таблице и выводить их как минусовые страницы:
-2 -1 1 2 3 4…
это как это так? для каждого пользователя по таблице? Это решается простой передачей айдишника последнего просмотренного объекта. и тогда и нет дубликатов и ничего не теряется
Здравствуйте, Дмитрий, если вы забываете на чём оборвалось последнее предложение предыдущей страницы в процессе перелистывания, то у вас серьёзные проблемы с памятью или концентрацией.
С чего вы взяли, что я что-то забываю?
Вы же сами с рассказа об этом начали свой пост:
Здравствуйте, меня зовут Дмитрий Карловский и я… не люблю читать книги, потому что пока перелистываешь страницу, ты вырываешься из увлекательного повествования. И стоит чуть замешкаться, как ты забываешь на чём оборвалось последнее предложение предыдущей страницы, и приходится листать обратно, чтобы перечитать его.
Уже забыл наверное.
Пациент пришёл к доктору:
— Доктор, у меня пробелы в памяти.
— Минуточку, пожалуйста.
… проходит 15 секунд…
— Так, что у вас там говорите за пробелы?
— Какие пробелы?
— доктор, у меня бывают провалы в памяти.
— и как часто они у вас бывают?
— кто?!
— провалы!
— какие провалы?!?!?!
Точно, спасибо! Этот вариант я и забыл, пришлось додумывать :)
Таким образом мы получаем проблемы двух типов c точки зрения пользователя:
  1. На следующей станице могут вновь показаться сообщения, что уже были на предыдущей.
  2. Некоторые сообщения пользователь вообще не увидит, так как они успели переехать с 6 страницы на 5 ровно между переходом пользователя с 5 на 6.

В качестве решения этих проблем можно запомнить дату первоначального запроса (initial_request_date). При переходе от страницы к странице выборки передавать дату первоначального запроса.


SELECT FROM Message WHERE text LICENE "паджинация" AND created <= initial_request_date ORDER BY created DESC SKIP 5 * 10 LIMIT 10

Изначально проблема в том, что читаются большие объемы из таблички, в которую часто что-то пишется. Можно разбить источник данных на две неравномерных части: маленькая "недавняя", большая "история". Писать постоянно в маленькую и периодически переписывать все из маленькой в большую.

В качестве решения этих проблем можно запомнить дату первоначального запроса (initial_request_date).

Так вы защититесь лишь от появления новых сообщений, но не от удаления старых или перемещения существующих в выборке. Кроме того, появляться в выборке могут и старые сообщения вследствии их редактирования.


Изначально проблема в том, что читаются большие объемы из таблички, в которую часто что-то пишется.

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

Что значит "перемещения существующих в выборке"? У них поле created меняется?
Удаленные сообщения из таблички можно не удалять, а помечать удаленными. Если настолько важна пажинация без сдвигов, то клиенту можно возвращать плашку "тут было сообщение, но его удалили" вместо текста.

Что значит "перемещения существующих в выборке"? У них поле created меняется?

Ну поменял я на changed, теперь они будут скакать туда-сюда как угорелые. Попробуйте не к описанному примеру цепляться, а разглядеть мысль, которую он иллюстрирует.


Если настолько важна пажинация без сдвигов, то клиенту можно возвращать плашку "тут было сообщение, но его удалили" вместо текста.

Прилетело НЛО и поудаляло сообщения со словом "антипаттерн". После чего пришёл пользователь и читает 10 страниц сообщений "Здесь было НЛО".

Ну поменял я на changed, теперь они будут скакать туда-сюда как угорелые.

Зачем менять на changed, если можно просто добавить поле changed, а выводить все с той же сортировкой по created?
Нужно ли вообще отслеживать дату последнего изменения?


Прилетело НЛО и поудаляло сообщения со словом "антипаттерн". После чего пришёл пользователь и читает 10 страниц сообщений "Здесь было НЛО".

Зато сообщения не прыгают со страницы на страницу. Тут либо мы показываем следы НЛО, либо пользователь может иногда пропускать сообщения при переключении со страницы на страницу.


Теперь клиент может нарисовать хоть паджинатор, хоть виртуальный скролл,

Как, кстати, будет вести себя пажинатор по списку идентификаторов в вашем решении, если сообщения в середине будут удаляться?
Допустим выводим по 5 элементов на странице. Сценарий такой:
1) Получили список 1-20
2) Для первой страницы запросили 1,2,3,4,5
3) НЛО удалило сообщения 6, 11-15
4) Запросили 6,7,8,9,10 для второй страницы
5) Запросили 11,12,13,14,15 для третьей страницы
6) Запросили 16,17,18,19,20 для четвертой страницы


Есть еще вариант — показывать страницы "внахлест". Т.е. на первой сообщения с 1 по 10. На второй с 8 по 18. На третьей с 16 по 24.

Да, если после поискового запроса сообщение было удалено, то вместо него будет показываться «НЛО было здесь».
UFO landed and left these words here

Звучит интересно. Где можно почитать этот рассказ?

UFO landed and left these words here

Насколько далеко Вы можете зайти?

Думаю не дальше половины длины окружности Земли.

UFO landed and left these words here
Проблема, возможно, в том, что никто не обращает внимание на слово «чуть» перед «замешкавшимся»: Ваше «чуть» может сильно отличаться от моего, да и от случая к случаю «чуть» может меняться в широких пределах. Если Вы имели в виду что-то вроде «отвлёкся на полчаса, чтобы масло поменять, вернулся — и уже не помню, что там было на предыдущей странице „Так говорил Заратустра“ », то в общем всё вполне логично.
Схватки обрушивались на нее, будто горы, скала за скалою, и она словно звала нас из длинного, гулкого туннеля своих мук, и я сидел, скрестив ноги, разрываемый на части ее страданием, и беззвучное «тик-так» звучало в моем мозгу, а в хижине тройняшки поливали водой тело Парвати, чтобы оно не иссохло, ибо воды отходили потоками; разжимали ей зубы и вставляли палочку, чтобы несчастная не откусила себе язык; надавливали на веки, стараясь опустить их, потому что страшно было смотреть, как глаза Парвати вылезают из орбит – девушки боялись, что глазные яблоки выкатятся на пол и выпачкаются в грязи; и настал двенадцатый день, и я уже был ни жив ни мертв от голода, а где-то в городе, в другом месте, верховный суд уведомил госпожу Ганди, что она может не подавать в отставку, пока не будет рассмотрена ее апелляция, но при этом не должна голосовать в Лок Сабха и получать жалованье, и когда премьер-министр Индира, воодушевившись этой частичной победой, принялась честить своих противников в выражениях, каким позавидовали бы и рыбачки коли, роды моей Парвати достигли такой точки, когда, несмотря на крайнее изнеможение, она нашла в себе силы извергнуть из обескровленных уст целую литанию грязных, воняющих клоакой ругательств; смрад непристойной брани шибанул нам в ноздри, вывернул нас наизнанку; три акробатки стремглав вылетели из хижины, крича, что Парвати так высохла, так побледнела: еще немножко, и станет совсем прозрачная; и что она всенепременно умрет, если ребенок не выйдет тотчас же, прямо сейчас; а в ушах у меня звучало «тик-так», громко звучало «тик-так», и я наконец убедился – да, скоро, скоро-скоро-скоро, и когда тройняшки вернулись к ее постели вечером тринадцатого дня, они завопили – да, да, она стала тужиться; ну давай, Парвати, тужься-тужься-тужься, и пока Парвати тужилась в своей лачуге, Дж.П. Нараян и Морарджи Десаи тоже подстрекали Индиру Ганди; пока тройняшки визжали – тужься-тужься-тужься – лидеры «Джанаты Морчи» призывали полицию и армию не подчиняться приказам ограниченного в правах премьер-министра и в каком-то смысле заставляли госпожу Ганди тужиться тоже, и и когда тьма сгустилась к полуночному часу, ибо разве может что-то случиться в какой-то другой час, тройняшки заверещали – он идет-идет-идет – а где-то там, далеко, премьер-министр Индира рожала свое дитя… в трущобах, в хижине, подле которой я сидел, полуживой от голода, мой сын шел-шел-шел – вот уже показалась головка – заверещали тройняшки, а в это время отряды особой резервной полиции арестовывали лидеров «Джанаты Морчи», включая таких невозможно древних, почти мифологических персонажей, как Морарджи Десаи и Дж.П. Нараян; тужься-тужъся-тужься – и в самом сердце этой ужасной полуночи, когда «тик-так» гремело у меня в ушах, родился ребенок, в самом деле первый сорт, настоящий богатырь, выскочил в конце концов так легко, что невозможно было понять, из-за чего разгорелся весь сыр-бор.
Пагинация — это один из тех классических подходов, про которые можно сказать примерно следующее.
Да, это неидеально, да, есть минусы. Но альтернативы, как правило, ещё хуже и непонятнее (кроме частных случаев).
И что же хуже и не понятнее в предложенном решении?
Да, простите мне мою дремучесть, пожалуйста, но после первого же SQL-оператора в этой статье (на мой взгляд, интересной, всяко интереснее всякой мути про реакты&ангуляры) — у меня возник вопрос: что это за БД и почему она отдаёт результаты запросов в таком странном формате??

А что насчёт серверных курсоров? Тот же elastic умеет присваивать Id каждому запросу и потом работать в рамках его изначальных результатов.

Это и есть вариант с хранением снепшота на сервере. В этом случае клиенту надо передавать идентификатор этого снепшота.
Обычно тут не комментирую, но тема задела. Уважаемый автор, плохо что ты не любишь читать книг. Тогда бы ты мог знать, что не в одном русском словаре ты не сможешь найти слова: «паджинация», «снепшот». Ну а теперь по теме: Реализация постраничной навигации напрямую зависит от структуры базы данных и самого движка БД. Так что, когда пишешь статью о БД, указывай структуру и движок.
Уважаемый автор, плохо что ты

Вы либо уважайте, либо тыкайте.


читать книг

читать книги


не в одном

ни в одном


ты не сможешь найти слова: «паджинация», «снепшот»

Однако все меня поняли, вот парадокс.


Реализация постраничной навигации напрямую зависит от структуры базы данных и самого движка БД.

Например?

Как быстро будет работать твоя реализация с 3 миллионами записей, содержащих JSON, либо TEXT поле, 10 фильтров и хотя бы 100rps?
Моя реализация чего? Паджинации? Так статья как раз о том, что её не надо делать.
Ради Вселенской Справедливости:
Не читать книг, не попить воды, не надеть обуви… не включать мозгов.

Или единственно правильно будет: не попить воду, не надеть обувь, не включать мозг?
Идеально было бы:

1) Один раз произвести поиск и где-то запомнить его результаты в виде снепшота на определённый момент времени.
2) Быстро выбирать данные мелкими порциями по мере необходимости.

На сколько я понял, то автор «изобрел» кеширование результатов выборки?
Данная статья просветительская, а не инновационная. Данный подход «изобретён» не мной. Но, к сожалению, используется он куда реже чем следовало бы.
единственный скриншот — пример пагинации гугла…

но проблема в том, что пример гугла в очень редких случаях иллюстрирует описанную автором задачу, хабр лучше показывает случай с пагинацией: переходишь на страницу i+1, и первой статьей отображается последняя статья с i-й страницы, в случае, когда появилась 1 новая статься в момент перехода на страницу i+1…

для меня это вообще не проблема, но если пользователей моего ресурса это бы напрягало, более того, было бы ясно о возможности подобного случая, одно из странных решений: фиксировать дату начала постраничной навигации конкретного пользователя, и игнорировать появление новых записей (статей), но решение технически трудоемкого и громоздкое, хоть и возможное… другой вариант — опять привязываясь к конкретному пользователю, при явном переходе на 2-ю страницу — фиксировать первый ИД записи на 2й страницы, и отталкиваться от него при выводе данных на последующих страницах, что по своему объему не меньше первого варианта…

и третий вариант, который приходит на ум, это то что использовал ВК (не знаю как сейчас, полгода не заходил), и вроде как фейсбук в свое время (опять же очень давно там не был): подгружать следующие N-записей относительно последней, так мы точно загрузим заранее фиксированное количество записей, и можем в самом начале добавлять новые, по мере их появления… но такой вариант имеет проблемы: пока не просмотришь все записи, к подвалу сайта не добраться, и когда записей сотни-тысячи — просмотр страницы превращается в кошмар…

Вообще, проблемы, описаны автором, возможны на ресурсе с приличной активностью, где новые материалы появляются по несколько штук в каждую секунду… мне, например, такие ресурсы неизвестны, если у кого есть конкретный пример — пожалуйста напишите, интересно посмотреть как у них интерфейс устроен… а коль и другие таких мест не знают, тем более автор: можно будет считать — проблемы высосана из пальца
единственный скриншот — пример пагинации гугла…

Там не только Гугл, присмотритесь.


фиксировать дату начала постраничной навигации конкретного пользователя, и игнорировать появление новых записей (статей)

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


где новые материалы появляются по несколько штук в каждую секунду…

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

Данная статья ввела меня в когнитивный диссонанс того как вы решаете проблему с пагинацией.
Решение вашей проблемы можно осуществить всего лишь одним SQL запросом:
SELECT * FROM <b>наша_таблица</b> LIMIT <b>x</b>, n

где SQL-запрос вернёт записи, начиная с x-го номера включительно в количестве n штук.

Объясняю.
Допустим пагинация будет передаваться в php и на каждом элементе пагинации вешается ссылка типа "/page=n", где n означает номер следующей страницы и обрабатываете в php скрипте так:
$number_of_showing_items = 10;
$page = 0;
if(isset($get['page']))
          $page = $get['page'] * number_of_showing_items;
$rows = $db -> select('SELECT * FROM `our_table` LIMIT '.$page.', '.$number_of_showing_items);
for($i = 0; $i <= sizeof($rows) - 1; $i++)
{
           echo $rows[$i]['post_author'];
           echo $rows[$i]['post_header'];
           echo $rows[$i]['post_content'];
}


Если надо добавить какие либо условия, то передаем в запросе эти условия и в итоге SQL запрос может выглядеть вот так:
$rows = $db -> select('SELECT * FROM `our_table` WHERE наш_столбец="наш_текст" LIMIT '.$page.', '.$number_of_showing_items);


Это самый простой и эффективный на мой взгляд метод решения проблемы. А то что вы написали в статье, извините меня, полный бред.
Помните как в вк при пролистывании новостей натыкались на дубликаты? вот и минус вашего простого способа. Если постоянно появляются новые записи, то мы утекая вперед будем ловить дубли или если исключить дубли на клиенте — ловить меньший поток данных чем требовалось бы предоставить пользователю. Тем не менее, я такую проблему (динамично наполняемая контентом стена и прокрутка) реализовал гораздо проще. Я запоминал максимальный ID который увидел пользователь и все, проблема решена. Мы никогда не получим на другой странице повторяющийся результат. Сочетается с LIMIT. или можно запоминать какие посты уже показали и сразу их отсекать.
Вы всегда читаете статьи вверх ногами и не дочитав до начала бросаетесь писать гневные комментарии?
Есть два других решения проблемы того что данные перескакивают со страницы на страницу при постраничной навигации. У этих решений тоже есть свои проблемы, надо искать меньшее зло в конкретной задаче.
— Обратный порядок странцы. Пользователь смотрит не с первой, а с наибольшей по номеру страницы. На первой странице самые «старые» данные. Проблема: на наибольшая по номеру страница может быть заполнена не полностью
— Относительная паджинация. Если у данных есть уникальный монотонно возрастающий ключ (а id, как правило, такой и есть), то можно передавать не номер страницы, а последний просмотренный id, от которого «листаем» дальше.
Проблема: на наибольшая по номеру страница может быть заполнена не полностью
Или переполненная, если страницу с недостаточным количеством записей объеденить с предыдущей.
Есть два других решения проблемы того что данные перескакивают со страницы на страницу

Я описал несколько проблем паджинации, почему вы пытаетесь решать только одну?


Пользователь смотрит не с первой, а с наибольшей по номеру страницы.

Это не решение. Это костыль для одного очень частного случая: когда поле, по которому идёт сортировка никогда не меняется, записи никогда не удаляются, а новые элементы в выборке появляются исключительно с одного конца.


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

Тот же костыль, но ещё и сортировка исключительно по id.

Я описал несколько проблем паджинации, почему вы пытаетесь решать только одну?

Потому что мне так хочется. По-вашему я должен решать все?

Оба, предложенных мной решения, не костыли, а именно решения со своими плюсами и минусами, о чем я, собственно, и написал.
Если не рассматривать «сферическую паджинацию в ваккуме», то создание снепшотов умеет далеко не каждая БД, а для тех, кто умеет, это довольно дорогая операция.

Даже постраничная паджинация имеет проблемы с производительностью по сравнению с относительной паджинацией. Потому что на больших данных OFFSET та еще жесть для БД. Зпрос для относительной паджинации почти вчегда будет быстрее запроса постраничной.

Так что, еще раз скажу, надо выбирать меньшее из зол исходя из конкретной задачи.
По-вашему я должен решать все?

Реализация без паджинации, но с разделением поиска и выборки решает их все. Было бы не плохо предлагать альтернативы, которые как минимум не хуже.


Оба, предложенных мной решения, не костыли, а именно решения

Если решаемая проблема остаётся, то это не решение.


Если не рассматривать «сферическую паджинацию в ваккуме», то создание снепшотов умеет далеко не каждая БД, а для тех, кто умеет, это довольно дорогая операция.

Снепшот — это просто список идентификаторов. В статье предлагается его сразу выгружать клиенту. Никакая поддержка со стороны СУБД тут не нужна.


Даже постраничная паджинация имеет проблемы с производительностью по сравнению с относительной паджинацией.

Об этом написано в статье.


Зпрос для относительной паджинации почти вчегда будет быстрее запроса постраничной.

Она применима только при сортировке по уникальному иммутабельному полю. Обычно это только id строки.

Реализация без паджинации, но с разделением поиска и выборки решает их все. Было бы не плохо предлагать альтернативы, которые как минимум не хуже.

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


Относительная паджинация, предложенная maximw, решает как проблему дублей, так и проблему удаленных записей.

оно не решает остальные поставленные вами проблемы

Какие такие проблемы оно не решает?

Это такой тонкий троллинг?


Вам же уже написали:


  • Метод не будет работать если пользователю важны последние записи.
  • При удалении записей которые есть в снепшоте вы получите НЛО или дырки. Если удаленных материалов много, то вы можете получить вообще дырку от бублика пустую страницу.
  • Снепшот, это по сути кэширование, со всеми вытекающими последствиями.
  • Вы ограничиваете результат до 1000 записей, но это не всегда корректно. Это корректно для поисковой выдаче, но не корректно для каталогов и архивов. Вспоминаем Бездну.

А вы принципиально не читаете мои ответы на те комментарии, ссылки на которые приводите? Что ж, я повторю:


Метод не будет работать если пользователю важны последние записи.

Когда пользователь перемещается между страницами его явно интересуют не последние записи, а "дай мне следующие 20 элементов". Самому пользователю ваша паджинация даром не нужна. Она — лишь техническая деталь, позволяющая показать пользователю большой список за разумное время. И пользователя интересует именно этот список, а не какая-то страница из него.


При удалении записей которые есть в снепшоте вы получите НЛО или дырки.

Я не вижу проблемы в том, что мы честно пишем, что "на момент запроса тут было сообщение, но к тому моменту как вы до него добрались оно уже было удалено, поэтому мы не можем показать вам его содержимое". И это гораздо лучше, чем "мы можем случайно не показать вам какие-то сообщение, а некоторые показать по нескольку раз, но мы вам об этом не сообщим" и неприятных скачков виртуального скролла из-за изменения состава выдачи.


Снепшот, это по сути кэширование, со всеми вытекающими последствиями.

Ошибка того же порядка, что и "человек произошёл от шимпанзе". У человека и шимпанзе общий предок, но частным случаем друг друга никто из них не является. Так вот, "снепшот" и "кеш" — это частные случаи "производных данных". Снепшот создаётся клиентом целенаправленно, чтобы с ним потом работать независимо от изменений в исходных данных. Вы можете создать сколько угодно снепшотов для одного и того же поискового запроса. Кеш же работает прозрачно между клиентом и исходными данными, не создавая отдельной сущности с которой можно оперировать. В то время как снепшот является иммутабельным со всеми вытекающими. С кешом у вас есть вечная проблема по его инвалидации. Пока он не инвалидировался вы получаете устаревшие данные. А как только инвалидировался — вы получаете изменение выборки со всеми описанными в статье проблемами. Так что нет, по сути это два совершенно различных механизма.


Однако, справедливости ради, стоит отметить, что механизм кеширования можно приспособить, чтобы он создавал снепшоты. Для этого клиенту при первом запросе нужно сформировать query_id и передавать его вместе с запросом к каждой странице. А серверу формировать ключ для кеша на основе: query_id, id сессии и параметров запроса. При этом нужно задать заведомо большое время жизни кеша, например, в неделю.


Вы ограничиваете результат до 1000 записей, но это не всегда корректно.

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


А ограничение на число элементов есть всегда. Я уже приводил в пример Гугл с его паджинатором. Да что там далеко ходить: https://habr.com/all/page101/

Когда пользователь перемещается между страницами его явно интересуют не последние записи

И вернувшись на первую страницу он не увидит новых записей.


на момент запроса тут было сообщение, но к тому моменту как вы до него добрались оно уже было удалено, поэтому мы не можем показать вам его содержимое

Вот вы не видите проблемы в НЛО, но видите проблему в дублях. Другие же разработчики и пользователи не видят проблемы в дублях, а вот НЛО раздражает.


Снепшот создаётся клиентом целенаправленно, чтобы с ним потом работать независимо от изменений в исходных данных.

Из вашей статьи я делаю вывод, что вы запрашиваете с сервера список id записей соответствующих поисковому запросу и кешируете результат на клиенте. Далее, реализуете пагинацию на клиенте по списку закешированных записей.
Вы список закешированных записей называете снепшотом, но механика явно кеширующая.
Так что, нет. В вашем случае кеш и снепшот это синонимы.


А ограничение на число элементов есть всегда

И снова нет. Ограничений почти никогда нет. Вам уже привели 2 примера и можно привести ещё миллион.
Да, некоторые искусственно ограничивают число элементом в силу своих каких-то технических ограничений и Google Search тому яркий пример. Выдавать в поисковой выдаче результаты до которых пользователей не долистает для них скорей всего экономически не выгодно. А вот для каталогов (1, 2, 3) это не корректно. Тоже с архивом новостей. А обрезание комментариев на форуме или сообщений в личке или списка конкурсантов в онлайн конкурсе или ленты в соцсетях это вообще нарушение целостности данных и пользователи вас за это по головке не погладят.


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

И вернувшись на первую страницу он не увидит новых записей.

А с чего бы ему видеть там не те записи, что были там буквально пару минут назад? "перейти на первую страницу выдачи" и "повторить поиск" — это две различные операции. Чтобы лучше понять разницу — представьте себе виртуальный скролл. Должен ли поиск повторяться только лишь потому, что пользователь мотнул на начало?


Другие же разработчики и пользователи не видят проблемы в дублях,

Ну конечно, пользователь перешёл с 5 на 6 страницу, а мы ему показываем ровно те же самые данные, только лишь потому, что между переходами данных в начало выборки добавилось аж на целую страницу. Совсем никакой проблемы, да.


а вот НЛО раздражает.

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


Из вашей статьи я делаю вывод, что вы запрашиваете с сервера список id записей соответствующих поисковому запросу и кешируете результат на клиенте.

Не надо "делать выводы", если там чётко написано, что выгружается список идентификаторов, который сохраняется на клиенте. Вы вот зачем со мной спорите? Я вам вполне конкретно описал чем снепшот отличается от кеша и почему некорректно называть кешом любые производные данные. Можете сколько угодно делать вид, что разницы нет. Но вы правда верите, что и я перестану её видеть только лишь от того, что вы 10 раз назовёте снепшот кешом?


Ограничений почти никогда нет.

Приведите ссылку на исследование.


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

А для кого выгодно выдавать результаты, которых пользователь не увидит?


А вот для каталогов (1, 2, 3) это не корректно.

Давайте пройдёмся по ссылкам:


Амазон. Максимальная страница — 400, что даёт не более 5000 элементов в выдаче. Список идентификаторов весил бы 64КБ максимум. Вроде бы много, пока не узнаешь, что один только html каждой страницы Амазона весит более 500КБ.


Кинопоиск. Тут похоже действительно нет ограничения. Однако, вы можете представить себе пользователя, который бы прощёлкал 1148 страниц? На всякий случай — возможность перейти на последнюю страницу не является обоснованием, так как гораздо лучше это решается инвертированием сортировки. Ну и посмотрите сколько стоит переход между страницами. Формирование каждой страницы — полторы секунды. Навигация по снепшоту была бы почти мгновенной. Какой мог бы получиться размер снепшота? Ну, не более 360КБ. Много. Но не так много как текущий html каждой страницы в 400КБ.


Тоже с архивом новостей.

Там просто фильтрация по дате и всё.


А обрезание комментариев на форуме или сообщений в личке или списка конкурсантов в онлайн конкурсе или ленты в соцсетях это вообще нарушение целостности данных и пользователи вас за это по головке не погладят.

Во всех этих случаях пользователю нужен поиск, а не паджинатор на сотни страниц. К целостности данных это отношения не имеет.


Но это конечно ваше право иметь собственное мнение и не принимать истину.

Не путайте, пожалуйста, истину с мнением большинства.

Считаю, что варианты типа " возможность перейти на последнюю страницу не является обоснованием, так как гораздо лучше это решается инвертированием сортировки" или «Во всех этих случаях пользователю нужен поиск, а не паджинатор на сотни страниц» в глубине обсуждения типа «какой должен быть паджинатор по умолчанию» не уместен. Исходить надо из того, что паджинатор уприори должен быть к моменту когда мы задаемся вопросом «а какой7»
Есть предложение, что вы не можете решить те проблемы с тормозами в mol и решили пойти административным путем — объяснив, почему необходимо ограничить количество выдаваемых данных)

Сомнительно, чтобы пользователю хватило терпения домотать хотя бы до 100 страницы, не воспользовавшись фильтрацией и сортировкой.
Зависит от темы и пользователя. Не все пользователи ведут себя так, как вы хотите. Некоторые, к примеру, сразу берут скролл и приблизительно проматывают длинное полотно, если вы понимаете о чем я.

Ну вот к примеру я пришел на какой-либо абстрактный ДжойРеактор впервые в жизни, мне он очень понравился и я начинаю поглощать контент быстрее, чем он появляется. Лег вечерком, поржал с 20 страничек старых мемчиков, выключил телефон, лег спать, а на следующую продолжил с той же страницы.

Вполне разумным будет задать жёсткий лимит на размер выдачи в, допустим, 1000 элементов.
Нет, совсем не разумным)

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

Я смотрю высокая скорость работы $mol вам спать спокойно не даёт?


Некоторые, к примеру, сразу берут скролл и приблизительно проматывают длинное полотно, если вы понимаете о чем я.

Ага, а потом наигранно удивляются, чего это браузер не может отрендерить всё это полотно мгновенно.


Лег вечерком, поржал с 20 страничек старых мемчиков, выключил телефон, лег спать, а на следующую продолжил с той же страницы.

Только за ночь напостили ещё контента, поэтому с 20 по 30 страницу идут одни бояны. Поэтому ленты новостей — это совершенно другая опера. Правильно реализуется двумя ручками:


  1. Дай топ свежих новостей.
  2. Пометь такие-то новости как прочитанные.

Но об этом уже в другой статье.


Нет, совсем не разумным)

Обоснуйте. Даже если на один элемент пользователь потратит секунду, то на 1000 элементов ему потребуется пол часа, что запредельно много.


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

Попробуйте в Гугле дойти до 100 страницы.

Попробуйте в Гугле дойти до 100 страницы.
Ну, может, в гугле и нету в этом смысла, но это не значит, что нигде нет.

Только за ночь напостили ещё контента, поэтому с 20 по 30 страницу идут одни бояны.
Нет, оно работает совершенно не так. Тот сценарий, что я описал вполне реален.
Популярный антипаттерн — выпиливать фичи, по бредовым поводам или «эту фичу юзает 1%»

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

1. Увидеть не очень хорошую реализацию рест апи
2. Придумать решение проблемы совсем не там, где она возникает
3. Накатать эпатажную статью
4. Профит получить гору справедливой критики

Где же она по вашему возникает?

Почему не делать по since_id или просто по since created_at?
Сообщение не содержало слов «паджинация», а потом пользователь его отредактировал и оно стало содержать. Чем вам тут поможет since?
Ищете вы «паджинация», а потом пользователь отредактировал сообщение, и оно перестало содержать это слово. Я не вижу проблемы, если честно.

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

Я правильно понимаю, что вы этот прекрасный антипаттерн рассматриваете только в контексте потребителей-людей, и чисто программные клиенты вас не интересуют?

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

ваше предположение "можно отдать всего не больше 1000 элементов" некорректно

1000 — разумное ограничение для пользователя. Для разных запросов и клиентов вы вольны задавать любые ограничения.


Что делать будем?

Формировать снепшот и стримить данные из него любым удобным способом, ибо снепшот иммутабелен.

Формировать снепшот

На сервере? Ресурсов-то хватит, удерживать снепшоты для всех клиентов?

… и, кстати, как для клиента это будет отличаться от обычного пейджинга?

Он будет получать данные, которые были на момент первого запроса в случае полного снэпшота, или, хотя бы, только те пункты списка, которіе біли на момент первого запроса если снепшот только id запоминает.

Скажем, на Хабре в пагинации списка постов я бы предпочёл снепшоты или ещё какие курсоры, запоминающие список постов по id.
На сервере? Ресурсов-то хватит, удерживать снепшоты для всех клиентов?

Придётся найти.


как для клиента это будет отличаться от обычного пейджинга?

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


  1. Сформируй снепшот по параметрам
  2. Дай такую-то страницу такого-то снепшота.
Придётся найти.

… или не придется, если не делать снепшоты.


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

Если стримингом, то это ничем не отличается от обычного "отдай мне все данные" (разумные БД все равно там делают consistent-read-транзакцию), со всеми вытекающими недостатками.


Даже если делаете пейджингом, то у вас будет два типа запросов:

Очевидно, нет. Можно просто отдавать в каждой странице уникальный continuation id (потому что обратно программным клиентам ходить не надо). Другое дело, что это все равно требует хранения на сервере, что возвращает нас к вопросу "ресурсы-то где брать".

Зависит от количества клиентов и сценариев их работы. Оптимального по всем метрикам решения, годящегося для всех случаев использования, просто нет, хотя бы потому что требования в разных случаях могут быть противоположными, например в одних случаях клиент при дополнительных запросах должен получать данные, которые были на момент первого запроса, а в других — с учётом всех обновлений.
Зависит от количества клиентов и сценариев их работы.

Ну так некоторые клиенты и обычный пейджинг устраивает, так что антипаттерн как-то не складывается.

Слона-то я и не заметил.

Суть моего комментария прямо ниже — нет в этом вопросе бест-практайс и антипаттернов, которые были бы определенно хороши или определнно плохи.
Суть моего комментария прямо ниже — нет в этом вопросе бест-практайс и антипаттернов, которые были бы определенно хороши или определнно плохи.

Ну вот да.


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

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

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

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

У кого исключение, а у кого и нет.


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

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

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


Так вот, по сути вопроса. Пейджинг — это прием, который (как и многие другие) применим только в определенной ситуации. Предлагаемые вами снепшоты тоже применимы только в определенных сиутациях.


Для многих кейсов свести задачу к такой ситуации, где применим пейджинг, проще, чем свести ее к такой ситуации, где применимы снепшоты.

Пейджинг — это прием, который (как и многие другие) применим только в определенной ситуации.

И эта ситуация — иммутабельная коллекция. По снепшоту тоже можно безопасно применять этот приём. Собственно создание снепшота — это и есть способ свести любую ситуацию к той, где можно применить пейджинг. Или не применять, если снепшот получается небольшой.

И эта ситуация — иммутабельная коллекция.

Это один из вариантов.


Собственно создание снепшота — это и есть способ свести любую ситуацию к той, где можно применить пейджинг.

… и этот способ можно применить далеко не всегда. Далее смотри выше.

Ни снэпшоты исключение из правил типичной пагинации, ни наоборот.

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

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

А вы пробовали объяснить бизнесу эти особенности? Я вот пробовал. Ответ был коротким:


Меня не волнует где и почему вы накосячили. Исправляйте за свой счёт.
Объяснять надо не при или, тем более, после сдачи проекта/задачи, а при взятии задачи в работу или выставлении плана техрешения или его аналога. Ну банально, увидев пагинацию на макете или мокапе сообщить бизнесу что-то вроде «реализуем самый простой, быстрый и дешевый вариант перехода по страницам, заключающийся в том, что при переходе показывается страница с данными, соотвествующими моменту перехода, а не моменту показа первой страницы. Если кто-то что-то успел добавить, удалить или изменить, то покажутся актуальная вторая страница, с учётом этих изменений. Согласны?» Ну это если не хотите на пагинацию тратить свое время и ресурсы сервера и(или) клиента.

Если хотите использовать свои снэпшоты, то даже вызвать заказчика на диалог можно фразой типа «Самым быстрым и дешевым будет реализация примитивной пагинации, которая будет постоянно выводить непредсказуемые результаты, когда кто-то изменяет, удаляет или добавлять данные. Если вам нужно что-то другое — есть варианты. Как поступим?» Объективно — та же информация :)

В идеальном-то мире все думают наперёд, договариваются заранее, не косячат и не перекладывают друг на друга ответственность :-)


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


Собственно, комментарии к этой статье весьма показательны. Думаю никто не пошёл к бизнесу уточнить "а устраивают ли вас вот эти вот особенности, предложенного нами ранее решения? Может нам всё переделать пока не поздно?". Это ж надо признать свою возможную неправоту, что сильно бьёт по самолюбию.


Вместо этого, понеслось:


  1. Слили рейтинг статьи до рекордных значений.
  2. Слили автору карму, чтоб не мутил воду.
  3. Обозвали автора троллем, шизофреником и вообще дилетантом.
  4. Обвинили автора в NIH синдроме за то, что он рассказал про давно известное, но не столь популярное решение.
  5. Предложили несколько костылей, которые лишь частично решают некоторые из поставленных проблем в некоторых частных случаях.
  6. Усмотрели в предложенном решении кучу несуществующих недостатков.
  7. Отделались общими фразами про "для любого решения можно подобрать такие условия, где оно будет единственно возможным или совершенно не возможным".

Представьте, что сейчас 1968 год, все пользуются goto, а тут Дейкстра пишет свою небезызвестную статью. Так и вижу комментарии:


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

Однако, спустя 50 лет, использование goto без крайней необходимости будет считается плохой практикой. Хотя он всё так же будет присутствовать в языках наших потомков:


Универсального решения постраничного просмотра больших списков, да ещё динамически меняющихся, не существует потому что требования к ним могут быть противоречивыми как со стороны пользователей, так и со стороны бизнеса. И в целом решения лежат в области UI/UX, а уж потом нам надо придумывать как эти решения реализовывать на фронте, бэке и базе(ах). В одних случаях не важны возможные обновления, в других их пропуск из-за неудачного UI решения критически скажется на бизнесе.

Часто вообще необходимости в паджинации при детальном анализе «а зачем» не оказывается. Оказывается, что в одних кейсах юзеру нужна печать списка из 500-1000 пунктов и ему лишь достаточно первой страницы на экране, чтобу убедиться что грубых ошибок не сделал при поиске/фильтрации, а в других для не очевидно как искать/фильтровать записи и только поэтому листал по страницам. Упростили использование поиска, стали отдавать первые 50 и общее количество, а в UI кнопка «показать все N» с предупреждением «может занять очень много времени» если N > 1000.
Сперва давайте обратим внимание, что при работе с базой есть 2 разные по своей сути операции:
  1. Поиск. Относительно тяжёлая операция поиска указателей на данные по некоторому запросу.
  2. Выборка. Относительно простая операция собственно получения данных.

Я отдельно, кстати, хочу заметить, что далеко не всегда сценарий "сначала выбери список идентификаторов, а потом забирай записи по этим идентификаторам" будет быстрее (или хотя бы не медленнее), чем просто получение всех записей, подходящих под критерий; особенно это сильно проявляется, когда мы не напрямую с БД говорим (а какой клиент это нынче делает?), а с прикладным сервером, у которого своих мозгов [не] хватает.

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

Моя реализация чего? Паджинации? Так статья как раз о том, что её не надо делать.


Почитав статью и комментарии, мне кажется, что автор просто толсто троллит или это шизофрения?

Идеально было бы:

Один раз произвести поиск и где-то запомнить его результаты в виде снепшота на определённый момент времени.
Быстро выбирать данные мелкими порциями по мере необходимости.


Ну так ты же сам понимаешь в чём проблема, так зачем штамповать Снапшоты, если есть Кэширование? Дак еще потом ты выбрку делать. Кто мешает закэшировать данные на стороне сервера и спокойно через skip / take (или как традиционно offset / limit) брать порционно данные?
Пример:

public Info getInfo(
@RequestParam(value = "sort", required = false) final String sort,
@RequestParam(value = "skip") final Integer skip,
@RequestParam(value = "take") final Integer take) {

Criteria criteria = Info.builder()
.range(Range.of(skip, take, Sort.by(SortUtils.getOrder(sort, Sort.Direction.ASC))))
.build();
return InfoService.find(criteria);
}


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

Где хранить снепшоты? тут есть 2 варианта:

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


Тот кто читает этот пост, если ты заметил, что у тебя тормозит сайт, знай — это автор этого поста постарался.

image

Когда пришел в новую команду и меня попросили оптимизировать работу сайта, я просто в шоке был с того что он на фронт тянет. Наркоманы…
Поэтому прошу вас, не делайте никаких антипаттернов и всяких криворульных универсальных велосипедов. Реализация простыми и понятными способами — самый лаконичный вариант в любом деле.

P.S. Полистав статьи автора, особенно эту сделал вывод:

Здравствуйте, меня зовут Дмитрий Карловский и я… антиконформист


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

Краткий итог: Да, есть пагинация. Да, всем нужно знать какие есть проблемы. Прекратите придумывать велосипеды, уже до вас всё придумали (и это не только пагинации касается), берите, исходя от ваших потребностей, и пользуйтесь.
зачем штамповать Снапшоты, если есть Кэширование?

Отличительная особенность "кэша" в том, что он может быть в любой момент очищен, не ломая приложение. Кеш увеличивает производительность, но не несёт никакой бизнес-ценности. Если кеш у вас потухнет между 5 и 6 страницей, вы получите все описанные в статье проблемы. Если же вы сделаете большое время жизни кеша, то получите другую проблему — пользователь обновляет страницу в надежде получить актуальный список, а вы ему показываете устаревшие данные.

Если кеш у вас потухнет между 5 и 6 страницей, вы получите все описанные в статье проблемы


Если бы, да кабы.
Расскажу по секрету — можно не обновляй страницу целиком, а только блок с гридом.
Реализуй механизм обновления кэша, это не сложно. Опять же, зависит от задачи. Критично иметь тру данные в Realtime или некритично иметь расхождение в какой-то промежуток времени — это не то на чем стоит заострять внимание.

Если же вы сделаете большое время жизни кеша, то получите другую проблему

Извините, а снапшот раз в 10 секунд будет обновляться или как? Откуда я буду знать, что на данный момент смотрю на актуальные данные? Точно такая же ситуация.
Повторюсь, что уже писал раз пять. Тут всё решается просто — поговорил с бизнесом, согласовал как им нужно.Не надо париться.
Извините, а снапшот раз в 10 секунд будет обновляться или как? Откуда я буду знать, что на данный момент смотрю на актуальные данные?

Он создаётся каждый раз, когда вы инициируете новый поиск. В частности, при обновлении страницы.

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

Если же мы говорим про многостраничный сайт, то, очевидно, в результате поиска будет редирект на урл с идентификатором снепшота и номером страницы. Тут сколько ни перезагружай страницу — ничего не поменяется.
> зачем штамповать Снапшоты, если есть Кэширование

В данном кейсе это одно и то же.

> В большинстве случаев, заказчику надо чтобы просто данные были, а как там страницы съезжают или данные восполняются — уходит на голову разработчика.

И претензии тоже потом уходят на голову, в лучшем случае, разработчика. Как только слышу «данные нужно частями показывать» (не важно, страницы или подгрузка), то всегда спрашиваю чёткий алгоритм показа. Могу предложить варианты, их плюсы и минусы, но ни в коем случае не выбирать за заказчика.
Only those users with full accounts are able to leave comments. Log in, please.