# Оставь сообщение, не жди

Экспресс-курс об очередях и событийных системах. 21 понятие — Synchronous Call, Message Queue, Asynchronous, Producer и Consumer, Broker, Competing Consumers, Command и Event, Pub/Sub, Event-driven, Load Leveling, Idempotency, Eventual Consistency, Dead-Letter Queue и другие — у каждого английское название, образ, схема и карточка с примером. В конце — карта связей и шпаргалка на одной странице. Главная мысль: расцепить работу во времени — это как системы остаются быстрыми и переживают сбои.

- **Author:** Фёдор Чищин
- **Level:** intermediate
- **Reading time:** ~35 минут чтения
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/ru/learn/queues-express

---

## 1. Проблема прямого вызова

Чтобы увидеть, зачем существуют очереди, сперва почувствуй боль простой альтернативы: один сервис зовёт другой и ждёт его. Это работает до того мига, когда становится по-настоящему важно.

### Карта: 21 понятие в шести группах

> Карта метро: сначала видишь линии целиком, потом едешь по станциям.

Сначала **проблема** — почему прямой вызов подводит. Дальше по главе на группу: **очередь** (сама идея), **роли** (кто пишет, кто читает, кто хранит), **команды и события** (два вида сообщений), **выгоды** (за что платим) и **сложные части** (что ломается). Последняя глава — когда очередь брать, а когда нет, карта связей и шпаргалка.

_Diagram: Шесть групп понятий — по ним идут главы 1–6. Глава 7 — как выбирать и шпаргалка._

### Как читать каждое понятие

> Паспорт: одинаковые поля у всех, поэтому их легко сравнивать.

У каждого понятия дальше одно и то же: **английское название** (запомни его — по нему ищут в документации брокеров и обсуждают на собеседованиях), образ из жизни, **схема** и **карточка**: _проблема_, _идея_, _пример_, _цена_ и _связи_ с другими понятиями. В конце — карта связей и шпаргалка.

### Synchronous Call — прямой вызов связывает два сервиса

> Позвонить кому-то и оставаться на линии, пока он не закончит задачу, — нельзя положить трубку, нельзя делать что-то ещё, а если он не берёт, ты застрял.

Очевидный способ для сервиса A заставить сервис B что-то сделать — **синхронный вызов** (synchronous call): A спрашивает, потом ждёт, удерживая всё, пока B не ответит. Это туго **связывает** (coupling) их — скорость A теперь заложник скорости B, и A не может продолжить, пока B не готов. Для быстрого ответа это нормально. Для медленной или тяжёлой работы A проводит время замороженным, в ожидании кого-то другого.

_Diagram: A позвал B и стоит: вся медленная работа B — это время, когда A заблокирован._

#### Synchronous Call (синхронный вызов, request–response)

- **Проблема:** Вызывающий заморожен, пока вызываемый не ответит.
- **Идея:** Спросил — ждёшь ответа, потом идёшь дальше. Самый простой способ общения.
- **Пример:** Сервис заказов зовёт сервис цен по HTTP и ждёт цену, прежде чем показать корзину.
- **Цена:** Тугая связь (**coupling**): скорость и доступность A зависят от B.
- **Связи:** Его боль лечит **Message Queue**; для мгновенных ответов он остаётся лучшим выбором.

### Failure Propagation — если вызываемый лежит, вызывающий ломается

> Звонить на склад подтвердить заказ и, раз никто не отвечает, отказываться брать любые новые заказы вообще — одна закрытая дверь клинит весь магазин.

Глубже лежит проблема сбоя. Если B лежит или перегружен, когда A зовёт, запрос A тоже падает — сбой **распространяется** (failure propagation) прямо вверх по цепочке. Один медленный или сломанный сервис может застопорить всё, что от него зависит, а всплеск трафика к B тянет A за собой вниз. Тугая связь значит, что слабость расходится, и система доступна ровно настолько, насколько её самая шаткая часть.

_Diagram: Склад упал — и ошибка поднимается вверх: заказы падают, оформление падает._

#### Failure Propagation (распространение сбоя, каскад)

- **Проблема:** Сбой одного сервиса превращается в сбой всех, кто его зовёт.
- **Идея:** Синхронная цепочка доступна ровно настолько, насколько её самое слабое звено.
- **Пример:** Склад не отвечает → заказы падают с ошибкой → оформление заказа не работает.
- **Цена:** Всплеск трафика к одному сервису тянет вниз всех над ним.
- **Связи:** Лечится буфером: **Message Queue** даёт **Resilience** — сбой становится задержкой.

