#postgresql #http #pagination #database-cursor #continuous
#postgresql #http #разбивка на страницы #база данных-курсор #непрерывный
Вопрос:
Я создаю API через HTTP, который извлекает много строк из PostgreSQL с нумерацией страниц. В обычных случаях я обычно реализую такую разбивку на страницы через наивное предложение OFFET
/ LIMIT
. Однако в этом случае существуют некоторые особые требования:
- Там так много строк, что я считаю, что пользователи не могут дойти до конца (представьте временную шкалу Twitter).
- Страницы не должны быть доступны случайным образом, а только последовательно.
- API вернет URL-адрес, содержащий маркер курсора, который направляет на страницу непрерывных блоков.
- Токены курсора должны существовать не постоянно, а в течение некоторого времени.
- Его порядок часто меняется (например, рейтинги Reddit), однако непрерывные курсоры должны сохранять свой постоянный порядок.
Как я могу выполнить миссию? Я готов изменить всю свою схему базы данных для этого!
Комментарии:
1. Просто чтобы быть уверенным, о чем вы спрашиваете. Вы говорите, что много строк или действительно широкие строки, или и то, и другое?
2. обязательно ли это делать только с помощью курсоров? потому что для этого есть другие методы, которые вообще не требуют управления курсорами
Ответ №1:
Предполагая, что изменяется только порядок результатов, а не данные в строках, ответ Фредрика имеет смысл. Тем не менее, я бы предложил следующие дополнения:
-
храните список идентификаторов в таблице postgresql, используя тип массива, а не в памяти. Выполнение этого в памяти, если вы не используете что-то вроде redis с автоматическим истечением срока действия и ограничениями памяти, настраивает вас на атаку с использованием памяти DOS. Я предполагаю, что это будет выглядеть примерно так:
create table foo_paging_cursor ( cursor_token ..., -- probably a uuid is best or timestamp (see below) result_ids integer[], -- or text[] if you have non-integer ids expiry_time TIMESTAMP );
-
Вам нужно решить, могут ли cursor_token и result_ids использоваться совместно между пользователями, чтобы уменьшить ваши потребности в хранилище и время, необходимое для выполнения начального запроса для каждого пользователя. Если они могут быть общими, выберите окно кэша, скажем, 1 или 5 минут, а затем по новому запросу создайте cache_token для этого периода времени, а затем проверьте, были ли уже вычислены идентификаторы результатов для этого токена. Если нет, добавьте новую строку для этого токена. Вероятно, вам следует добавить блокировку вокруг кода проверки / вставки для обработки одновременных запросов на новый токен.
-
Запланируйте фоновое задание, которое удаляет старые токены / результаты, и убедитесь, что ваш клиентский код может обрабатывать любые ошибки, связанные с истекшими / недействительными токенами.
Даже не рассматривайте возможность использования для этого реальных курсоров db.
Сохранение идентификаторов результатов в списках Redis — еще один способ справиться с этим (см. Команду LRANGE), но будьте осторожны с истечением срока действия и использованием памяти, если вы пойдете по этому пути. Вашим ключом Redis будет cursor_token, а идентификаторы будут элементами списка.
Комментарии:
1. А еще лучше, сделайте это временной таблицей . Быстрее, меньше нагрузка на диск. Не нужно беспокоиться о DOS-атаке, временные таблицы могут использовать только ограниченную оперативную память (читайте об этом
temp_buffers
в руководстве ) и записываются на диск, когда оперативной памяти недостаточно.2. Временные таблицы являются локальными для сеанса и удаляются по завершении сеанса. Таким образом, это не будет работать с пулом соединений с БД или в случае, когда конечная точка http api распределена по нескольким узлам и использует разные соединения. Это также может привести к проблемам при перезапуске сервера приложений и необходимости повторного подключения к БД. Тем не менее, вы получаете те же преимущества, помещая таблицу в табличное пространство с поддержкой памяти (через tmpfs). Смотрите magazine.redhat.com/2007/12/12 /…
3. Спасибо за ваш совет. Я решил использовать memcached и хранить идентификаторы, разделенные запятыми, в ключах (которые являются токенами курсора) со временем истечения срока действия. Спасибо!
Ответ №2:
Я абсолютно ничего не знаю о PostgreSQL, но я довольно приличный разработчик SQL Server, поэтому я все равно хотел бы попробовать это 🙂
Сколько строк / страниц, как вы ожидаете, пользователь будет максимально просматривать за сеанс? Например, если вы ожидаете, что пользователь будет просматривать максимум 10 страниц за каждый сеанс [каждая страница содержит 50 строк], вы могли бы использовать этот максимум и настроить веб-сервис так, чтобы, когда пользователь запрашивает первую страницу, вы кэшировали 10 * 50 строк (или просто Id: s для строк, зависит от того, сколько памяти / одновременных пользователей у вас есть).
Это, безусловно, помогло бы ускорить работу вашего веб-сервиса несколькими способами. И это довольно легко реализовать. Итак:
- Когда пользователь запрашивает данные со страницы # 1. Запустите запрос (в комплекте с order by, проверками соединения и т.д.), Сохраните все идентификаторы в массиве (но не более 500 идентификаторов). Возвращает потоки данных, соответствующие идентификаторам в массиве в позициях 0-9.
- Когда пользователь запрашивает страницу # 2-10. Возвращает потоки данных, соответствующие идентификаторам в массиве в положениях (страница-1)*50 — (страница)*50-1.
Вы также можете увеличить числа, массив из 500 int: s займет всего 2 КБ памяти, но это также зависит от того, насколько быстро вы хотите получить свой первоначальный запрос / ответ.
Я использовал аналогичную технику на живом веб-сайте, и когда пользователь прошел мимо страницы 10, я просто переключился на запросы. Я думаю, другим решением было бы продолжить расширять / заполнять массив. (Повторный запуск запроса, но за исключением уже включенных идентификаторов).
В любом случае, надеюсь, это поможет!