# Очереди и события

← [Раздел](README.md) · [Главная](../README.md)

## Цель

Понять, зачем в облачной архитектуре **очереди сообщений** и **события**, чем они отличаются от синхронного HTTP и какие гарантии доставки бывают.

## Предварительно

- [Базы и хранилища](bazy-i-hranilishcha.md)

## Время

**3–5 часов**

---

## Проблема синхронного ожидания

Пользователь нажал «поделиться списком». Сервер должен:

1. Сохранить приглашение в БД  
2. Отправить email через SaaS (200–800 ms)  
3. Записать audit log  

Если всё в **одном HTTP-запросе**, клиент ждёт, SaaS тормозит — **таймаут** и плохой UX. Решение: **тяжёлое** вынести в фон.

---

## Очередь сообщений (message queue)

**Очередь** — буфер между **производителем** (producer) и **потребителем** (consumer).

```mermaid
sequenceDiagram
  participant API as API
  participant Q as Очередь
  participant W as Email Worker
  API->>Q: publish InviteSent
  API-->>API: 202 Accepted (быстро)
  Q->>W: deliver message
  W->>W: send email via SaaS
```

| Примеры систем | Модель |
|----------------|--------|
| RabbitMQ | Брокер AMQP, exchanges, queues |
| Amazon SQS | Managed очередь в AWS |
| Redis Streams | Лёгкая очередь на Redis |
| Apache Kafka | Лог событий, высокий throughput |

---

## Сообщение vs событие

| Понятие | Акцент |
|---------|--------|
| **Сообщение (command)** | «Сделай X» — один исполнитель |
| **Событие (event)** | «X уже произошло» — много подписчиков |

Пример события: `ListItemAdded { listId, itemId, at }` — уведомления, аналитика, поиск индексируют **независимо**.

Подробнее паттерны — [event-driven](../04-vzaimodeistvie/event-driven.md).

---

## Гарантии доставки

| Гарантия | Смысл | Цена |
|----------|-------|------|
| At-most-once | Может потеряться | Просто |
| At-least-once | Может дублироваться | Нужна идемпотентность consumer |
| Exactly-once | Ровно один раз | Сложно, часто «effectively once» |

**Практика:** проектируйте worker **идемпотентным**: повторная обработка `invite_id=7` не шлёт второй email.

---

## Dead Letter Queue (DLQ)

Если сообщение падает N раз (битый JSON, недоступный SaaS), его кладут в **DLQ** для ручного разбора.

| Без DLQ | С DLQ |
|---------|-------|
| Бесконечные retry блокируют очередь | Проблемные сообщения изолированы |
| Потеря видимости | Алерт на размер DLQ |

---

## Pub/Sub

**Publish-Subscribe:** издатель не знает подписчиков. Топик `list-events` — читают сервис уведомлений и сервис статистики.

```mermaid
flowchart LR
  API[API] --> Topic[Topic list-events]
  Topic --> N[Notifications]
  Topic --> A[Analytics]
  Topic --> I[Search indexer]
```

Kafka — типичный **event log** с хранением и replay; Rabbit — классический **брокер задач**.

---

## Когда очередь не нужна

| Ситуация | Почему |
|----------|--------|
| Простой CRUD, мало пользователей | Overhead брокера |
| Нужен немедленный ответ с результатом | Sync API |
| Строгая транзакция «всё или ничего» в одной БД | Outbox pattern позже |

Начинайте с sync; вводите очередь при **измеримой** боли latency или пиков.

---

## Outbox pattern (введение)

Проблема: записали в БД, упали до publish в очередь — **рассинхрон**.

**Transactional outbox:** в той же транзакции пишем строку в таблицу `outbox`; отдельный процесс читает outbox и публикует в брокер.

| Шаг | Действие |
|-----|----------|
| 1 | BEGIN; INSERT item; INSERT outbox |
| 2 | COMMIT |
| 3 | Relay читает outbox → queue |

Тема [раздела 04](../04-vzaimodeistvie/asinhron-i-ocheredi.md).

---

## Мониторинг очередей

| Метрика | Тревога если |
|---------|--------------|
| Queue depth | Растёт часами — workers не успевают |
| Age of oldest message | > SLA обработки |
| DLQ size | > 0 стабильно |
| Consumer lag (Kafka) | Отставание от producers |

---

## Сравнение с cron

| Cron job | Очередь |
|----------|---------|
| «Каждые 5 минут проверить всё» | «Обработать ровно это событие» |
| Пики нагрузки в одну минуту | Равномерное потребление workers |
| Сложно масштабировать параллельно | Несколько consumers |

---

## Практика

1. Для «отправить приглашение» нарисуйте sync и async варианты (2 sequence diagram).  
2. Что случится при at-least-once без идемпотентности email worker?  
3. Назовите два подписчика на событие `UserRegistered`.

---

## Самопроверка

1. Зачем API возвращает 202 вместо 200 при постановке в очередь?  
2. Чем команда «SendEmail» отличается от события «EmailRequested»?  
3. Что такое DLQ?

---

## Дальше

→ [03 — Проектирование](../03-proektirovanie/README.md)
