Оглавление

Введение

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

Тем не менее, некоторые бесстрашные технологи, например, из webapp.io, рискуют быть отлученными — их продукт использует очереди Postgres для обеспечения основных функций. Такие компании, как webapp.io, являются исключением из нормы, признавая, что иногда другие принципы перевешивают «масштабируемость». Когда культ масштабируемости дает трещину, трещины часто бывают небольшими, но они основаны на новых принципах, таких как простота эксплуатации, удобство сопровождения, понятность и привычность. Иногда они основаны на новых идеях, таких как повторное использование старых технологий новыми способами или использование Postgres для очередей. Вам тоже следует рискнуть отлучиться от культа масштабируемости.

Что такое технология очередей Postgres?

Технология очередей Postgres состоит из двух частей: объявление и прослушивание новых заданий (pub/sub) и взаимное исключение (блокировки строк). Оба предоставляются «из коробки», начиная с Postgres 9.5, выпущенного в 2016 году.

Комбинируя NOTIFYиLISTEN, Postgres упрощает добавление pub/sub в любое приложение. Помимо публикации/подписки, Postgres также предоставляет семантику «одно задание на каждого воркера» с помощью FOR UPDATE SKIP LOCKED. Запросы с этим суффиксом блокируют строки для соответствующих записей и игнорируют все записи, для которых уже установлены блокировки. Применительно к jobзаписям эта функция позволяет выполнять простые запросы обработки очереди, например SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1.

В совокупности эти две функции составляют основу для ресурсоэффективной обработки очередей. Важно отметить, что SKIP LOCKEDобеспечивает «непоследовательное» представление своих данных. Эта несогласованность — именно то, что нужно от очереди; уже обрабатываемые задания (т. е. с блокировкой строк) невидимы для других воркеров, что обеспечивает распределенное взаимное исключение. Эти блокировки открывают путь как для периодической пакетной обработки, так и для обработки заданий в реальном времени NOTIFYучастников дляLISTENновых заданий.

Несмотря на то, что эти возможности Postgres имеют множество пользователей, существует относительно мало публичных сторонников их использования в качестве серверной части очереди. Например, в комментарии Hacker News говорилось, что использование Postgres таким способом является «хакерским», и комментатор не получил никакой реакции. Я обнаружил, что комментарий содержит массу ерунды и аргументов. Похоже, что такое мнение является «преобладающей мудростью» в отрасли: если вы хотите публично говорить о технологии очередей, лучше не использовать реляционную базу данных. Эта индустрия карго-культов не склонна сопротивляться любой мудрости, которая «преобладает». Я надеюсь развеять у кого-либо представление о том, что Postgres — это худшая технология очередей.

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

Ландшафт фоновых заданий

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

  • Redis — замечательное хранилище данных в памяти и «брокер сообщений», который является серверной частью многих популярных библиотек для фоновых заданий.

  • Apache Kafka Распределенная «платформа потоковой передачи событий», поддерживаемая Apache Foundation.

  • RabbitMQ Предположительно, наиболее широко используемый «брокер сообщений»

  • Amazon SQS Продукт Amazon SaaS для высокомасштабируемых очередей.

Приношу извинения, если исключил ваших фаворитов; это не претендует на исчерпывающий характер.

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