### Background Work — некоторая работа не должна блокировать пользователя

> На кассе ты платишь и уходишь с чеком — ты не стоишь у кассы, пока они пакуют, отгружают и шлют тебе письмо. Медленные части случаются после того, как ты ушёл.

Куча работы не обязана завершиться до того, как пользователь получит ответ, — отправить подтверждающее письмо, сгенерировать счёт, уменьшить фото. Это **фоновая работа** (background work). Заставлять пользователя ждать всего этого медленно и бессмысленно. Ты хочешь принять запрос, ответить быстро и дать медленным частям случиться после. Прямой вызов так не может; он заставляет всех ждать всего.

_Diagram: API отвечает сразу, а письмо, счёт и фото делаются потом — из очереди._

#### Background Work (фоновая работа, background jobs)

- **Проблема:** Пользователь ждёт работу, от которой его ответ не зависит.
- **Идея:** Принять запрос, ответить быстро, медленные части сделать после.
- **Пример:** Оплата прошла — чек сразу; письмо, счёт и уменьшение фото — в фоне.
- **Цена:** Результат фоновой работы появляется не мгновенно.
- **Связи:** Делается через **Message Queue** и **Asynchronous Processing**.

> **Вопрос рамки:** «Правда ли вызывающему надо ждать завершения этого — или он просто застрял на линии, пока идёт медленная работа, от которой он не зависит?»

> Прямой вызов связывает два сервиса во времени: вызывающий ждёт и наследует медлительность и сбои вызываемого. Для медленной или несрочной работы это ловушка.

## 2. Очередь

Лекарство — перестать звать и начать слать сообщения. Очередь — это буфер посередине, что даёт одной стороне оставить работу, а другой забрать её, расцепляя их во времени.

### Message Queue — брось сообщение и иди дальше

> Оставить голосовое вместо ожидания на линии: ты говоришь, что нужно, кладёшь трубку и занимаешься своим днём — они разберутся, когда смогут.

Вместо того чтобы звать B и ждать, A пишет **сообщение** (message), описывающее работу, и бросает его в **очередь** (queue) — линию, где сообщения ждут обработки. A теперь свободен продолжить немедленно; ему всё равно, когда B до него доберётся. Сообщение — это маленький пакет «вот что надо сделать», переданный, а не выполненный на месте. Эта передача и есть вся идея.

_Diagram: A бросает сообщение и идёт дальше; сообщения ждут в очереди; B берёт, когда готов. Внизу — как может выглядеть само сообщение._

#### Message Queue (очередь сообщений)

- **Проблема:** Прямой вызов держит отправителя на линии.
- **Идея:** Описать работу **сообщением** (message) и положить его в **очередь** (queue); получатель заберёт сам.
- **Пример:** Вместо вызова почтового сервиса — сообщение `SendEmail` с номером заказа в очереди.
- **Цена:** Новая движущаяся часть, которую надо гонять и понимать.
- **Связи:** Даёт **Temporal Decoupling**; держит её **Broker**; читают её **Consumer**'ы.

### Temporal Decoupling — очередь как буфер во времени

> Лоток входящих на столе: работа копится в нём и обрабатывается ровно, так что внезапный наплыв бумаг не захлёстывает человека — он лишь делает лоток выше на время.

Очередь сидит между двумя сторонами как **буфер** (buffer), держа сообщения, пока их не обработают. Это **расцепляет их во времени** (temporal decoupling): отправитель и получатель больше не обязаны быть быстрыми, доступными или даже работающими в один момент. Работу, произведённую всплеском, можно потреблять ровно. Очередь впитывает несоответствие между тем, как быстро работа приходит, и тем, как быстро её можно сделать.

_Diagram: Слева работа приходит рывками, справа обрабатывается ровно. Очередь посередине впитывает разницу._

#### Temporal Decoupling (расцепление во времени, буфер)

- **Проблема:** Обе стороны должны быть живы и быстры в один и тот же момент.
- **Идея:** Буфер между ними: отправитель и получатель работают каждый в своё время.
- **Пример:** Всплеск заказов копится в очереди и обрабатывается ровным темпом.
- **Цена:** Работа может подождать в очереди — результат не мгновенный.
- **Связи:** Основа для **Resilience** и **Load Leveling**.

### Asynchronous Processing — не ждать результата

> Отправить письмо, а не вести разговор, — ты шлёшь его и продолжаешь, веря, что его прочтут и по нему сработают позже, без того чтобы ты стоял там.

