Привет Хабр! Довольно часто на обсуждениях/созвонах слышу о том, что заказчики хотят реализовать очередность доставки с порядком. В этой статье я расскажу, почему требование FIFO зачастую является началом дорогого приключения.

Введение: парадокс порядка в асинхронном мире

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

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

1. Почему это сложно в реализации?

Сначала звучит достаточно легко , нам необходимо просто читать сообщения по очереди, а потом в той же очереди их отдавать. Но на самом деле, как только мы выходим за рамки одного потока, могут начаться проблемы:

  • Если у вас 10 внешних систем читают из одной очереди, они никогда не закончат обработку в том же порядке, в котором начали. У кого-то тяжеловесный пакет в обработке, у кого-то произошли внутренние ошибки и так далее. В конечно счете, данные могли прийти в одном порядке, а уйти в другом.

  • В строгом FIFO, если одно сообщение вызывает ошибку, то оно должно заблокировать все остальные потоки, то есть работаем по принципу транзакций. Соответственно, весь этот хвост данных будет ждать, когда вы разберетесь с инцидентом не отправите данные повторно в том же порядке. Разумеется, такие мероприятия приводят к простою всей системы, при том что сегодня большая часть бизнеса работает 24/7 и им, соответственно, невыгоден простой.

  • В инструментах вроде RabbitMQ для строгого порядка часто используют Single Active Consumer. Это дает необходимую последовательность, но при всем этом лишает вас горизонтального масштабирования. Если нагрузка на очередь начнет расти, увеличатся объемы, то вам придется терпеть, потому что теперь вы заперты в одном потоке.

2. Где FIFO хорошо работает

FIFO хорошо себя показывает, например, в банковских сферах. В таких сферах без строго порядка все может развалиться. Классический пример - банковские транзакции.

Представим ситуацию, что у нас 1000 рублей на карте. Мы пришли в магазин что-то купить, после покупки получаем уведомления:

  • Списание 1000 рублей (Спасибо за покупку)

  • Начисление 50 рублей (Кешбэк)

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

FIFO хорошо работает в тех задачах, где последовательность очень хорошо продумана и по другому реализовать это нельзя, ни с точки зрения бизнеса, ни с точки зрения технической реализации.

3. Где FIFO плохо работает

Бизнес считает , что первым пришел - первым ушел - это универсальный стандарт, который подходит везде. Теперь возьмем в пример ритейл, особенно ритейл в категории "Ультра фреш".

И так, у нас есть склад с фруктами. В понедельник привезли партию винограда со сроком годности 5 дней. Во вторник привезли партию винограда со сроком годности 3 дня(потому что поставщик привез товар постарше). Если мы будем следовать принципу FIFO, то система сначала заставит нас полностью отгрузить партию понедельника, поскольку она пришла первой.

Таким образом, в текущей ситуации FIFO работает уже достаточно плохо, то есть в тех сценариях, где уже нужно просчитывать бизнес-логику. у Банковский операций строгий порядок и регламент. В примере же с ритейлом мы понимаем, что факторов намного больше, а также добавляется самый опасный - человеческий. На основе такого фактора очень трудно реализовать логику очередности.

4. Как это реализуют популярные системы

Apache Kafka: Иллюзия порядка через партиции

Kafka - стандарт индустрии, но её FIFO очень специфичен. Она гарантирует порядок только внутри партиции.

  • Механика: Мы используем ключ (например, user_id). Хэш от ключа всегда отправляет события одного юзера в одну и ту же партицию.

  • Проблема «Ядовитого сообщения» (Poison Pill): Если одно сообщение в партиции вызывает ошибку, оно блокирует всё. Весь хвост очереди стоит и ждет, пока вы не почините код или не скипнете это событие. В итоге - простой всей процессов.

  • Ребалансировка: Это самый "грязный" момент. Когда вы добавляете нового потребителя, Kafka перераспределяет партиции. В этот момент старый потребитель может еще доваривать сообщение, а новый уже начнет читать следующее из той же пачки.

RabbitMQ и Single Active Consumer

Вы объявляете, что из очереди может читать только один активный поток.

  • Плюсы: Железная последовательность.

  • Минусы: Никакого горизонтального масштабирования. Если ваша очередь пухнет, вы не можете просто докинуть воркеров. К тому же, если активный потребитель отвалится, RabbitMQ будет ждать таймаут (heartbeat), прежде чем передать эстафету следующему. Это могут быть десятки секунд простоя.

5. Итог

Прежде чем внедрять строгий порядок, задайте бизнесу простой вопрос: «Что самое страшное случится, если два сообщения поменяются местами?».

Если действительно FIFO является необходимостью, то помните, что лучше настраивать очередность получения сообщений и очередность отправки сообщений на системах‑источниках и системах‑получателях, а не на стороне интеграционных решений, таких как брокеры и ESB.

Всем большое спасибо!