Модуль 3. Инциденты RabbitMQ: memory alarm, «мёртвый хват», flow control, poison-циклы

Цель модуля: вести инцидент RabbitMQ от симптома к причине: пять канонических отказов с разбором «симптом → метрика → причина → лечение» и воспроизведением на кластере. Родственно курсам Redis/Kafka: методика одна — словарь причин разный.

Перед началом: Модули 1–2 (особенно alarms и триада метрик). Трудоёмкость: ~10–12 ч (разборы 5 ч · воспроизведения 5 ч · квиз 1 ч).


📖 3.1. Методика: триада решает

Первый взгляд — всегда на триаду очереди (consumers → unacked → ready):

  • consumers = 0 при живом publish — воркеры умерли/не подключились: ищите деплой, креды, сеть; брокер почти ни при чём;
  • unacked растёт/висит — потребители живы, но не подтверждают: обработка зависла или длиннее prefetch-окна;
  • ready растёт при живых consumers — не успевают: масштаб или медленная обработка;
  • publish_rate упал на всём кластере — чей-то alarm или flow control (§3.2, §3.5): сервер спасает себя.

📖 3.2. Инцидент «Memory alarm под нагрузкой»

Симптом: продюсеры по всему кластеру встали (сообщения копятся на стороне приложений), UI показывает memory alarm на одном узле.

Метрики: rabbitmq_node_mem_alarm = 1, node_mem_used растёт, publish_rate → 0 на всех узлах (alarm синхронизируется, Модуль 2 §2.2).

Причина (по частоте):

  1. Глубокая очередь — сообщения в ready живут в памяти узлов (классические очереди могут частично уходить на диск, но рост глубины = рост памяти);
  2. Много unacked — огромный prefetch у прожорливых потребителей;
  3. Утечка каналов/очередей — авто-delete выключен, очереди плодятся (тысячи пустых очередей тоже едят память);
  4. Смешанные нагрузки: TTL-сообщения копятся до истечения.

Лечение:

  • срочно: разгрузить готовые (временно подключить drain-воркер), поднять watermark (rabbitmqctl set_vm_memory_high_watermap absolute "8GB" — только если память физически есть) — но лучше этого не делать под огнём;
  • системно: лимит глубины (x-max-length + overflow=reject-publish — backpressure на издателя!), prefetch по формуле, чистка брошенных очередей (rabbitmqctl list_queues name consumers messages);
  • профилактика: отдельный кластер/вhost под жирные потоки, алерт на node_mem_used > 70 % watermark.

📖 3.3. Инцидент «Мёртвый хват»: unacked у зависшего потребителя

Симптом: ready = 0, unacked = prefetch × consumers и не падает; новые сообщения не приходят потребителям; воркер «работает» (логи живые) или висит.

Метрики: unacked стоит на потолке; deliver_rate упал; ack_rate → 0.

Причина: потребитель получил сообщения и не шлёт ack — обработка зависла (DB-лок, внешний сервис без таймаута) либо код забыл ack после успеха. Сообщения «в руках» — и недоступны другим.

Лечение:

  • heartbeat + consumer_timeout (RabbitMQ разрывает таких потребителей и делает redelivery — убедитесь, что таймаут меньше вашего SLA);
  • prefetch подбирайте от p99 обработки: prefetch ≈ rate × p99;
  • в коде: ack строго после успеха (контур из «Фундамента» М1), обработка с context-таймаутом, panic-обработка в воркере — соединение закроется, сообщения вернутся живым;
  • диагностика: rabbitmqctl list_queues name messages_unacknowledged consumers и per-consumer в management UI.

📖 3.4. Инцидент «Poison-цикл через DLX»

Симптом: сообщения «кружат»: одна очередь копится и пустеет ритмично, delivery count растёт, воркеры делают одну и ту же работу; лаг CPU на репакете.

Метрики: publish/deliver по очередям показывает цикл (work → retry → work), в management UI у сообщения огромный x-death count.

Причина: retry-топология зациклена: DLX возвращает сообщение в рабочую очередь без счётчика попыток (или TTL-ретрай с нулевым заголовком), либо nack с requeue=true навсегда.