Это **асинхронная** (asynchronous) работа: отправитель не ждёт результата. Он выстреливает сообщение и продолжает, а исход случается позже, в стороне. Ты отдаёшь мгновенный ответ прямого вызова, а взамен перестаёшь быть заблокированным. Для всего, что не требует немедленного ответа, этот размен — фундамент быстрых, устойчивых систем.

_Diagram: Сверху синхронно: A стоит и ждёт. Снизу асинхронно: A отправил сообщение и работает дальше, а worker делает задачу позже._

#### Asynchronous Processing (асинхронная обработка)

- **Проблема:** Ждать результат, который прямо сейчас не нужен.
- **Идея:** Отправил сообщение — продолжил; исход случится позже, в стороне.
- **Пример:** Отчёт генерируется в фоне; пользователь получает его, когда он готов.
- **Цена:** Нет мгновенного ответа; сложнее проследить, что произошло.
- **Связи:** Противоположность **Synchronous Call**; приносит **Eventual Consistency**.

> **Вопрос очереди:** «Могу ли я превратить это в сообщение, брошенное в очередь, — дав отправителю продолжить немедленно, а работе случиться, когда другая сторона готова?»

> Очередь — это буфер между отправителем и получателем. Брось сообщение и иди дальше — работа расцеплена во времени, сделана асинхронно, когда другая сторона готова.

## 3. Producer, consumer, брокер

Три роли заставляют очередь работать, и их именование снимает бо́льшую часть жаргона. Одна сторона делает сообщения, одна их обрабатывает, а что-то посередине держит их безопасно.

### Producer / Consumer — одни делают работу, другие её выполняют

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

Сторона, что создаёт сообщения, — **producer** (производитель, или publisher); сторона, что их обрабатывает, — **consumer** (потребитель, или worker). Producer бросает тикет; consumer берёт один, делает работу и идёт к следующему. Они не знают и не ждут друг друга — они знают только очередь. Это чистое разделение и позволяет каждой стороне строиться, масштабироваться и меняться самой по себе.

_Diagram: Producer'ы пишут в очередь, consumer'ы читают из неё. Напрямую они не знакомы._

#### Producer / Consumer (производитель / потребитель (publisher / worker))

- **Что это:** **Producer** создаёт сообщения; **consumer** забирает их и делает работу.
- **Идея:** Стороны знают только очередь, а не друг друга.
- **Пример:** Веб-приложение кладёт «уменьши фото»; worker берёт сообщение и уменьшает.
- **Подвох:** Producer не знает, когда и кем работа будет сделана.
- **Связи:** Между ними — **Broker**; много consumer'ов на одной очереди — **Competing Consumers**.

### Message Broker — брокер держит сообщения безопасно

> Почта между отправителем и получателем: она берёт твоё письмо, хранит его в безопасности и держит, пока получатель не заберёт, — чтобы ничто не потерялось, если его нет.

Посередине сидит **брокер** (message broker) — софт вроде **RabbitMQ** или **Apache Kafka**, что принимает сообщения, надёжно их хранит и доставляет consumer'ам. Это почта системы. Его надёжность — в этом суть: даже если все consumer'ы лежат, брокер держит сообщения в безопасности, пока кто-то не готов обработать, так что работа припаркована, а не потеряна.

_Diagram: Брокер принимает и надёжно хранит сообщения. Consumer лежит — сообщения ждут, а не пропадают._

#### Message Broker (брокер сообщений)

- **Проблема:** Сообщения надо где-то надёжно держать, пока их не заберут.
- **Идея:** Отдельный софт принимает, хранит и доставляет сообщения — почта системы.
- **Пример:** **RabbitMQ**, **Apache Kafka**.
- **Цена:** Ещё одна система, которую надо запускать и обслуживать.
- **Связи:** Держит **Message Queue**; на его надёжности стоит **Resilience**.

### Competing Consumers — добавь consumer'ов, чтобы идти быстрее

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

Поскольку consumer'ы просто тянут следующее сообщение, можно гонять **многих параллельно** на одной очереди — это паттерн **Competing Consumers** (конкурирующие потребители). Завал растёт? Добавь больше worker'ов, и они делят нагрузку автоматически — каждый хватает разное сообщение. Это одна из больших сил очереди: ты масштабируешь медленную часть независимо, добавляя consumer'ов, не трогая producer'ов или саму очередь.

