Как получить непрерывный список с помощью PostgreSQL в Web

#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, я просто переключился на запросы. Я думаю, другим решением было бы продолжить расширять / заполнять массив. (Повторный запуск запроса, но за исключением уже включенных идентификаторов).

В любом случае, надеюсь, это поможет!