Уже несколько лет применяем подобную пагинацию на своих проектах в компании. Mysql по обычному числовому идентификатору (таблицы более 500к записей) и в MongoDB по ObjectId (коллекции на пару десятков миллионов записей) - работает отлично. А вот в одном из проектов в таблице на 200к записей пагинация через обычный limit\offset стандартными средствами php-фреймворка. И вот там очень ощущается деградация скорости выполнения запроса ближе к последней трети. Наверное, стоит еще отметить, что для поля для курсора требуется индекс. Спасибо за видео!
называется это курсорная пагинация. проще объяснить так: мы ставим курсор на какую-то последнюю запись(которую приняли в квери-параметре хендлера из урла), и говорим: ОТДАЙ НАМ СЛЕДУЮЩИЕ %LIMIT%(предположим, три). ну оно и отдает. тем самым не прогружая всю БД как в случае с оффсетом. и считается хорошей практикой отдавать в ответе клиенту в жсоне поля next и prev путь на следующую/предыдущую страницу, где квери-параметры(в ссылке) next/prev - это base64 айдишников. ну а если бы id был интом, то сравнение с датой было бы лишнее лишнее. инта достаточно. за видео спасибо.
Идентификатор в примере либо лишний, либо не того типа, ибо сравнение картежа, где есть UUID не имеет смысла, имхо. В примере с сайта что вы показывали скорее всего используется числовой идентификатор, скорее всего автогенерация по Seq, чтобы каждая новая запись имела следующий номер, тогда сравнение картежей имеет смысл.
Интересный приём, действительно должно быстрее работать. Пара вопросов возникает: 1) Guid в качестве уникального идентификатора. При использовании гуида не стоит ожидать, что порядок выбора записей для пагинации будет совпадать с порядком того, как записи лежат в таблице. Хотя по факту работать будет и записи не пропустит(вроде как), но семантически может выглядеть странно, например, порядок книг будет выводится не в алфавитном порядке(не в том порядке, как мы их добавляли). 2) Было бы более показательно и всеообъемлюще, если бы в примере был не таймстемп, а просто дата. Т.е. если бы в примере были записи в одинаковыми значениями в поле даты. А то так получается, что у всех записей во всех полях значения уникальные. Тут хоть по чему сортируй - получишь корректную(с точки зрения отсутствия пропусков) пагинацию. 3) Получается что необходимо хранить стейт на стороне сервера? Или наоборот со стороны клиента передавать значения из последней полученной записи?
Этот вариант не подходит, если нужна классическая пагинация (в виде ссылок на страницы пагинации). А так давно заметил, что даже при небольших значениях OFFSET (10-15), запрос в MySQL выполняется значительно медленнее, чем без OFFSET
Почему во время интерпретации на ключевое слово offset, СУБД под капотом не делает тоже самое, если так быстрее? И что такого делает offset, что получается медленнее. СУБД типа сначала всю таблицу выгружает, а потом лишнее выкидывает? Поэтому?
потому что субд не ванга, откуда она знает, что ей делать надо. на то и нужны команды. offset по сути отсчитывает все записи до указанного смещения. если задал offset 100000 limit 10, то по сути базе надо отсчитать все записи, пока не будет по счету 100к и начиная со следующей отдаст 10 записей. а существует какое-то доп.условие WHERE, то скорость намного ухудшается. например когда надо достать просто 10 записей по условию WHERE, то база найдет первые 10 попавшихся и отдаст их, а тут она должна найти все 100к подходящие под условие WHERE и только следующие 10 отдать
Получается что вместо select * from video order by created desc offset 0 limit 10 нам предлагают делать select * from video where created < {last_created} order by created desc limit 10 что означает что на клиенте нужно хранить не page_id, а данные всех полей по которым идет сортировка Ну ок, классно, что есть такой вариант. Но я думаю это уже немного относится к ранней оптимизации. То есть, я бы так делал если бы я реально понимал, что для меня это важно, в других случаях бы не заморачивался. p. s. про кортежное сравнение первый раз узнал xD
А ты попробуй сгенери таблицу с 10 миллионами записей и попробуй бабахнуть в ней оффсетом начиная с 9 миллионной записи. И каунт до кучи сделай. И сразу станет понятно, что все это перестаёт нормально работать :)
А точно ли есть такая задача? Обычно эта цифра на самом деле не нужна, если коллекция огромная (сотни тысяч записей), ну если вы не гугл, конечно. Но если нужна - то конечно делать count(*)
привет! немного критики, не обессудь... видео кажется затянутым, и если бы я совсем не понимал данную тему, вряд-ли бы понял. т к в данном видео ничего не сказано про индексы, то лучше использовать limit offset)
Очень долго, это можно было в минут 5 уложить. А так идея интересная, получается это на беке нужно всегда сохранять ключи последней записи? Думаю что скорость достигается за счет того, что при миллионах записей вместо последовательного пропуска от OFFSET отрабатывается какой нибудь бинарный поиск по индексу.
Не могу понять что так все любят пихать в поле примари кей тип uuid когда он в бд 128 бит занимает, что как бы много, тот же биг инт будет занимать в 2 раза меньше
потому что автоинкримент на id не всегда решает все виды задач. в распределенных действительно больших системах получать автоинкрементный id становится большой болью. когда нужно спрятать последовательность выдачи id тоже подходит uuid, его можно получать с timestamp - uuid 7 версии, есть uuid с hash. Все зависит от решаемой задачи.
Уже несколько лет применяем подобную пагинацию на своих проектах в компании. Mysql по обычному числовому идентификатору (таблицы более 500к записей) и в MongoDB по ObjectId (коллекции на пару десятков миллионов записей) - работает отлично. А вот в одном из проектов в таблице на 200к записей пагинация через обычный limit\offset стандартными средствами php-фреймворка. И вот там очень ощущается деградация скорости выполнения запроса ближе к последней трети. Наверное, стоит еще отметить, что для поля для курсора требуется индекс. Спасибо за видео!
а почему нельзя просто ID сравнивать ? сделав его big int ? без кортежного сравнения и обойтись 1 полем
называется это курсорная пагинация. проще объяснить так: мы ставим курсор на какую-то последнюю запись(которую приняли в квери-параметре хендлера из урла), и говорим: ОТДАЙ НАМ СЛЕДУЮЩИЕ %LIMIT%(предположим, три). ну оно и отдает. тем самым не прогружая всю БД как в случае с оффсетом. и считается хорошей практикой отдавать в ответе клиенту в жсоне поля next и prev путь на следующую/предыдущую страницу, где квери-параметры(в ссылке) next/prev - это base64 айдишников.
ну а если бы id был интом, то сравнение с датой было бы лишнее лишнее. инта достаточно.
за видео спасибо.
Господь храни тебя и твой канал, ничего более информативного и интересного по гошке не видел, надеюсь ты когда-нибудь вернешься...
Идентификатор в примере либо лишний, либо не того типа, ибо сравнение картежа, где есть UUID не имеет смысла, имхо. В примере с сайта что вы показывали скорее всего используется числовой идентификатор, скорее всего автогенерация по Seq, чтобы каждая новая запись имела следующий номер, тогда сравнение картежей имеет смысл.
Что скажете по поводу такой пагинации?
P.S. на этот раз вместо вебки телефон, но помешали тени. некст тайм включу софтбоксы
Интересный приём, действительно должно быстрее работать.
Пара вопросов возникает:
1) Guid в качестве уникального идентификатора. При использовании гуида не стоит ожидать, что порядок выбора записей для пагинации будет совпадать с порядком того, как записи лежат в таблице. Хотя по факту работать будет и записи не пропустит(вроде как), но семантически может выглядеть странно, например, порядок книг будет выводится не в алфавитном порядке(не в том порядке, как мы их добавляли).
2) Было бы более показательно и всеообъемлюще, если бы в примере был не таймстемп, а просто дата. Т.е. если бы в примере были записи в одинаковыми значениями в поле даты. А то так получается, что у всех записей во всех полях значения уникальные. Тут хоть по чему сортируй - получишь корректную(с точки зрения отсутствия пропусков) пагинацию.
3) Получается что необходимо хранить стейт на стороне сервера? Или наоборот со стороны клиента передавать значения из последней полученной записи?
Спасибо за видео. Коммент в поддержку!
Я только начинаю изучать гошку, для меня выглядит это как игра в наперстки с уличным жуликом. И я такой - Это магия... Но в конце всё понял
Спасибо! Теперь я знаю почему у меня борода не растет 😂
Этот вариант не подходит, если нужна классическая пагинация (в виде ссылок на страницы пагинации). А так давно заметил, что даже при небольших значениях OFFSET (10-15), запрос в MySQL выполняется значительно медленнее, чем без OFFSET
Почему во время интерпретации на ключевое слово offset, СУБД под капотом не делает тоже самое, если так быстрее? И что такого делает offset, что получается медленнее. СУБД типа сначала всю таблицу выгружает, а потом лишнее выкидывает? Поэтому?
потому что субд не ванга, откуда она знает, что ей делать надо. на то и нужны команды. offset по сути отсчитывает все записи до указанного смещения. если задал offset 100000 limit 10, то по сути базе надо отсчитать все записи, пока не будет по счету 100к и начиная со следующей отдаст 10 записей. а существует какое-то доп.условие WHERE, то скорость намного ухудшается. например когда надо достать просто 10 записей по условию WHERE, то база найдет первые 10 попавшихся и отдаст их, а тут она должна найти все 100к подходящие под условие WHERE и только следующие 10 отдать
Получается что вместо
select * from video order by created desc offset 0 limit 10
нам предлагают делать
select * from video where created < {last_created} order by created desc limit 10
что означает что на клиенте нужно хранить не page_id, а данные всех полей по которым идет сортировка
Ну ок, классно, что есть такой вариант.
Но я думаю это уже немного относится к ранней оптимизации.
То есть, я бы так делал если бы я реально понимал, что для меня это важно, в других случаях бы не заморачивался.
p. s. про кортежное сравнение первый раз узнал xD
А ты попробуй сгенери таблицу с 10 миллионами записей и попробуй бабахнуть в ней оффсетом начиная с 9 миллионной записи. И каунт до кучи сделай. И сразу станет понятно, что все это перестаёт нормально работать :)
интересно, можно ли сделать пагинацию без ORDER BY?
может быть
а почему нельзя просто ID сравнивать ? сделав его big int ? без кортежного сравнения и обойтись 1 полем
можно
Можно всю эту логику в функцию на БД засунуть. Типа `SELECT get_books(1, 10) ` чтоб было хорошо и беку и фронту.
А как на клиенте показывать сколько всего есть страниц, если не использовать count(*)?
А точно ли есть такая задача?
Обычно эта цифра на самом деле не нужна, если коллекция огромная (сотни тысяч записей), ну если вы не гугл, конечно.
Но если нужна - то конечно делать count(*)
@@TheArtofDevelopment Спасибо!
привет! немного критики, не обессудь...
видео кажется затянутым, и если бы я совсем не понимал данную тему, вряд-ли бы понял.
т к в данном видео ничего не сказано про индексы, то лучше использовать limit offset)
Очень долго, это можно было в минут 5 уложить.
А так идея интересная, получается это на беке нужно всегда сохранять ключи последней записи?
Думаю что скорость достигается за счет того, что при миллионах записей вместо последовательного пропуска от OFFSET отрабатывается какой нибудь бинарный поиск по индексу.
главное чтоб пользователь не нажал переход на 69 страницу, сразу с первой ;)
Это просто порционная выдача пачки данных, а не "навигация" по пагинации.
В хайлоад на 69 странице, раз в долю секунды будут меняться данные ;)
Это всё хорошо, но мы не можем взять и перейти сразу на тысячную страницу. И не знаем их общее количество.
а есть такая задача? если есть ее можно решить иначе
@@TheArtofDevelopment не подскажите, как?
стандартно, через limit и offset
@@TheArtofDevelopment хорошо, спасибо
Не могу понять что так все любят пихать в поле примари кей тип uuid когда он в бд 128 бит занимает, что как бы много, тот же биг инт будет занимать в 2 раза меньше
потому что автоинкримент на id не всегда решает все виды задач.
в распределенных действительно больших системах получать автоинкрементный id становится большой болью. когда нужно спрятать последовательность выдачи id тоже подходит uuid, его можно получать с timestamp - uuid 7 версии, есть uuid с hash. Все зависит от решаемой задачи.