_Diagram: Три worker'а на одной очереди: каждый берёт своё сообщение. Третьего добавили — завал тает быстрее._

#### Competing Consumers (конкурирующие потребители)

- **Проблема:** Один worker не успевает, завал в очереди растёт.
- **Идея:** Несколько consumer'ов читают одну очередь; каждое сообщение берёт один из них.
- **Пример:** Перед распродажей — с двух worker'ов уменьшения фото до десяти.
- **Цена:** Параллельная обработка ломает порядок — см. **Message Ordering**.
- **Связи:** Основа **Independent Scaling**; работает с **Command**-сообщениями.

> **Вопрос ролей:** «Кто производит эти сообщения, кто их потребляет, и мог бы я расчистить завал, просто добавив больше consumer'ов на ту же очередь?»

> Producer'ы создают сообщения, consumer'ы их обрабатывают, а брокер держит их безопасно посередине. Нужно больше пропускной? Добавь consumer'ов — они делят очередь автоматически.

## 4. События против команд

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

### Command — команда велит одному worker'у сделать задачу

> Рабочий наряд, вручённый одному конкретному отделу: «уменьши эту картинку». Он называет работу, ждёт, что ровно одна команда её сделает, и на этом всё.

Сообщение-**команда** (command) — это прямая инструкция, нацеленная на одного consumer'а: «отправь это письмо», «обработай этот платёж». Это очередь, используемая как список дел — producer знает, что должно случиться, и передаёт задачу, чтобы её сделали раз, тем, кто её вытянет. Это простейшее использование очереди: сгрузить конкретную работу, чтобы сделать её позже, ровно один раз.

_Diagram: Команда SendEmail: «сделай это». Её берёт ровно один consumer._

#### Command (команда, command message)

- **Что это:** Сообщение-приказ: «сделай это». Названо глаголом.
- **Как работает:** Producer знает, что должно случиться; задачу выполняет один consumer.
- **Пример:** `SendEmail`, `ProcessPayment`, `ResizeImage`.
- **Подвох:** Producer всё ещё знает, какая работа нужна, — связь остаётся.
- **Связи:** Противоположность **Event**; часто разбирается через **Competing Consumers**.

### Event — событие объявляет, что что-то случилось

> Объявление на доске для всей компании: «только что зарегистрировался новый клиент». Оно никому не велит, что делать, — кто заинтересован, реагирует по-своему.

**Событие** (event) — другое: оно констатирует факт о прошлом — «заказ размещён», «пользователь зарегистрирован» — не говоря, кто что должен делать. Producer просто объявляет его и не знает и не заботится, кто слушает. Может, никто не среагирует; может, пять сервисов. Это переворачивает отношение: вместо того чтобы командовать конкретному worker'у, ты вещаешь, что что-то истинно, и даёшь заинтересованным сторонам решать.

_Diagram: Событие OrderPlaced: «это случилось». Кто его слушает — producer не знает._

#### Event (событие, event message)

- **Что это:** Факт о прошлом: «это случилось». Назван в прошедшем времени.
- **Как работает:** Producer объявляет и не знает, кто слушает: ноль, один или пять сервисов.
- **Пример:** `OrderPlaced`, `UserRegistered`.
- **Подвох:** Труднее понять, что произойдёт в итоге, — реакции разбросаны по сервисам.
- **Связи:** Противоположность **Command**; рассылается через **Pub/Sub**; основа **Event-driven**.

### Publish/Subscribe — одно событие, много реакций

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

Эта модель вещания — **publish/subscribe** (pub/sub): producer **публикует** событие в **топик** (topic), а каждый заинтересованный consumer **подписывается** и получает свою копию. Одно событие «заказ размещён» может запустить сервис писем, сервис аналитики и сервис отгрузки разом — это **fan-out** (веерная рассылка). Публикатор даже не знает, что они существуют. Новые реакции можно добавить позже, вообще не трогая публикатора.

_Diagram: Одно событие в топике — и каждый подписчик получает свою копию: это fan-out._

#### Publish/Subscribe (pub/sub, topic, fan-out)

- **Проблема:** Одно событие нужно многим сервисам, и их список растёт.
- **Идея:** Публикуешь в **topic**; каждый подписчик получает **свою копию** (**fan-out**).
- **Пример:** «Заказ размещён» → письма, аналитика и отгрузка реагируют разом.
- **Цена:** Каждая копия обрабатывается отдельно; трудно увидеть всю цепочку реакций.
- **Связи:** Разносит **Event**'ы; на нём строится **Event-driven Architecture**.

