Очередь без серверов и головной боли: как устроен Serverless Queue в MWS Cloud Platform

Если вы хоть раз эксплуатировали Kafka, то знаете, сколько нервных клеток способен съесть её кластер. Поднять Kafka для PoC несложно, а вот дальше начинается планирование ёмкости, диски, replication factor, перераспределение партиций и восстановление после отказа брокера.
В MWS Cloud Platform мы решили, что так дальше жить нельзя, и запустили Serverless Queue — бессерверный Kafka-совместимый брокер. Читайте статью, чтобы узнать, как с его помощью быстро и просто можно запустить сценарии обработки сообщений по Kafka-протоколу.

Message queue — важная часть бэкенда. Задача сервиса проста: надёжно и безопасно передавать сообщения от producer’ов к consumer’ам. Очередь можно организовать с помощью RabbitMQ, а можно с помощью PostgreSQL или Redis, а если нужен не просто транспорт, а история событий, которую можно перечитать, то стандарт индустрии, конечно, Kafka.
В среде бэкендеров и devops’ов Kafka считается сложной технологией в первую очередь из-за непростой эксплуатации: планирования ёмкости локальных дисков для брокеров, репликации и перераспределения партиций. Но что, если мы скажем, что большинству команд (на старте уж точно) не нужна избыточная сложность Kafka?
Меня зовут Кирилл Пятковский, я разработчик в команде Serverless Data Streams в MWS Cloud Platform. Я готовил эту статью вместе с Денисом Титовым, TPO дата-сервисов платформы.
В материале мы разберём модели передачи сообщений, а затем посмотрим, как сервис MWS Serverless Queue устроен внутри: почему брокеры не владеют ни одной партицией, как сообщения разных клиентов оказываются в одном объекте S3 и почему Kafka-клиент при этом ничего не замечает. И честно скажем, сколько это стоит по задержке.
Две модели, которые прячутся за словом «очередь»
Прежде чем разбирать конкретные технологии, стоит развести понятия, дальше на этом различии строится выбор инструмента.
Классическая очередь. Здесь сообщение предназначено для обработки одним из consumer'ов. После успешной обработки и подтверждения сообщение обычно удаляется из очереди. Несколько consumer'ов могут конкурировать за работу, распределяя сообщения между собой. Такая модель хорошо подходит, когда нужно раздать задачи исполнителям и обработать каждую ровно один раз. Например, пользователь нажал «восстановить пароль». Задача уходит в очередь, любой свободный обработчик её забирает, отправляет письмо и подтверждает. Кто именно из десяти обработчиков это сделал, неважно. Важно, что забрал её ровно один: иначе пользователь получит два письма с разными ссылками и не поймёт, какая из них рабочая.
Потоковая передача событий. Здесь событие не исчезает после чтения. Оно хранится в течение заданного retention-периода, а разные группы consumer'ов читают один и тот же поток независимо друг от друга. Каждая группа хранит собственную позицию чтения, поэтому историю можно перечитать повторно. Такая модель нужна, когда важен не только сам факт доставки сообщения, но и история того, что происходило в системе. Возьмём заказ в интернет-магазине. Событие «заказ оплачен» интересно сразу нескольким системам: складу, чтобы зарезервировать товар, сервису уведомлений, чтобы отправить пуш, аналитике, чтобы посчитать выручку за день, и антифроду, чтобы посмотреть на поведение покупателя. Все четыре читают один и тот же поток, каждая в своём темпе. А если через неделю аналитика захочет пересчитать метрику по новой формуле, она просто перечитает поток за нужный период — и просить магазин заново прислать все заказы не придётся.
Давайте быстро пробежимся и разберёмся, как устроены эти очереди.
Классическая очередь
В основе классической очереди три роли:
Producer: отправляет сообщения.
Broker (Queue): хранит и доставляет.
Consumer: читает и обрабатывает.

Схема тривиальная, но именно она даёт архитектуре:
Развязку сервисов: они не знают друг о друге, producer не ждёт, пока consumer освободится.
Буферизацию: не успевает consumer, сообщения копятся в очереди, а не роняют систему.
Отказоустойчивость: упавший consumer не теряет сообщения, они переотправляются.
Независимое масштабирование: producer и consumer растут отдельно друг от друга, под свою нагрузку.
Схема простая, поэтому, если нужно перечитать историю заново или подключить несколько независимых читателей одного потока, классическая очередь тут не подойдёт в принципе. Если сообщение прочитали, то второй раз его никто не увидит.
Самый известный пример классической очереди — RabbitMQ. К модели выше добавляется Exchange и Binding — первый принимает сообщения, и по правилам binding раскладывает его по очередям, так что одно и то же сообщение можно разослать в несколько очередей, не трогая producer’а.

