Перейти к содержимому
К программе курса
Архитектура высоконагруженных веб-приложений
8 / 1173%

Очереди, backpressure и борьба с потерянными сообщениями

90 минут

Для кого: инженеры, которым надоело слушать «просто поставь Kafka». Если у вас не настроены retry, DLQ и мониторинг lag — вы не управляете очередями. Этот урок заставит вас перестать терять сообщения.

Провокация №1: вы знаете, сколько сообщений сейчас в очереди?

  • Если вы не можете в течение 60 секунд назвать queue_depth и consumer_lag, значит, вы живёте в надежде. Очередь может взорваться в любой момент.
  • Чек: в Grafana должны быть панели messages_ready, messages_unacked (RabbitMQ) или consumer_lag (Kafka). Нет? Начинайте с этого.

1. Модели доставки: at-most-once, at-least-once, exactly-once

МодельЧто гарантируетЦена
At-most-onceсообщение будет доставлено максимум один раз, может потерятьсябыстро, но опасно
At-least-onceдоставим, пока не подтвердитедубликаты, нужна идемпотентность
Exactly-onceдоставим один раз (Kafka transactional, SQS FIFO)дорого, сложнее, ограничения throughput

Правило: по умолчанию используйте at-least-once + идемпотентность. Exactly-once только там, где ошибка > стоимости архитектуры.

Провокация №2: вы знаете свой max throughput и lag?

  • consumer_lag > X минут? Значит, вы уже не контролируете SLA. В бизнес-час пик пользователь увидит сообщения через час.

2. Backpressure и управление нагрузкой

2.1 Метрики

  • messages_ready (RabbitMQ) / queue_depth (SQS)
  • consumer_lag (Kafka)
  • processing_time (worker)
  • retry_count, DLQ_rate

2.2 Действия при росте lag

LagЧто делаем
< 1 миннорма
1–5 минмасштабируем воркеры, проверяем hot partition
> 5 минвключаем backpressure (отказываем продюсерам), включаем лидерских инженеров
> 30 минавария: уведомляем бизнес, режем входящий трафик, DLQ анализируем каждую минуту

2.3 Backpressure механизмы

  • HTTP продюсеры → 429 (Too Many Requests), когда lag > X.
  • Limit зкном принятия задач (Bull/BullMQ limiter).
  • Kafka: динамический pause/resume consumption.
if (lag > 300000) {
  producer.rejectWith429();
  consumer.pause();
}

Провокация №3: у вас есть idempotent handlers?

  • Если нет — at-least-once превращается в «удвоенные платежи». Необсуждаемое требование: каждый handler должен быть идемпотентным.

3. Idempotency паттерны

  • Deduplication table:
    CREATE TABLE processed_events (
      event_id TEXT PRIMARY KEY,
      processed_at TIMESTAMPTZ DEFAULT now()
    );
  • Внешние ключи: операции обновления по ON CONFLICT DO NOTHING.
  • Idempotent APIs: PATCH вместо POST, INSERT ... ON CONFLICT.

Провокация №4: DLQ — это не помойка

  • Dead Letter Queue не для того, чтобы «складировать» сообщения. Там живут баги. Если вы не выгребаете DLQ в течение дня — вы хороните бизнес-события.

4. DLQ дисциплина

ПравилоОписание
Timerочистка DLQ не реже 1 раза в сутки
Протоколкаждая запись → JIRA/incident
AlarmDLQ_rate > 0 больше 10 минут — PagerDuty

4. Практические очереди

4.1 RabbitMQ

  • Подтверждения (ack) обязательны.
  • Prefetch = channel.prefetch(10) — иначе один worker задушит others.
  • Durable queues + persistent messages (но медленнее).
  • Management API → мониторинг messages_ready, messages_unacked.

4.2 Kafka

  • Multi-partition = параллельность.
  • Consumer group → балансировка.
  • Lag = latest_offset - committed_offset.
  • Transactions = exactly-once, но цена — сложность.
  • acks=all, min.insync.replicas=2 для durability.

4.3 Bull/BullMQ (Node.js)

  • Redis-backed, хорош для задач до 10k/s.
  • limiter → ограничение скорости, retry с backoff.
  • stalled detection: воркер умер → job возвращается в очередь.

Провокация №5: вы тестировали потерю сообщений?

  • Убейте воркер на середине обработки. Сообщение вернулось? Если нет — вы теряете данные.

Инцидентная симуляция

# RabbitMQ: имитируем network partition
iptables -A INPUT -p tcp --dport 5672 -j DROP
sleep 30
iptables -D INPUT -p tcp --dport 5672 -j DROP

Цель: после восстановления нет «зависших» сообщений, DLQ пуст.

Провокация №6: вы отслеживаете throughput per partition?

  • Kafka: одна «горячая» партиция может положить весь consumer. Мониторьте records-lag-max по partition.

5. Архитектурные паттерны

5.1 Producer-Consumer

  • Не публикуйте сообщения «на глаз». Логируйте event_id, входные параметры.

5.2 CQRS + Event sourcing

  • Event store → projections → eventual consistency. Нужны реплеи.

5.3 Outbox pattern

  • Transactions: запись в БД + событие в outbox → воркер публикует в очередь.
INSERT INTO payments (id, status) VALUES ($1, 'NEW');
INSERT INTO outbox (event_id, payload, status) VALUES ($1, $2, 'pending');

Воркер:

const events = await db.query(
  "SELECT * FROM outbox WHERE status = $1 LIMIT 100",
  ["pending"]
);
for (const event of events.rows) {
  await publish(event);
  await db.query("UPDATE outbox SET status = $1 WHERE event_id = $2", [
    "sent",
    event.event_id,
  ]);
}

Провокация №7: у вас есть план деградации?

  • Очередь начинает расти, вы не успеваете. Что делает API? Отказывает? «Кладёт к себе»? Ответ должен быть заранее готов.

Практика

  1. Метрики:
    • Настройте Grafana: queue_depth, consumer_lag, DLQ_rate.
    • Алерт: consumer_lag > 300s → PagerDuty.
  2. Idempotency:
    • Добавьте таблицу processed_events.
    • Покройте handler тестами: две доставки → один результат.
  3. DLQ drill:
    • Намеренно киньте невалидное сообщение.
    • Проверьте: ушло в DLQ, создан тикет, есть перезапуск.
  4. Backpressure test:
    • Сымитируйте рост lag (замедлите consumer).
    • Убедитесь, что продюсер начинает 429/ограничивать отправку.
  5. Outbox:
    • Реализуйте outbox + воркер. Остановите воркер на 1 минуту, потом запустите. Убедитесь, что события публикуются без пропусков.

Безопасность: инцидентные тесты (lag, network drop) проводите на staging. Session tokens, личные данные — никогда не логируйте в DLQ. Удаляйте/обезличивайте перед анализом.

Что дальше

Следующая глава — event-driven архитектура: там заставим вас проектировать события, схемы, contract-тесты и Debezium. Очереди без правильной событийной модели — просто трубы. Готовьтесь.

Очереди, backpressure и борьба с потерянными сообщениями — Архитектура высоконагруженных веб-приложений — Potapov.me