Я думаю, здесь важно остановиться и обсудить значение Redis в мире «фоновых заданий». Если вы просмотрите тему фоновых заданий на GitHub , то увидите, что все пять самых популярных библиотек поддерживаются Redis:

  1. Sidekiq (ruby)

  2. resque (ruby)

  3. rq (python)

  4. Hangfire (C#)

  5. asynq (go)

Для этого есть причина; поскольку Redis хранит данные в памяти, скорость его вставки и извлечения феноменальна. Он также имеет встроенный API-интерфейс pub-sub, а также собственную listи setструктуры данных, которые в сочетании создают фантастическую очередь. Redis масштабируется. Для многих разработчиков такая масштабируемость сделала его выбором по умолчанию, а значения по умолчанию очень эффективны.

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

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

Как отрасль, мы стали абсолютно одержимы «масштабируемостью». По-видимому, за счет всего остального, например, простоты, удобства обслуживания и снижения когнитивной нагрузки на разработчиков. Нам всем хочется верить, что мы создаем следующую вещь, которая потребует масштабов Google, Facebook или Uber, но на самом деле почти всегда — нет. Наши технологические решения должны отражать этот факт. Скорее всего, мы строим для относительно небольших масштабов и должны оптимизировать наши решения с учетом совершенно другого набора факторов, которые больше связаны с составом команды, чем с технологическим превосходством.

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

Чего еще не было сказано, так это того, что Postgres на самом деле хорошо масштабируется. Но Postgres — это программное обеспечение общего назначения, и оно не будет «лучшим» при масштабировании для случаев использования очередей. Для этого варианта использования он будет работать очень хорошо, точно так же, как он работает очень хорошо, делая все, что он делает.

Принимая решения

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


Самый важный вопрос, который следует задать при принятии технологических решений: какие технологии я сейчас использую и хорошо понимаю?

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

Велика вероятность, что вы уже используете реляционную базу данных, и если этой реляционной базой данных является Postgres, вам следует рассмотреть возможность использования очередей перед любым другим программным обеспечением. Если вы не используете Postgres, вам следует рассмотреть любую технологию, которая вам кажется наиболее скучной, прежде чем рассматривать что-либо еще.

Когда затраты невелики, выбирайте любой. Источник: https://boringtechnology.club
Когда затраты невелики, выбирайте любой. Источник: https://boringtechnology.club

Технологии, которые (пока) не используются, стоят дороже.

Когда затраты высоки, выбирайте мало. Источник: https://boringtechnology.club
Когда затраты высоки, выбирайте мало. Источник: https://boringtechnology.club

Другими словами, скучная технология соотносится с тем, что уже используется. Приложения, ориентированные на передачу сообщений, например системы уведомлений, могут счесть технологию RabbitMQ скучной. Приложения для кэширования могут счесть технологию Redis скучной. Приложения с большим объемом реляционных данных могут счесть технологию Postgres скучной. Максимально скучный выбор, скорее всего, подойдет вам и вашей команде.

Если вы еще не используете Redis, Kafka, RabbitMQ или SQS, использование любого из них только для фоновых заданий обойдется дорого. Вы добавляете новую системную зависимость в каждую среду разработки, тестирования и производства, вероятно, на всю оставшуюся жизнь приложения. Теперь от каждой будущей роли разработчика, администратора базы данных и/или SRE в команде требуется новый набор навыков. Теперь им необходимо знать сложные режимы сбоев и настройки конфигурации этих новых систем. Кандидаты на работу должны быть убеждены, что изучение этой новой технологии — это стоящая инвестиция времени. Администраторы баз данных/SRE должны знать, как восстанавливаться после операционных сбоев, диагностировать проблемы и контролировать производительность. Нам нужно многое узнать; и есть много такого, что никто из команды не понимает, что им нужно знать. Неизвестные неизвестные этих систем представляют собой риск. Особенно, если эти системы являются для вас выбором по умолчанию, и вы не особо задумывались, почему они являются вашим выбором по умолчанию.

Это не значит, что самая скучная технология — панацея, включая Postgres. То, что мы жертвуем ради знакомства, известных видов отказов и амортизированной «стоимости», может быть оплачено производительностью или каким-то другим жизненно важным принципом. В конце концов, загрузка и извлечение из очереди Postgres происходит значительно медленнее, чем из Redis. Использование Postgres для очередей может означать, что вместо одной реляционной базы данных на одном сервере приложениям теперь требуются база данных «приложения» и база данных «очереди». Это может даже означать совершенно отдельный сервер базы данных для фоновых заданий, поэтому фоновые задания можно масштабировать независимо. Это может означать, что базы данных придется VACUUMировать чаще, что приведет к снижению производительности этого процесса. Есть много последствий, которые следует учитывать перед внедрением Postgres для очередей, и их следует сопоставлять с потребностями команды и приложения, чтобы можно было принять обоснованное решение. Postgres не должен быть выбором по умолчанию. Точно так же не следует использовать Redis, Kafka, RabbitMQ, SQS или любую другую распределенную очередь. Выбор скучной технологии должен стать выбором по умолчанию.

Выбор технологий – это компромисс во всех отношениях . Я обнаружил, что Dagster придерживается прагматичного подхода к использованию Postgres для своих очередей. Если вы сомневаетесь, считайте аксиомой следующее:

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

Постройка с аварийными люками 

Ранее я упомянул про «не быть загнанным в угол». Что касается фоновых заданий, это означает, что код приложения для обработки заданий не должен зависеть от очереди.

Передовые технологии одного дня — скучные технологии другого дня. По мере роста приложений и достижения успеха новые технологии имеют тенденцию привязываться к приложениям по необходимости. Обычно в качестве слоев кэширования добавляют memcached или Redis (но сначала посмотрите на нелоггируемые таблицы Postgres!). Это означает, что эти технологии со временем становятся «скучными», что снижает их стоимость и меняет подход к их использованию в качестве очередей.

Здание с аварийными люками — это абстракция. Ранее я перечислил пять самых популярных библиотек фоновых заданий на GitHub. За исключением Hangfire, ни одна из этих библиотек не предоставляет аварийный люк для выбора технологии постановки в очередь, кроме Redis. Это означает, что переключение очередей требует переписывания кода приложения, поскольку перед базовой очередью нет надежной абстракции.

Так не должно быть. Технология очередей должна быть абстрагирована, чтобы пользователи могли выбрать правильную очередь для выполнения задания. Я не являюсь пользователем Hangfire (или C#), но Hangfire, похоже, правильно понял абстракцию.

Именно с учетом предыдущей философии выбора скучных технологий и строительства с аварийными люками я создал Neoq https://github.com/acaloiaro/neoq . Очереди Neoq могут быть в памяти, Postgres или Redis (добро пожаловать в вашу любимую скучную технологию!). Пользователи могут переключаться между очередями, не меняя код приложения — просто инициализируйте его с помощью другого бэкэнда очереди. Neoq — это скорее абстракция, чем конкретная реализация. Хотя реализации как в памяти, так и Postgres являются собственными, реализация Redis является async . Речь идет скорее о предоставлении аварийных люков, чем о привязке разработчиков к конкретной базовой технологии очередей.

Мне бы хотелось видеть больше библиотек, подобных neoq, для языков, отличных от Go. Я думаю, что отсутствие программных библиотек с аварийными люками — это то, что загоняет многих разработчиков в угол, вынуждая их начинать простые проекты с зависимостью Redis задолго до того, как Redis будет гарантирован. Redis великолепен, но это не всегда подходящая очередь или необходимый уровень сложности для работы. То же самое касается Kafka, RabbitMQ и SQS.

Выбор технологии очередей Postgres

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

Ваше здоровье!

Комментарии (9)


  1. Gromilo
    25.09.2023 08:29
    +1

    Для .net есть библиотека CAP.

    Можно выбирать хранилище, можно выбирать транспорт.

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


    1. breninsul
      25.09.2023 08:29
      +1

      т.е. не использует for update ... skip locked?

      так оно же всё ломает в таком случае...


      1. Gromilo
        25.09.2023 08:29
        +1

        И да и нет.

        Во-первых автор сказал, что защита от повторной обработки сообщений - это ваша проблема. А мы помним, что если мы хотим гарантированную обработку, то у нас будет "хотя бы 1 раз" и всё что мы делаем направлено на снижение вероятности второй обработки, но не гарантирует её отсутствия.

        Во-вторых автор использует оптимистичную обработку с таймаутами, что резко снижает вероятность конфликта. Идея такая: воркер получает сообщение из очереди, сохранят в бд, и начинает обрабатывать. Если всё хорошо, то всё хорошо. А вот если выполнение задачи упадёт, то тут может быть гонка.

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


  1. dph
    25.09.2023 08:29
    +3

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


  1. Deq56
    25.09.2023 08:29

    А как себя будет вести очереди PG, если клиентов будет около 100? Будет ли это корректно работать через pgpool?


    1. bel1k0v Автор
      25.09.2023 08:29

      100 клиентов это маловато, будет, если правильно настроить, но он не нужен для такого небольшого к-ва


    1. dimkus
      25.09.2023 08:29

      допустим при 100 коннектов в среднем обработка очереди в транзакции может занять порядка 100мс. то это приблизительно 1000 транзакций за секунду. Если упаковывать в бечи, то можно ещё больше обработать. так что вопрос, что вы хотите. У кафки пропускная способность порядка 25000 сообщений в секунду (с использованием ACK раз в десять меньше). Важно понимать какую задачу решаете и как вы технологиями пользуетесь. В целом транзакционные очереди в постгресе крутая штука. В статье ключевой момент это FOR UPDATE и SKIP LOCKED. Остальное в статье - вода.


      1. bel1k0v Автор
        25.09.2023 08:29
        -1

        NOTIFY и LISTEN тоже нужны


  1. kenoma
    25.09.2023 08:29
    +1

    Для C# есть Rebus, для которого существует очень широкий набор движков очередей, в том числе и PosgtreSql. Как альтернатива в ряде случаев и masstransit зайдет.