### Event-driven Architecture — события расцепляют, кто кого знает

> Добавление нового подписчика к газете ничего не меняет в том, как газета пишется, — публикатору никогда не надо знать, кто читает.

В этом глубокая мощь **событийного** (event-driven) дизайна: producer события полностью расцеплен со всеми, кто на него реагирует. Чтобы добавить новую фичу, что откликается на «заказ размещён», ты просто подписываешь нового consumer'а — никаких изменений в сервисе заказов. Системы, построенные так, растут добавлением слушателей, а не правкой того, что слушают. Так большие системы развиваются без того, чтобы всё зависело от всего.

_Diagram: Новая фича — новый подписчик. Сервис заказов при этом не меняется._

#### Event-driven Architecture (событийная архитектура, EDA)

- **Проблема:** Каждая новая фича требует правок в сервисе, где «всё случается».
- **Идея:** Сервисы объявляют события; фичи добавляются новыми слушателями.
- **Пример:** Начислять баллы лояльности за заказ — подписать новый сервис на «заказ размещён».
- **Цена:** **Eventual Consistency** и сложнее отлаживать запрос, что скачет через сообщения.
- **Связи:** Стоит на **Event** и **Pub/Sub**; даёт **Independent Scaling**.

> **Вопрос pub/sub:** «Я командую одному конкретному worker'у сделать задачу — или объявляю, что что-то случилось, и даю любому числу сервисов реагировать самим?»

> Команда велит одному consumer'у сделать задачу. Событие объявляет факт и даёт любому реагировать — а pub/sub веером раскидывает одно событие многим, расцепляя, кто кого знает.

## 5. Почему это мощно

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

### Resilience — лежащий consumer просто задерживает работу

> Если кухня закрывается на час, тикеты заказов просто ждут на рейке — когда она открывается, повара прорабатывают завал. Ни один заказ не потерян.

Поскольку брокер держит сообщения, падение consumer'а не теряет работу — сообщения ждут, пока он не вернётся, потом обрабатываются. Сравни с прямым вызовом, где лежащий сервис означает прямой сбой. Очередь превращает «сервис лежит» из жёсткой ошибки во временную задержку. Эта **устойчивость** (resilience) — работа переживает сбой и возобновляется — часто единственная самая большая причина, почему команды тянутся к очереди.

_Diagram: Сверху: B лежит — запрос падает. Снизу: B лежит — работа ждёт в очереди и продолжится, когда B вернётся._

#### Resilience (устойчивость)

- **Проблема:** При прямом вызове лежащий сервис = ошибка у всех.
- **Идея:** Брокер держит сообщения; сбой превращается в задержку.
- **Пример:** Worker писем упал на час — письма ждут в очереди и уходят после рестарта.
- **Цена:** Пока consumer лежит, работа копится и опаздывает.
- **Связи:** Лечит **Failure Propagation**; держится на надёжности **Broker**.

### Load Leveling — впитать всплески

> Водохранилище между паводком и городом: волна вливается в водохранилище, что выпускает воду ровным, безопасным темпом ниже по течению.

Когда наплыв запросов приходит разом — распродажа, вирусный момент — очередь даёт **выровнять нагрузку** (load leveling): всплеск наполняет очередь, а consumer'ы осушают её своим ровным темпом. Медленная нижестоящая система никогда не видит всплеска, только управляемый, ровный поток. Давление завала называется **backpressure** (обратное давление). Без очереди тот же всплеск ударил бы по системе напрямую и, вероятно, опрокинул бы её.

_Diagram: Пик запросов наполняет очередь, а consumer'ы осушают её ровно. Нижестоящая система пика не видит._

#### Queue-Based Load Leveling (выравнивание нагрузки, backpressure)

- **Проблема:** Всплеск трафика бьёт по медленной системе напрямую и роняет её.
- **Идея:** Всплеск наполняет очередь; consumer'ы разбирают её своим темпом.
- **Пример:** Распродажа: тысячи заказов за минуту обрабатываются ровным потоком.
- **Цена:** Завал растёт, работа опаздывает (**backpressure**) — за длиной очереди надо следить.
- **Связи:** Стоит на **Temporal Decoupling**; завал разгребают **Competing Consumers**.

### Independent Scaling — масштабируй и развивай каждую сторону саму по себе

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