Такой подход хорошо служит там, где нужно надёжно доставить задачу, например при отправке писем, обработке платежей.
Потоковая передача событий
В streaming-модели роли немного меняются: Producer пишет события в поток, сам поток хранит их заданное время и не удаляет после чтения, а Consumer Group читает поток независимо от других групп и хранит собственную позицию — offset.

Здесь появляется главное преимущество: повторное чтение истории. Новый consumer может подключиться сегодня и начать читать события не только с текущего момента, но и с нужной позиции в истории.
Несколько независимых систем могут читать один и тот же поток одновременно, каждый в своём темпе. Сам поток при этом становится не просто механизмом доставки, а журналом того, что происходило в системе. Это открывает дорогу к event-driven архитектурам, аудиту и event sourcing.
Хороший пример — Apache Kafka. Ключевое отличие от классической очереди: сообщение не удаляется после чтения. Каждый потребитель хранит свой offset, поэтому одно и то же событие независимо читают сразу несколько систем, отрабатывая его в своем темпе и не мешая остальным.
Вернемся к тому же заказу из интернет-магазина. Продюсеру, который пишет событие, не нужно знать, сколько у события потребителей: новая система просто подключается к топику со своей группой, и остальных это никак не задевает. А если потребитель упал или в нем нашли баг, он может перечитать сообщения с нужного offset, пока они не вышли за retention.
Serverless меняет модель эксплуатации
Сервисы RabbitMQ и Kafka можно поднять самостоятельно. Правда, в таком случае, всё находится под вашим контролем — от планирования ресурсов до восстановления после отказа и масштабирования под нагрузку. Managed-сервисы снимают значительную часть рутины. Провайдер разворачивает и обслуживает кластер за вас, но сама модель остаётся во многом ресурсной: заранее определить размер и конфигурацию кластера и платить за выделенную инфру приходится независимо от того, проходят через неё в минуту миллионы сообщений или одно.
А вот в случае с Serverless, пользователь не управляет брокерами и серверами, не выбирает их количество и не планирует инфраструктурную ёмкость. Платформа сама масштабирует внутренние ресурсы под фактическую нагрузку и отвечает за их отказоустойчивость. Для очередей и стриминга это особенно интересно. Не нужно держать кластер, рассчитанный на пик, который случается несколько раз в неделю или месяц, а также не нужно заранее гадать, понадобится завтра три брокера или десять. Инфраструктура начинает больше походить на сервис: создал поток, отправил сообщение, получил сообщение. Мы в MWS Cloud Platform решили посмотреть, можно ли построить Kafka-совместимый сервис именно по такой модели.
MWS Serverless Queue
MWS Serverless Queue реализует Kafka-совместимый протокол, но внутри построен на принципиально другой архитектуре хранения.
В классической Kafka брокер одновременно участвует в обработке Kafka-протокола и хранит данные на локальных дисках. Из-за этого Compute и Storage тесно связаны друг с другом. Если брокер исчезает, системе нужно восстановить нужное количество копий данных на других узлах. Если кластер масштабируется, данные могут потребовать перераспределения между брокерами.
В MWS Serverless Queue эти роли разделены. Брокеры становятся stateless, то есть не хранят между запросами состояние, которое было бы жалко потерять, и не владеют долговременными копиями сообщений. Payload сообщений хранится в объектном хранилище S3, а метаданные — отдельно. То есть Compute можно масштабировать независимо от Storage. Данные больше не принадлежат конкретному брокеру. За долговечность payload отвечает объектное хранилище, поэтому нам не нужно держать по несколько копий каждого сообщения на локальных дисках брокеров, как это делает Kafka через коэффициент репликации, и перемещать данные партиций при изменении состава брокер-слоя. Здесь можно почитать о том, как устроено наше объектное хранилище.
Подобная архитектура уже существует на рынке, например, компания WarpStream построила Kafka-совместимую streaming-платформу поверх object storage без локальных дисков на брокерах. В 2024 году компанию приобрела Confluent — компания, основанная создателями Apache Kafka. То есть zero-disk streaming уже не просто эксперимент или идея, а целое направление развития streaming-инфраструктуры.
Упрощённая схема нашей модели выглядит следующим образом:
Broker: принимает kafka-запросы от клиентов и реализует kafka-совместимый протокол, сам брокер stateless — долговременные данные в нём не хранятся.
Store: получает сообщения от broker, агрегирует их в блоки и записывает в S3.
Meta: хранит транзакционные метаданные: инфо о топиках, блоках, offset’ах, расположении данных, состоянии потоков.