Лечение:

  • счётчик попыток в заголовке (x-death или свой attempt), после N — в DLQ (кладбище, не круговорот);
  • nack(requeue=false) + DLX вместо requeue=true для повторов — повтор управляем, а не бесконечен;
  • мониторинг DLQ: глубина > 0 — это алерт (у нас «кладбище» переполняется) и регулярный разбор.

📖 3.5. Инцидент «Flow control / blocked publishers»

Симптом: продюсеры периодически «замечают» паузы записи; в логах клиентов — connection blocked и flow; alarms при этом чистые.

Метрики: rabbitmq_connection_blocked... точнее — на узлах rabbitmq_node_* в норме, но в management UI клиенты со статусом flow/blocking; publish_rate пилит.

Причина: credit flow — RabbitMQ лимитирует канал, который не успевает обрабатывать (обычно медленный потребительский канал тащит за собой издателя на том же соединении, или сетевой узел перегружен). Это не отказ, а честный backpressure — но постоянный flow = деградация.

Лечение:

  • развести продюсерские и потребительские соединения (не смешивать в одном connection);
  • чинить медленного потребителя (это его credit исчерпал);
  • если flow ритмичен и совпадает с батчами — увеличить consumer_prefetch/publish batching на клиенте.

📖 3.6. Инцидент «Шторм соединений»

Симптом: connections_opened_total растёт стеной; fd_used к лимиту; брокер отвечает медленно; через минуту всё «прошло», повторяется по расписанию.

Метрики: rate(opened) − rate(closed), fd_used/sockets_used к лимитам.

Причина: балансировщик/K8s liveness-пробы открывают соединения без переиспользования; клиентская библиотека создаёт connection на каждое сообщение (антипаттерн: AMQP-соединение дорогое, канал — дешёвый).

Лечение:

  • пул длинных соединений, каналы на операцию (или долгоживущие); amqp091-go: один Connection, Channel на goroutine-воркер;
  • пробы — через health-endpoint приложения, а не через брокер;
  • лимиты клиента (max channels per connection) и алерт на churn.

💻 Практика

  1. Memory alarm: make mem-alarm под нагрузкой → publish встал; разгрузка drain-воркером → alarm снялся. Запишите время блокировки.
  2. Мёртвый хват: воркер с обработчиком «висит на DB-локе» — unacked на потолке; включите короткий consumer_timeout и убедитесь, что redelivery возвращает сообщения живым.
  3. Poison-цикл: соберите retry-топологию без счётчика → цикл; добавьте счётчик попыток и DLQ → цикл разорван, DLQ-алерт работает.
  4. Flow control: смешайте продюсера и медленного потребителя в одном соединении — поймайте flow в UI; разведите соединения — flow исчезает.
  5. Шторм соединений: скрипт открывает по connection на сообщение — churn на графике; переделайте на пул из 2 соединений × каналы.

Артефакт: журнал пяти воспроизведений «симптом / ведущая метрика / лечение» + топология retry с DLQ из шага 3.


🧩 Quiz "Инцидент-квиз: причина по симптому"

1. Продюсеры всего кластера встали, alarms есть на одном узле. Почему все?
2. Сообщение обрабатывается в 20-й раз, delivery count растёт. Что сломано?
3. В UI клиенты со статусом flow, alarms чистые, publish_rate пилит. Что происходит?
4. unacked = 320 (prefetch 16 × 20 consumers) и стоит, ready = 0, ack_rate = 0. Диагноз?

✅ Итоги модуля

ИнцидентВедущая метрикаЯдро причины
Memory alarmnode_mem_alarm / mem_usedглубина очереди в памяти; alarm блочит весь кластер
Мёртвый хватunacked на потолке, ack_rate=0зависшая обработка / забытый ack; лечит consumer_timeout
Poison-циклцикл в publish/deliver + x-deathretry без счётчика попыток; лечит DLQ-топология
Flow controlстатус flow у клиентовcredit flow: медленный канал; разводить соединения
Шторм соединенийopened/closed rate, fd_usedconnection на сообщение; лечит пул + каналы

🔗 Мостик к Модулю 4

Пять инцидентов закрывают эксплуатацию классической AMQP-модели. Последний модуль курса — RabbitMQ Streams и multi-DC: лог-модель внутри Rabbit (replay, офсеты, большие retention), shovel и federation для связи кластеров, и честный ответ «когда RabbitMQ Streams, а когда всё-таки Kafka».