Расцепление значит, что каждая сторона может меняться **независимо** (independent scaling). Масштабируй consumer'ов вверх или вниз под спрос; перепиши consumer на другом языке; добавь совсем новый consumer, реагирующий на существующие события, — всё, не трогая producer'ов. Очередь — это стабильный шов между частями системы, а стабильные швы — вот что даёт большой системе расти и меняться по частям, а не всё разом.

_Diagram: Очередь — стабильный шов. Справа можно масштабировать, переписывать и добавлять — слева ничего не меняется._

#### Independent Scaling (независимое масштабирование и развитие)

- **Проблема:** Чтобы поменять одну часть, приходится трогать все.
- **Идея:** Очередь — стабильный шов; стороны меняются каждая сама по себе.
- **Пример:** Worker переписали на другом языке — producer'ы даже не заметили.
- **Цена:** Формат сообщений становится контрактом — менять его надо аккуратно.
- **Связи:** Даёт **Competing Consumers** и **Event-driven Architecture**.

> **Вопрос выгоды:** «Я тянусь к очереди ради устойчивости, впитывания всплесков или независимого масштабирования — конкретной выгоды, что оправдывает лишнюю движущуюся часть?»

> Очереди покупают устойчивость (работа переживает падение), выравнивание нагрузки (всплески впитываются) и независимое масштабирование — выгоды, что прямой вызов попросту не даст.

## 6. Новые сложные части

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

### At-least-once и Idempotency — сообщения могут прийти дважды

> Почта, на всякий случай, иногда доставляет копию письма, в доставке которого не уверена, — лучше дважды, чем никогда, но теперь ты можешь сработать по одной инструкции дважды.

Большинство брокеров гарантируют доставку **at-least-once** (хотя бы один раз): они скорее пошлют сообщение дважды, чем рискнут потерять его, так что consumer может изредка получить одно сообщение больше раза. Если «списать с клиента» сработает дважды — это реальная проблема. Лекарство — **идемпотентность** (idempotency): спроектировать работу так, чтобы сделать её дважды имело тот же эффект, что и раз (сперва проверь «уже списано?»). Предполагай дубликаты; делай их безвредными.

_Diagram: Брокер прислал «списать за заказ 42» дважды. Consumer сперва проверяет «уже списано?» — и второй раз просто пропускает._

#### Idempotency (идемпотентность; at-least-once delivery)

- **Проблема:** Доставка **at-least-once**: одно сообщение может прийти больше одного раза.
- **Идея:** Сделать работу так, чтобы два запуска давали тот же эффект, что и один.
- **Пример:** Перед списанием проверить «за заказ 42 уже списано?» — если да, пропустить.
- **Цена:** Нужно где-то помнить, что уже сделано.
- **Связи:** Обязательна для любого **Consumer**; вместе с **Dead-Letter Queue** делает ретраи безопасными.

### Message Ordering — порядок не гарантирован

> Два письма, отправленные подряд, могут прийти в любом порядке, — так что если шаг два появляется раньше шага один, получатель в замешательстве.

При многих сообщениях и многих параллельных consumer'ах сообщения не обязательно обрабатываются в порядке отправки. Если «обнови адрес» и «удали аккаунт» приходят не по порядку, получаешь бессмыслицу. Некоторые системы сохраняют порядок внутри категории ценой усилий; часто дешевле спроектировать работу, что не зависит от строгого порядка. Так или иначе, **никогда не предполагай порядок** (message ordering), если специально его не устроил.

_Diagram: Отправили «обнови адрес», потом «удали аккаунт». Обработали наоборот — и обновляем аккаунт, которого уже нет._

#### Message Ordering (порядок сообщений)

- **Проблема:** Параллельные consumer'ы обрабатывают сообщения не в порядке отправки.
- **Идея:** Не полагаться на порядок — или явно устроить его внутри категории.
- **Пример:** «Удали аккаунт» обработали раньше «обнови адрес» — получилась бессмыслица.
- **Цена:** Строгий порядок стоит усилий и мешает параллельности.
- **Связи:** Плата за **Competing Consumers**; соседствует с **Eventual Consistency**.

### Eventual Consistency — результат согласован в итоге

> Новость, расходящаяся по офису: пару мгновений одни знают, а другие нет, пока слух не дойдёт до всех и они снова не согласятся.

Поскольку реакции случаются асинхронно, система **согласована в итоге** (eventually consistent): сразу после события разные части могут кратко расходиться — заказ существует, но аналитика его ещё не сосчитала. Она нагоняет за мгновения, но «мгновенно согласовано везде» ушло. Ты проектируешь вокруг этого зазора, показывая пользователям разумные промежуточные состояния, а не предполагая, что каждая часть обновляется в один миг.

