Привет Хабр! Довольно часто на обсуждениях/созвонах слышу о том, что заказчики хотят реализовать очередность доставки с порядком. В этой статье я расскажу, почему требование 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.
Всем большое спасибо!
Комментарии (6)

akakoychenko
08.03.2026 06:46Я бы сказал, что и в мире FIFO переоценен, и его суют, куда не попадя потому, что кто-то когда решил, что это хорошая идея. К примеру, та же очередь на кассу в супермаркете, к врачу, или очередь на рассмотрение заявления на ПМЖ.
Если обобщить, то живая очередь означает лишь функцию выбора следующего заказа (заказчика) при освобождении обслуживателя, которая ранжирует заказы по уже просранному времени в этой очереди. То есть, то абсолютно неконструктивному критерию, не имеющему ценности. Да, в этом есть пара плюсов - например, ни один заказ не может ждать обработки вечно. Или, есть "справедливость" между заказчиками. Или то, что при возникновении несоразмерно большой в сравнении с получаемой ценность очереди, происходит саморегуляция и часть заказчиков уходит получать услугу в другом месте.
Проблема в том, что при достижении определённого размера очереди услуга теряет смысл в принципе. Хороший пример - западная Европа, где можно ждать 6 месяцев визита к стоматологу и 2 года визита к психиатру. Объективно, при таком сроке можно лишь констатировать, что услуга в должном качестве не предоставляется в принципе (ходить с больным зубом 6 мес неадекватно, а человек с проблемами с головой может создать достаточно проблем себе и окружающим за 2 года). Причём, за счёт того, что ждут все, она не предоставляется в должном качестве никому.
И, если так подумать, то, если отказаться от концепции того, что заказ, просравший время в очереди, важнее, то можно сделать эффективные решения под целевую функцию каждого конкретного случая.
Приведу пример: олдскульные таксисты в заповедниках, где это ещё есть, например, на Мадейре, стоят длинными вереницами машин в специально отведенных местах. И очередного клиента обслуживает первый в этой очереди. После чего остальные подкатываются на 1 корпус вперёд (иногда даже мускульной силой, ибо зачем заводить мотор ради 5 метров, а размяться не помешает). Убер заменил это логичным алгоритмом "кто ближе, тот и папа" и рассредоточением машин по городу.
И так можно разобрать любой кейс. Та же стоматология, например. Либо надо решать проблему радикально, расширяя количество стоматологов. Либо, если на уровне принятия решения нет такой возможности, то перестраивать процесс, например, делая запись не через интернет, а через медсестру, делающую первичный осмотр и отказывающую в записи всем, у кого недостаточно критичный случай. Или супермаркет - можно иметь разные кассы с разной ценой. Например, 8 касс с базовой ценой. 1 с +5%, 1 с +10%. Тогда человек, ценящий свое время, сможет решить задачу быстро. Ну, или совсем теоретический пример, когда вместо очереди стоит толпа, и при освобождении кассы происходит аукцион. Победитель платит в кассу цену товара + победную ставку за обслуживание

vlsnake
08.03.2026 06:46В примере с виноградом бизнес-логика складского учета почему-то переложена на очередь FIFO - неудачный пример. Какой именно виноград взять со стеллажа (понедельника или вторника) решает же не очередь сообщений.
В примере с банком - если у нас 10 млн пользователей, нам не нужно выстраивать операции Васи в очередь перед операциями Пети. Т.е. строгий глобальный FIFO редко нужен. Нужна консистентность в рамках одной сущности (аккаунта пользователя), т.е. "простой" будет только по user_id. Уточню - Kafka гарантирует порядок только при условии, что потребитель не читает сообщения параллельно из одной партиции (число консюмеров <= числа партиций).
А в целом, спасибо, идея масштабируемости - это правильный способ выявить требования и аргумент, который можно приводить заказчикам.

OlegZH
08.03.2026 06:46И так, у нас есть склад с фруктами. В понедельник привезли партию винограда со сроком годности 5 дней. Во вторник привезли партию винограда со сроком годности 3 дня(потому что поставщик привез товар постарше).
Разве, в этом случае, не будет двух различных позиций номенклатуры и двух отдельных карточек в интернет-магазине с явным указанием срока годности?
Но, вообще говоря, учёт партий — важная штука. В первую очередь, конечно же, следует реализовывать партию с меньшим сроком годности.

zarfaz
08.03.2026 06:46Как бы ни логично это звучало, а что еще хуже оно так реализовано во многих онлайн фуд техах, это сильно подрывает доверие к онлайн заказу продуктов.
Изза этого я до сих пор хожу ножками, потому что на полке я могу взять по принципу самое свежее, а не самое залежалое

baimkin
08.03.2026 06:46Ну на примере винограда вы на прилавке не сможете отличить самое свежее, от чуть менее свежего, если оно прям явно не испорчено. Может такое быть что старая партия будет выглядеть свежее и красивее чем новая.
zarfaz
Я как проджект был тем самым «бизнесом» который просил просто сделать по fifo, а тех лид мне на бумажке популярно объяснял примерно то же самое что в статье :)
Сталкивался с такой же проблемой при расчете доступности товара к продаже. С одной стороны два сообщения «товар заблокирован» и «товар снова доступен» нельзя перепутать местами, иначе товар останется не в том статусе. С другой стороны, нагрузка была просто гигантская и нужно было много партиций Кафки.
Решение - партицию куда отправить сообщение выбирали по хэшу артикула. Появилась гарантия что один артикул попадётся строго в одной партиции и будет обработан последовательно, а с другой стороны нет проблем перемешивать сообщения от разных артикулов.
В довесок еще и потребитель навесил логику обработки сообщений по временной метке, что бы игнорировать устаревшие смены состояний.