Dead-letter queue: что делать с заданиями, которые постоянно завершаются ошибкой
Dead-letter queue, или DLQ, — отдельная очередь для сообщений, которые выведены из обычной обработки и требуют разбора. Она помогает изолировать проблемное задание, выяснить причину отказа и решить, можно ли выполнить его повторно. Отправить сообщение обратно недостаточно: без исправления причины оно может снова завершиться ошибкой или повторить уже совершённое действие.
Представьте сайт, где заказ принят, но фоновая задача не создаёт документ. Остальные задания выполняются, а одно сообщение возвращается снова и снова. Именно такие случаи рассматриваются ниже: как сохранить контекст, отличить неисправные данные от временного сбоя, проверить исправление и провести контролируемый повтор. Технические примеры относятся к RabbitMQ; механизмы других брокеров могут отличаться.
DLQ и DLX: очередь хранения и механизм маршрутизации
В RabbitMQ DLX, dead-letter exchange, — обменник, через который сообщения маршрутизируются после определённых событий. DLQ — очередь, выбранная для их приёма. Само имя с окончанием .dlq не создаёт особых свойств. Как объясняет документация RabbitMQ о dead-letter exchanges, DLX является обычным exchange, а получатели определяются настройкой маршрута.
Например, в учебной схеме рабочая очередь documents.work использует обменник documents.failed, связанный с очередью documents.review. Последняя играет роль DLQ. Если обменник создан, но подходящей связи с очередью нет, одного указания его имени недостаточно для доставки сообщения на разбор.
У этого разделения есть практическая польза. Разработчик отвечает за поведение обработчика и смысл ошибки, администратор — за действующую топологию, а владелец процесса — за решение о повторе конкретного задания. Список изолированных сообщений становится рабочей очередью расследований, когда у него есть ответственный и понятные исходы.
Почему сообщение попадает в DLQ
Попадание в очередь разбора не доказывает, что содержимое испорчено. В RabbitMQ причиной может быть отказ обработчика с отрицательным подтверждением без возврата в исходную очередь, истечение времени жизни сообщения, вытеснение из-за ограничения длины или превышение соответствующего лимита доставок у quorum queue. Для dead lettering должна быть настроена необходимая схема.
При AMQP 0-9-1 команда basic.reject или basic.nack с requeue=false может направить сообщение в DLX. Возврат с requeue=true имеет другой смысл: сообщение снова становится кандидатом на обработку. Не считайте любое отрицательное подтверждение автоматическим переносом в DLQ.
Время жизни сообщения и самой очереди
В описании TTL RabbitMQ различаются срок хранения сообщения и срок существования очереди. Истечение TTL сообщения может привести к dead lettering; удаление истёкшей очереди не означает переноса всего содержимого в DLQ. Кроме того, обработка истечения связана с положением сообщения в очереди: TTL не следует представлять как точный будильник для повторного запуска.
Ограничение длины и число доставок
Настройка длины очереди RabbitMQ предусматривает разные варианты переполнения. Вытеснение старых сообщений и отказ в приёме новых дают разные последствия. Проверьте overflow и поддержку выбранного режима конкретным типом очереди: ошибка переполнения не равнозначна ошибке исполнения задания.
Для quorum queues важны версия и фактическая настройка delivery-limit. В документации quorum queues отмечено изменение начиная с RabbitMQ 4.3: возвраты через basic.nack не увеличивают delivery-count. Поэтому нельзя рассчитывать, что любой бесконечный цикл возвратов остановится этим лимитом. Собственное ограничение повторов и проверку поведения обработчика проектируют отдельно.
Сначала разделите временные и постоянные ошибки
Временная ошибка означает, что при изменившихся внешних условиях то же корректное задание может выполниться. Например, зависимая система временно недоступна. Постоянной для текущей версии обработки будет ошибка, которую ожидание само по себе не устранит: неизвестная версия схемы, отсутствующее обязательное поле или нарушенное правило операции.
Слово «постоянная» не означает «невозможно исправить никогда». Оно означает, что простой автоматический повтор сейчас бессмысленен. После обновления обработчика, восстановления справочника или согласованного исправления данных решение может измениться. Классификацию подтверждают наблюдениями, а не только текстом исключения.
| Наблюдение | Что выяснить | Дальнейшее действие |
|---|---|---|
| Истёк тайм-аут внешнего запроса | Был ли фактически выполнен запрос на другой стороне? | Проверить состояние операции, затем выбрать безопасный повтор |
| Нет обязательного поля | Какой контракт должен был выполнить отправитель? | Исправить источник или преобразование данных |
| Неизвестный идентификатор | Объект ещё не создан, удалён или указан неверно? | Разобрать зависимость и актуальность задания |
| Повторяется одна ошибка после релиза | Какая версия обработчика и схема сообщения используются? | Проверить совместимость на сохранённом примере |
Для временных отказов задайте ограниченное количество попыток, задержку и условие прекращения. Конкретные интервалы зависят от операции. DLQ удобно использовать после исчерпания принятой стратегии либо сразу для явно непригодного задания; она не обязана становиться промежуточной остановкой каждого краткого сетевого сбоя.
Как сохранить контекст отказа
Разбор начинается с одного представительного сообщения и связанных записей приложения. Сохраните исходное содержимое в контролируемом месте, не изменяя единственный экземпляр ради эксперимента. Зафиксируйте, какой результат ожидался и что уже могло произойти до ошибки. Последняя строка стека редко отвечает на оба вопроса.
Для AMQP 0-9-1 RabbitMQ добавляет историю dead lettering в x-death. Она группируется по сочетанию очереди и причины. Это полезная отправная точка, но не полный журнал выполнения приложения: число таких событий не следует выдавать за точное число всех запусков бизнес-операции.
- Идентичность: идентификатор сообщения, идентификатор операции и связь с заказом или заявкой.
- Происхождение: источник, рабочая очередь, версия схемы, время создания и поступления на разбор.
- Ошибка: код, безопасное описание, версия обработчика и ссылка на диагностическую запись.
- Выполненные действия: какие изменения уже подтверждены базой или внешней системой.
- История решений: кто классифицировал отказ, что исправлено и почему повтор теперь допустим.
Разделяйте диагностические данные и секреты. Для поиска обычно достаточно идентификатора и кода ошибки; полные токены, пароли и содержимое личных документов не нужны в общей таблице инцидентов. Доступ к исходным сообщениям предоставьте тем, кто действительно выполняет разбор. Так рабочий журнал останется полезным, даже если его читают маркетолог, поддержка и разработчик одновременно.
Группируйте сообщения по проверенному признаку: версии схемы, коду отказа, обработчику. Не предполагайте, что одинаковый текст «внешняя ошибка» означает одинаковую причину. Для каждой группы сначала изучите несколько случаев и убедитесь, что решение распространяется на всю выбранную партию.
Почему настроенный маршрут не гарантирует сохранение
Для рабочей схемы нужны существующий обменник, правильные связи, допустимые ключи маршрутизации и доступная целевая очередь. На тестовом сообщении проверьте реальный переход в DLQ. Затем воспроизведите недоступность получателя в отдельной проверочной среде и посмотрите, что происходит с исходным сообщением. Название настройки не заменяет наблюдение за результатом.
По умолчанию RabbitMQ выполняет dead lettering без внутренних publisher confirms, поэтому при недоступности получателя возможна потеря. Для исходной quorum queue предусмотрена стратегия at-least-once, требующая отдельной конфигурации, включая dead-letter-strategy=at-least-once, overflow=reject-publish, настроенный DLX и включённый флаг stream_queue, если он ещё не активирован в используемой версии. Это не автоматическое свойство каждой DLQ; возможны повторные доставки.
Отдельно проверьте сохранность после перезапуска брокера. Исходная quorum queue хранит сообщения независимо от delivery mode, но при dead lettering этот режим сохраняется. Например, для целевой classic queue нужны durable-очередь и persistent-режим исходной публикации, чтобы сообщения пережили рестарт. Ограничения сохранности описаны в документации quorum queues.
Назначьте также срок разбора и правила хранения самой очереди ошибок. Если её содержимое удаляется раньше, чем ответственный успевает расследовать отказ, практическая ценность изоляции исчезает. Установите, какие сведения сохраняются после завершения разбора и как подтвердить, что решение применено к правильным сообщениям.
Не меняйте производственную топологию наугад во время восстановления. Сначала запишите действующие параметры и ожидаемое поведение после изменения. Проверочная среда должна воспроизводить тип очереди, версию брокера и способ подтверждения, иначе успешный эксперимент может не описывать реальную систему.
Что исправлять до повторной обработки
Возможны три основных решения: исправить обработчик, восстановить корректные данные или признать задание больше неактуальным. Для ошибки схемы подготовьте воспроизводимый пример. Проверьте на нём новую версию кода, затем добавьте соседние случаи: отсутствующее поле, старый формат и уже существующий результат.
Если ошибся отправитель, устраните причину выпуска новых неправильных сообщений. Иначе DLQ продолжит получать задания одновременно с разбором старых. Когда некорректна лишь историческая партия, подготовьте явное преобразование: что меняется, из какого источника берётся исправленное значение и как сохраняется связь с оригиналом.
Не заполняйте обязательное поле случайным значением только ради прохождения проверки. Например, отсутствующий идентификатор покупателя нельзя заменить первым подходящим покупателем. Если восстановить значение по достоверным данным не удаётся, правильным исходом может быть ручное уточнение или отказ от исполнения с зафиксированной причиной.
Перед повтором проверьте актуальность события. Заказ могли отменить, документ — оформить вручную, уведомление — потерять смысл. Изолированное сообщение отражает состояние в момент создания; оно не даёт разрешения игнорировать последующие изменения. Решение об исполнении принимает логика процесса с учётом текущего состояния.
Как выполнить безопасный redrive
Redrive здесь означает управляемое возвращение выбранных сообщений к обработке. Это название операции в нашей процедуре, а не обещание универсальной команды RabbitMQ. Конкретный инструмент зависит от приложения и установленной инфраструктуры. Процесс должен позволять выбрать партию, ограничить темп, остановить запуск и сопоставить результат с исходными сообщениями.
- Зафиксируйте выборку. Составьте список идентификаторов и причину включения. Не выбирайте все сообщения только потому, что они находятся в одной DLQ.
- Проверьте готовность. Нужная версия обработчика развёрнута, зависимость доступна, данные проверены, защита от повторного эффекта работает.
- Выполните малую партию. В учебном сценарии можно начать с пяти заданий. Это пример, а не рекомендуемый универсальный размер.
- Проверьте конечный результат. Сопоставьте каждое задание с созданным документом, изменённой записью или обоснованным отказом.
- Продолжайте ограниченно. Увеличивайте объём только при понятных результатах; при возврате прежней ошибки остановите следующую партию.
- Сведите итоги. Для каждого исходного сообщения должен быть известен результат или назначен дальнейший разбор.
Отдельная программа переноса может читать из DLQ и публиковать в рабочий маршрут. В такой схеме нельзя сначала подтвердить удаление исходного сообщения, а затем надеяться на успешную публикацию. Сначала проверяют принятие новой публикации и маршрутизацию, после этого подтверждают исходную доставку. Даже такой порядок оставляет возможность дубля при обрыве между действиями, поэтому он требует идемпотентной обработки.
Для каждого запуска заведите собственный идентификатор партии и сохраните состав до отправки. Он связывает повтор с решением, версией исправления и оператором. Идентификатор самой бизнес-операции при этом должен оставаться устойчивым: создание нового транспортного сообщения не должно скрывать связь с прежней попыткой. В журнале удобно хранить исходный идентификатор сообщения, идентификатор новой публикации, время переноса и ссылку на результат. Тогда при расследовании не придётся угадывать, какое из внешне одинаковых заданий уже было выполнено.
Если часть партии завершилась неясно, не отправляйте всю партию повторно без сверки. Отметьте подтверждённые результаты, известные отказы и неопределённые случаи раздельно. Для последних сначала восстановите состояние по журналу операций. Остановка следующей партии не отменяет уже выполненные действия текущей.
Право запуска повтора отделите от простого просмотра диагностических записей. Инструмент должен показывать целевое окружение, рабочий маршрут и выбранную партию до исполнения. Сервисной учётной записи выдавайте необходимые права на конкретные ресурсы; доступ ко всему брокеру для такой задачи обычно не требуется. Сохраняйте, кто запустил перенос и по какому решению. При разборе сообщений с чувствительным содержимым не переносите их в сторонние тестовые сервисы: используйте контролируемую среду и подготовленные безопасные данные.
Почему ack не равен бизнес-результату
Руководство RabbitMQ по подтверждениям разделяет publisher confirms и consumer acknowledgements. Первые относятся к публикации в брокер, вторые — к взаимодействию брокера с потребителем. Ни одно из них само по себе не проверяет, что документ корректен, письмо дошло адресату или заказ принят внешней системой.
Более того, publisher confirm может быть получен и для сообщения, которому не нашлось подходящей очереди. При публикации с mandatory нужно обрабатывать возврат basic.return, а не считать один confirm доказательством нужной маршрутизации. Проверку доставки настройте вместе с обработкой ошибок используемой клиентской библиотеки.
Для бизнес-операции нужен устойчивый идентификатор. Представим, что обработчик создал документ и сохранил его, но потерял соединение до отправки ack. Повторная доставка не должна создавать второй документ. По идентификатору операции обработчик находит уже зафиксированный результат и завершает повтор без повторения эффекта.
Идемпотентность требует защиты от одновременных дублей. Простой порядок «проверить отсутствие записи, затем создать» допускает гонку между двумя обработчиками. Для локальной базы используйте согласованную атомарную запись результата и признака выполнения с ограничением уникальности. Конкретная реализация зависит от транзакционной модели приложения.
Для внешнего действия проверьте, поддерживает ли получатель ключ идемпотентности и поиск состояния операции. Если соединение оборвалось после отправки запроса, отсутствие ответа не доказывает отсутствие эффекта. Когда безопасно установить состояние нельзя, оставьте сообщение на ручную сверку вместо автоматического повторения потенциально необратимого действия.
Есть и отдельная проблема порядка событий. Старое сообщение может прийти после нового, даже если каждый идентификатор обрабатывается только один раз. Например, задание на прежнюю редакцию документа не должно затирать уже согласованную новую редакцию. Для таких операций проверяйте ожидаемую версию объекта и допустимость перехода состояния. Идемпотентность защищает от повторения одного эффекта, а контроль версии — от применения устаревшего, но формально другого события. Обе проверки особенно важны при возвращении исторической партии.
Учебный пример: восстановление формирования документов
Допустим, в DLQ накопилось сорок сообщений. Все числа в этом примере вымышлены. После проверки команда разделила задания по фактической причине и текущему состоянию заказов. Цель — получить корректные документы там, где они ещё нужны, и сохранить объяснение остальных исходов.
| Группа | Количество | Решение |
|---|---|---|
| Старый формат, который новый обработчик не принимал | 24 | Исправить совместимость и проверить повтор |
| Неверный код подразделения | 8 | Восстановить данные из подтверждённого справочника |
| Документ уже существует после прежней попытки | 5 | Сверить результат и исключить повторное создание |
| Заказ отменён | 3 | Зафиксировать, почему исполнение больше не требуется |
После исправлений к реальному исполнению готовы тридцать два задания: двадцать четыре плюс восемь. Пять случаев закрываются сверкой существующего результата, ещё три — решением об отмене. Уменьшение количества сообщений на сорок не означает создание сорока новых документов; отчёт должен сохранить это различие.
Сначала команда проверяет несколько заданий каждой исправленной группы и подтверждает соответствие документов исходным заказам. Затем завершает оставшуюся часть в выбранном темпе. Если среди первой группы появляется новая ошибка, обработку этой группы останавливают и расследуют её отдельно, сохраняя результаты уже успешных операций.
Как не превратить DLQ в забытый архив
Назначьте владельца разбора, срок следующего действия и понятный итог. Для небольшой команды достаточно карточки с идентификатором, причиной, актуальностью, ответственным и решением. Запись «переотправлено» является промежуточным состоянием: после неё ещё требуется подтверждение результата.
Следите за новыми поступлениями, возрастом самого старого неразобранного сообщения, повторным возвращением после redrive и числом случаев без владельца. Эти показатели отвечают на разные вопросы. Пустая DLQ может означать успешный разбор, но также удаление содержимого, нарушение маршрута или отсутствие наблюдения.
Критерий остановки повтора сформулируйте до запуска. Это может быть возвращение той же ошибки на проверенном примере, расхождение между ожидаемым и созданным документом, появление дубля или невозможность установить результат. Не ждите, пока исчезнут все сообщения: при таком подходе можно быстро размножить неверные действия. После остановки сохраните точный состав уже отправленной части и неисполненного остатка. Продолжение оформляйте как следующий запуск с новым идентификатором партии, сохраняя общую историю исходных операций.
Итог разбора должен отвечать на вопросы владельца сайта: какие клиентские действия были затронуты, какие восстановлены, какие требуют контакта с клиентом и кто отвечает за оставшиеся случаи. Одной технической фразы «очередь очищена» для этого недостаточно. Для каждого закрытого сообщения полезна ссылка на проверяемый результат либо конкретное решение об отказе от исполнения. При дальнейшем обращении клиента команда сможет восстановить ход событий без нового поиска по всем журналам.
Для проверки процесса подготовьте безопасное тестовое задание с известной ошибкой. Подтвердите его изоляцию, появление диагностической записи, назначение ответственного и корректный итог после исправления. Такое упражнение помогает обнаружить пробел между настройкой брокера и реальной работой команды.
Как связать разбор сообщений с работой сайта
Контроль внешних страниц и разбор DLQ решают связанные задачи на разных уровнях. Страница может открываться, пока фоновый документ не создаётся. Поэтому состояние брокера проверяют вместе с результатом пользовательского сценария, а для асинхронной операции отдельно фиксируют завершение обработки.
Подключите SiteGuard для внешнего HTTP-контроля доступности важных URL. Управление DLQ и проверку результата повторной обработки выполняйте средствами брокера и приложения.