Ниже рассмотрим детально, что под капотом, как это работает и почему пошли именно таким образом. А под капотом мы имеем семь Go-сервисов, развёрнутых в Kubernetes и разделённых на два слоя.

Data Plane — слой, через который идёт пользовательский Kafka-трафик:
Broker: терминация Kafka wire-протокола, аутентификация и авторизация клиентов.
Store: агрегация батчей сообщений и запись блоков данных в объектное хранилище.
Meta: управление метаданными в реляционной СУБД, retention и сборка мусора.
Group: координатор потребительских групп.
Control Plane — управление ресурсами на уровне облачной платформы:
Api: HTTP/REST-фасад: создание топиков, управление конфигами, интеграция с IaC и Cloud Console.
Quota: проектные квоты и интеграция с платформенным IAM.
И отдельно стоит седьмой сервис — Test. Это встроенный поведенческий тестер инвариантов, который разворачивается вместе с остальными и постоянно работает внутри кластера рядом с боевым трафиком. О нём поговорим подробнее в конце статьи.
Все stateful-сервисы (Meta, Group, API, Quota) используют одну и ту же инсталляцию PostgreSQL — четырёхузловой кластер 2+2 (по два узла в каждой из двух зон доступности) под управлением Patroni. Собственной выделенной БД нет ни у одного сервиса: их логические схемы сосуществуют в общей базе.
Решение может показаться спорным, но оно осознанное: одна СУБД — это одна операционная модель, один failover-сценарий, один набор дашбордов. И что важнее, не возникает задачи согласования состояния между разными хранилищами: offset потребительской группы и коммит блока с данными лежат в одной базе и могут фиксироваться в одной транзакции.
Брокер, который ничем не владеет
Главная особенность брокера в том, что он не владеет ни одной партицией. Любой инстанс брокера может принять Produce-запрос в любую партицию любого топика и переслать его на ближайший Store. Восстановление порядка батчей в логической партиции выполняется не на брокере, а на стороне Meta — через присвоение монотонных offset'ов и фиксацию ссылок на блок и смещение внутри него.
Чтение симметрично: брокер запрашивает у Meta карту батчей для нужного диапазона offset'ов и затем по этим ссылкам забирает данные из S3 через Store. Отсутствие закрепления партиций за брокером даёт два эффекта, ради которых всё и затевалось:
1. Добавление и удаление подов — это обычное масштабирование в Kubernetes. Никакого перемещения данных. Операция partition reassignment, знакомая всем, кто эксплуатировал Kafka, из эксплуатации просто исчезает.
2. При падении пода клиент переподключается на другой через балансировщик и продолжает работу. Устойчивого состояния, которое надо было бы восстановить, у брокера нет.
За это решение здесь мы платим дополнительным сетевым хопом между брокером и хранилищем, и к цифрам мы ещё вернёмся.
Чтобы этот хоп не превращался в два или три, в горячем пути брокер опирается на кеш метаданных топиков и их конфигурации, привязанный к конкретному клиентскому соединению. Это сознательный отказ от глобального кеша с его сложной инвалидацией при одновременной работе с большим числом клиентов разных арендаторов. В типичном случае Produce не порождает дополнительных round-trip'ов за метаданными вообще.
Свой генератор кода Kafka-протокола
Отдельная история, как вообще реализован Kafka Wire Protocol. Мы не писали его руками и не брали готовую библиотеку, вместо этого написали генератор кода.
Генератор обрабатывает JSON-спецификации Kafka API — те самые, что лежат в репозитории Apache Kafka, у нас их 199 файлов, и формирует на выходе сериализаторы и десериализаторы структур запросов и ответов по бинарному формату Kafka: variable-length encoding для строк и массивов, обработка tagged fields, согласование версии запроса с заявленной версией клиентского API. Для каждого Kafka API key поддерживаются все версии, которые брокер объявляет в ответе ApiVersions. Наверняка у вас возник вопрос: «Почему не взять franz-go, sarama или segmentio/kafka-go»? У нас было три причины:
Контроль над набором поддерживаемых API. Мы генерируем код только для тех методов, которые реально поддерживаем. Это ощутимо сокращает поверхность кода и упрощает аудит.
Независимость от сторонних библиотек. Все перечисленные библиотеки реализуют клиент Kafka, а не серверную часть. Адаптировать их под сервер — значит поддерживать форк.
Возможность эволюционировать вместе с системой. Меняется состав полей у какого-то API key — обновляем спецификацию и перегенерируем код.
Как сообщения превращаются в объекты S3
Store принимает по gRPC батчи сообщений от всех инстансов брокера, агрегирует их и записывает в объектное хранилище. И вот здесь принципиальное архитектурное решение, на которое стоит обратить особое внимание:
Один S3-объект может содержать сообщения, отправленные разными продюсерами в разные топики, принадлежащие разным арендаторам.
Звучит контринтуитивно, но мотивация чисто экономическая. Представим сценарий «тысяча арендаторов по 1 МБ/с». Если формировать отдельный объект в S3 на каждого арендатора, получим чрезмерное количество PUT-запросов (за которые платим) при низком коэффициенте утилизации объектов. Мультитенантный блок эту проблему снимает.
Формат блока сделан максимально простым — это конкатенация подготовленных брокером батчей, без распаковки записей на стороне Store. Границы батчей внутри блока (байтовое смещение начала и количество записей), хранятся в Meta, в реестре блоков.
То есть S3-объект у нас — плоский контейнер байтов. Вся логическая структура (топик, партиция, offset'ы) восстанавливается через индекс в PostgreSQL. S3 ничего не знает про Kafka.
Жизненный цикл блока
Агрегатор удерживает текущий формируемый блок и набор фоновых писателей. На каждую входящую запись он дописывает данные в текущий блок и возвращает promise, в который позже придёт результат. Блок закрывается по одному из двух условий: наполнился до предельного размера (у нас это порядка 4 МиБ) либо истекло время его жизни (десятки-сотня миллисекунд). После закрытия блок уходит в очередь к пулу фоновых писателей — каждый выполняет PUT в S3 и затем коммитит блок в Meta.
Кеш блоков: как не ходить в S3 на чтение
Ещё одно место, где стараемся не платить лишнего, — это путь Fetch. Каждое чтение по определению означает GET в объектное хранилище, а это и сетевой round-trip, и деньги за запрос. Поэтому Store держит перед S3 LRU-кеш готовых блоков. Читают его только на Fetch, а наполняется он с двух сторон, и вторая интереснее первой.
Первый способ наполнения очевидный: промахнулись, сходили в S3, положили блок в память. Промахи при этом защищены singleflight: если десять потребителей одновременно запросили один и тот же блок, GET в S3 уйдёт ровно один, остальные девять дождутся его результата. Учитывая, что блок у нас мультитенантный и в него попадают сообщения разных топиков и арендаторов, одновременный интерес нескольких читателей к одному объекту не редкий случай, а скорее норма.
Второй способ интереснее: блок попадает в кеш сразу после успешного PUT, то есть только что записанные данные уже лежат в памяти Store и первого чтения дожидаться не нужно. Это попадание в основной сценарий работы очереди, потому что типичный потребитель читает хвост потока, то есть то, что записали секунду назад. Такой Fetch обслуживается из памяти, и обращения к S3 не происходит вовсе. В объектное хранилище мы реально идём тогда, когда потребитель отстал или перечитывает историю.
Meta: индекс поверх плоского хранилища
Если S3 у нас — контейнер байтов, то Meta — то, что превращает эти байты обратно в Kafka. Здесь живёт реестр топиков с их конфигурацией, реестр партиций с latest- и earliest-offset, реестр блоков и зафиксированные потребительскими группами offset'ы.
Главная горячая нагрузка приходится на CommitBlock: после того как Store записал блок в S3, нужно атомарно зафиксировать новые батчи и сдвинуть partition offset для каждой пары «топик, партиция», представленной в блоке. Только после успеха этой операции брокер возвращает клиенту положительный ack.
В первой реализации брокер коммитил батчи напрямую и каждый CommitBlock оборачивался в отдельную транзакцию PostgreSQL. Нагрузочное тестирование довольно быстро подтвердило две неприятные гипотезы:
Задержка коммита растёт нелинейно с числом батчей в одной транзакции.
Под нагрузкой деградация усиливается из-за конкуренции за блокировки в PG.
Чтобы переложить эту нагрузку на более выгодный для СУБД режим, в Meta появился шардированный агрегатор batch-операций. Работает он так:
все входящие элементы коммита распределяются по шардам через хеш от ключа партиции;
каждый шард обслуживается отдельной горутиной с собственной очередью;
шард накапливает элементы в пакет, пока не выполнится одно из условий — набралось max_batches_in_transaction элементов либо истёк commit_interval;
после этого весь пакет коммитится одной транзакцией PostgreSQL.
Хеширование по ключу партиции здесь принципиально: элементы одной партиции всегда попадают в один шард, что и даёт корректный сериализационный порядок присвоения offset'ов внутри партиции.
Retention и сборка мусора живут здесь же как набор независимых периодических задач со своими периодами и лимитами: удаление батчей по истечении retention, пометка опустевших блоков, физическое удаление их из S3, чистка устаревших служебных записей. Независимые конфигурации тут необходимы: все эти задачи ходят в ту же общую PostgreSQL, которая обслуживает горячий путь, и синхронный запуск всех сразу создавал бы пиковую нагрузку на базу.
Group: координатор потребительских групп
Протокол потребительских групп: JoinGroup, SyncGroup, Heartbeat, LeaveGroup, OffsetCommit, OffsetFetch, OffsetDelete — вынесен в отдельный сервис по двум причинам:
Во-первых, другой профиль нагрузки. Запросы редкие, но критичны к корректности: любой неправильный rebalance ломает базовый инвариант «партиция в группе обслуживается ровно одним потребителем».
Во-вторых, отдельный сервис проще масштабировать независимо от брокеров и изолировать его таймеры от Produce/Fetch-трафика.
Состояние всех групп хранится в общей PostgreSQL, и источник истины — именно БД. Локальные копии в памяти подов служат кешем. Отсюда важное свойство: успешно подтверждённый коммит offset'а переживает падение любого пода — он уже зафиксирован в базе.
Что с семантикой acks
Это то отличие от Apache Kafka, которое важно понимать при интеграции. Apache Kafka трактует acks=all как «все реплики ISR»: запись подтверждается клиенту после доставки на все реплики в текущем In-Sync Replicas. У нас реплика на уровне Kafka одна, и это не настраиваемое значение. Реальная репликация живёт внутри S3 — хранилище платформы реплицирует объекты внутри зоны и между зонами.
Идемпотентный продюсер по KIP-98 поддержан: клиент с enable.idempotence=true получает дедупликацию повторных отправок в пределах пары ⟨topic, partition⟩.
Подробнее о сервисе Test
Теперь, как и обещали, расскажем про седьмой сервис Test. Он развёрнут одновременно с остальными и непрерывно проверяет корректность работы системы методом «чёрного ящика»: запускает реальные Kafka-клиенты (Sarama, segmentio/kafka-go) и сравнивает наблюдаемый поток событий с формально описанными инвариантами.
Сценарии охватывают типичные паттерны нагрузки и режимы отказов: базовая запись и чтение, последовательное подключение и отключение потребителей, поочерёдные остановки, проверка истечения retention, накопительные эффекты, сообщения большого размера, поведение при включённом сжатии, поведение идемпотентного продюсера при искусственно прерываемых соединениях.
Инвариантов около десятка, и они разделены на два уровня — предупреждающие и ошибочные. Например: сообщение прочитано дважды по одному и тому же offset; сообщение прочитано, но факта записи не было; offset закоммичен дважды; после коммита пришло чтение по тому же смещению. Любое нарушение инкрементирует Prometheus-метрику и автоматически попадает в систему алертинга платформы.
Такие нарушения зависят от совпадения нагрузки, сетевых задержек и фоновых задач, поэтому Test работает непрерывно, а не по расписанию. Самое интересное обычно происходит на стыках: retention удаляет батчи, пока потребитель читает тот же участок лога, блок закрывается в момент ребалансировки группы, сборка мусора идёт одновременно с коммитом блока в Meta. В итоге в алертах у нас есть не только технические сигналы вроде выросшей задержки PUT в S3, но и поведенческие: очередь потеряла сообщение или отдала его дважды.
Что даёт разделение Compute и Storage
Главная идея здесь не столько в использовании S3, а в том, что брокер перестаёт быть владельцем данных, когда масштабирование и восстановление кластера напрямую связано с перемещением данных между узлами. В serverless-архитектуре эти функции разделены: брокер можно масштабировать как Stateless Compute, а данные продолжают лежать в общем объектнике.
Но у данной архитектуры есть своя цена — путь через объектник длиннее, чем запись на локальный диск брокера. Дополнительные сетевые запросы, запись блоков в S3 и получение метаданных увеличивают latency. Поэтому serverless streaming не пытается быть лучшим решением абсолютно для каждого сценария. Это trade-off. С одной стороны — более высокая latency по сравнению с классической Kafka, с другой — отсутствие собственного кластера, автоматическое масштабирование, разделение compute и storage, меньше рутины с инфрой и оплата по факту использования сервиса.
Немного цифр: что имеем на текущий момент
Мы гоняли систему через OpenMessaging Benchmark Framework на профилях от 10 тысяч до 500 тысяч сообщений в секунду, с размерами сообщения от 100 байтов до 1 МиБ, на топике с 100 партициями, 100 продюсерами и 100 потребителями в одной группе.
Что важно зафиксировать: целевую пропускную способность система удерживает на всех профилях, включая 20 000 сообщений по 100 КиБ, — это около 1,9 ГиБ/с. Задержка при этом устойчиво измеряется сотнями миллисекунд: produce-latency в среднем 250–350 мс, end-to-end p99 — от 400 мс на лёгких профилях.
Интересная деталь — latency почти не зависит от размера сообщения. На профиле 10k×1 КиБ (около 10 МиБ/с) и на профиле 5k×100 КиБ (около 490 МиБ/с) цифры одного порядка. Причина в том, что узкое место — не байтовая пропускная способность, а число операций commit в общую PostgreSQL. Время регистрации блока слабо зависит от размера полезной нагрузки.
Это ровно та цена, о которой шла речь выше, и она полностью соответствует целевому классу сценариев. Если вам нужен sub-100 мс p99 на мелких сообщениях — архитектурно выигрывают системы с локальными NVMe-дисками брокеров, и это нормально: мы решаем другую задачу.
Как понять, что нужно в моменте, и сделать выбор
Когда serverless не подойдёт. Представим биржу криптовалют. Через систему постоянно проходят данные по рынку и тики по сотням торговых пар. Нагрузка высокая, постоянная, она предсказуема и работает 24/7. Задержка здесь один из ключевых параметров. В такой ситуации кластер, рассчитанный под стабильную нагрузку, будет быстрее и экономически эффективней serverless-архитектуры. Здесь managed kafka или self-managed выглядят более логично.
Когда serverless — идеальный вариант. Другой пример — CI/CD-pipeline. Предположим, что система генерирует несколько десятков событий в час — сборка началась/закончилась, тесты прошли, деплой стартовал/закончился. Большую часть времени ничего не происходит. Может быть, утром придёт несколько сообщений, а потом тишина, а может быть, внезапно начинается серия параллельных деплоев. Поднимать кластер избыточно, так как платить круглосуточно, ради эпизодической нагрузки совсем не хочется. И вот здесь Serverless естественно ложится на профиль: инфраструктура существует для пользователя как сервис, а масштабирование происходит внутри платформы.
Когда решения работают вместе. На практике вообще не обязательно выбирать что-то одно. Представим маркетплейс, где есть примерный цикл заказа:
заказ создан
платёж пришёл
на складе зарезервировали товар
заказ отправлен
заказ получен
Это стабильная и давно работающая система, всем известна нагрузка. Но рядом могут существовать другие события — маркетинговые кампании, массовые рассылки, фоновые интеграции и т. д. Раздувать основной кластер ради таких нагрузок не обязательно, можно просто их выделить в отдельные serverless-потоки. В итоге получаем гибридную архитектуру, где стабильная нагрузка живёт на выделенном кластере, а редкая и трудно прогнозируемая — в serverless.
Итог
Классическая очередь нужна, когда надо раздать работу worker’ам и выполнить каждую задачу один раз. Event stream нужен, когда важна история, повторное чтение и несколько независимых читателей. А serverless — когда понимаете, что нужна очередь, но голова не очень хочет болеть из-за инфраструктуры и управления.
Приходите попробовать MWS Serverless Queue, сейчас сервис доступен в Preview. Мы поддерживаем все популярные Kafka-совместимые клиенты (AKHQ, Kafka UI, Kafdrop), библиотеки: sarama, segmentio/kafka-go, librdkafka и CLI-утилиты.
Если у вас есть сценарий, который хочется проверить без отдельного Kafka-кластера, оставьте заявку на доступ прямо в консоли облака MWS Cloud Platform, а с обратной связью ждём вас в нашем сообществе в Telegram.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.