_Diagram: Заказ уже есть, а аналитика его ещё не сосчитала. Короткий зазор — потом все снова согласны._

#### Eventual Consistency (согласованность в итоге)

- **Проблема:** После события части системы на миг видят разное.
- **Идея:** Принять зазор: все части догонят и согласятся — чуть позже.
- **Пример:** Заказ создан, а в отчёте его ещё нет; через мгновение появится.
- **Цена:** Надо проектировать промежуточные состояния для пользователя («обрабатывается…»).
- **Связи:** Следствие **Asynchronous Processing** и **Event-driven Architecture**.

### Dead-Letter Queue — отравленному сообщению нужно куда-то деться

> Письмо, по которому никто не может сработать — смазанное, невозможное, — нельзя бесконечно перепосылать, клиня линию. Его откладывают в особый лоток, чтобы кто-то посмотрел.

Некоторые сообщения нельзя обработать успешно никогда — кривые или для данных, которых уже нет. Такое **отравленное сообщение** (poison message), оставленное само по себе, consumer перепосылает вечно и блокирует очередь. Стандартный ответ — **dead-letter queue** (очередь мёртвых писем): после пары неудачных попыток сообщение отодвигается в отдельную очередь на разбор, чтобы перестать отравлять поток. Планируй сообщения, что падают, иначе одно плохое сообщение застопорит всё за ним.

_Diagram: Обычные сообщения проходят. Отравленное после трёх попыток уезжает в dead-letter queue — на разбор человеку._

#### Dead-Letter Queue (DLQ, очередь мёртвых писем)

- **Проблема:** **Poison message** падает каждый раз, ретраится вечно и блокирует очередь.
- **Идея:** После нескольких неудач отодвинуть сообщение в отдельную очередь на разбор.
- **Пример:** Сообщение про удалённого пользователя после трёх попыток уходит в DLQ.
- **Цена:** Кто-то должен смотреть в DLQ, иначе это кладбище потерянной работы.
- **Связи:** Ретраи безопасны только при **Idempotency**; держит поток для **Resilience**.

> **Вопрос сложных частей:** «Сделал ли я эту работу безопасной для двойного запуска, свободной от допущений о порядке, терпимой к краткой несогласованности — и куда деваются сообщения, когда их нельзя обработать?»

> Асинхронные сообщения несут дубликаты (лечи идемпотентностью), негарантированный порядок, согласованность в итоге и падающие сообщения (используй dead-letter queue). Планируй все четыре.

## 7. Применять хорошо

Очередь — силовой инструмент, а не дефолт. Навык — тянуться к ней, когда расцепление правда окупается, и оставлять прямой вызов, когда простота стоит больше.

### Queue vs Direct Call — используй очередь, когда проблема — это ожидание

> Ты берёшь номерок и уходишь за медленной услугой, что заберёшь позже, — но для быстрого «да или нет» ты просто спрашиваешь у стойки, ведь брать талон было бы глупее, чем подождать.

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

_Diagram: Один вопрос: нужен ли ответ, чтобы продолжить? Да — прямой вызов. Нет — очередь._

#### Queue vs Direct Call (как выбирать)

- **Вопрос:** Нужен ли вызывающему ответ, чтобы продолжить?
- **Очередь:** Работа медленная, всплесковая, несрочная или нужна многим (fan-out).
- **Прямой вызов:** Нужен мгновенный ответ: поиск цены, проверка прав.
- **Цена очереди:** Брокер, дубликаты, порядок, согласованность в итоге, сложнее трассировка.
- **Связи:** Выбор между **Synchronous Call** и **Message Queue**.

### Не тянись к ней рефлексом

> Установить целую почтовую комнату с системой сортировки, чтобы передать записку человеку за соседним столом, — машинерия теперь стоит больше проблемы, что решает.

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

### Понятия связаны между собой

> Конвейер: от одной боли — к одному лекарству, а у лекарства свои выгоды и свои побочные эффекты.

Вся картина такая. Боль **синхронного вызова** лечит **очередь сообщений**, которую держит **брокер**. По очереди ходят **команды** (одному consumer'у) и **события** (через **pub/sub** — многим). Это покупает **выгоды** — устойчивость, выравнивание нагрузки, масштабирование — и приносит **сложные части**: дубликаты, порядок, согласованность в итоге и DLQ.

_Diagram: Главные связи между понятиями курса: от прямого вызова к очереди, её выгодам и её цене._

#### Прежде чем добавить очередь

- **Проблема ли ожидание** — работа медленная, всплесковая или несрочная настолько, чтобы расцеплять?
- **Команда или событие** — один worker делает задачу или много сервисов реагируют на факт?
- **Идемпотентна ли работа** — безопасна для двойного запуска, раз сообщения могут прийти дважды?
- **Предполагает ли она порядок**, и устроил ли я его или спроектировал так, чтобы не зависеть?
- **Может ли система терпеть** краткую согласованность-в-итоге?
- **Куда деваются упавшие сообщения** — есть ли dead-letter queue?

#### Признаки, что ты используешь очереди хорошо

- Ты ставишь в очередь **медленную, всплесковую, несрочную** работу и держишь **прямые вызовы** для мгновенных ответов.
- Твои consumer'ы **идемпотентны**, так что дубликат сообщения не вредит.
- Ты **не предполагаешь порядок**, если явно его не устроил.
- Ты проектируешь под **согласованность в итоге**, а не ждёшь мгновенного согласия.
- Упавшие сообщения садятся в **dead-letter queue**, а не клинят линию.

## Шпаргалка: все 21 понятие

### Проблема прямого вызова

- **Synchronous Call** (синхронный вызов): Позвал и ждёшь. Просто, но связывает сервисы во времени.
- **Failure Propagation** (распространение сбоя): Один упавший сервис роняет всех, кто его зовёт.
- **Background Work** (фоновая работа): Работа, которую пользователь не должен ждать: письма, счета, фото.

### Очередь

- **Message Queue** (очередь сообщений): Положил сообщение и пошёл дальше; получатель заберёт сам.
- **Temporal Decoupling** (расцепление во времени): Буфер: стороны не обязаны работать в один момент.
- **Asynchronous Processing** (асинхронность): Не ждать результата; исход случится позже.

### Роли

- **Producer / Consumer** (производитель / потребитель): Один пишет сообщения, другой их обрабатывает; знают только очередь.
- **Message Broker** (брокер): Софт, что надёжно хранит и доставляет сообщения: RabbitMQ, Kafka.
- **Competing Consumers** (конкурирующие потребители): Много worker'ов на одной очереди делят нагрузку.

### Команды и события

- **Command** (команда): «Сделай это» — выполняет ровно один consumer.
- **Event** (событие): «Это случилось» — реагирует кто угодно, или никто.
- **Publish/Subscribe** (pub/sub, topic, fan-out): Событие в топик — каждый подписчик получает свою копию.
- **Event-driven Architecture** (событийная архитектура): Фичи добавляются новыми слушателями, а не правкой публикатора.

### Выгоды

- **Resilience** (устойчивость): Сбой consumer'а — это задержка, а не потерянная работа.
- **Queue-Based Load Leveling** (выравнивание нагрузки, backpressure): Всплеск наполняет очередь, consumer'ы разбирают её ровно.
- **Independent Scaling** (независимое масштабирование): Каждая сторона масштабируется и меняется сама по себе.

### Сложные части

- **Idempotency** (идемпотентность, at-least-once): Дубликат возможен — сделай так, чтобы он не вредил.
- **Message Ordering** (порядок сообщений): Порядок не гарантирован, пока ты его не устроил.
- **Eventual Consistency** (согласованность в итоге): Части системы кратко расходятся, потом догоняют.
- **Dead-Letter Queue** (DLQ): Отдельная очередь для сообщений, что падают снова и снова.

### Выбор

- **Queue vs Direct Call** (как выбирать): Нужен ответ, чтобы продолжить, — прямой вызов; нет — очередь.

> Очереди намеренны: тянись к одной, когда расцепление во времени покупает устойчивость, масштаб или fan-out, — и оставляй прямой вызов, когда мгновенный ответ важнее всего этого.

Конец экспресс-курса · 7 глав · 21 понятие

Дальше — практика: возьми одну медленную вещь, что твоё приложение делает прямо в потоке, — отправку письма, генерацию отчёта — и убери её за очередь, чтобы пользователь получил мгновенный ответ, а worker сделал медленную часть после. Потом сделай того worker'а безопасным для двойного запуска. Паттерн щёлкает в тот миг, как ты видишь, что ответ стал быстрым, а работа всё равно делается. Но держи одну мысль выше прочих: сдвиг — от «позвать и ждать» к «оставить сообщение и продолжить». Расцепи работу во времени, и система станет быстрее, крепче и проще для роста — ценой мышления событиями вместо команд.
