Перейти к содержанию

Очередь отказов и осознанный повтор

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

Используем ветки callback и publish_raw_dead итогового проекта. Автор не создавал отказов и не просматривал очереди. Официальное руководство DLX определяет dead-letter-механизмы брокера. В нашей dead-очереди часть сообщений публикуется приложением явно, что важно отличать от автоматического перенаправления broker.

Dead-очередь хранит вопрос к сопровождающему

Название delivery.catalog.dead означает, что каталог не завершил работу автоматически. Оно не доказывает, что событие бесполезно или должно быть удалено. Причина может быть в несовместимой версии, неверной редакции, повреждённом теле, постоянной конфигурационной ошибке или временном отказе, который длился дольше разрешённого бюджета.

Поэтому у каждого получателя своя очередь отказов. Audit способен обработать событие, которое catalog отверг из-за конфликта своей проекции. Общая dead-очередь без указания обязанности сделала бы расследование сложнее: сопровождающий не знал бы, чей эффект отсутствует. Queue name сохраняет эту часть контекста, а eventId связывает известный конверт с другими наблюдениями.

В заголовке failure-reason сохраняем короткое имя класса отказа. Не записываем произвольный полный traceback с секретами и всем содержимым локального окружения. Полезная причина должна помогать определить следующий шаг, а не переносить в брокер случайный диагностический текст. Подробности можно хранить в защищённом журнале с устойчивым correlation-id.

Некорректное тело нельзя объявить правильным событием

Если JSON не разбирается или schemaVersion неизвестна, функция publish_event не подходит: она сама проверяет v1-конверт. Для этого случая publish_raw_dead сохраняет исходные байты с типом application/octet-stream. Мы не создаём ложный корректный конверт вокруг повреждённого тела и не присваиваем ему новый предметный факт.

Полный центральный вызов из этой функции:

channel.basic_publish(
    exchange='', routing_key=f'delivery.{name}.dead',
    body=body, mandatory=True,
    properties=pika.BasicProperties(
        delivery_mode=2,
        content_type='application/octet-stream',
        message_id=properties.message_id,
        headers={'failure-reason': reason[:100]}
    )
)

Канал находится в режиме publisher confirms. После успешного перенаправления callback подтверждает исходную доставку. Если сохранение dead-копии не принято, ack не выполняется. Но падение между принятой публикацией и ack всё равно допускает две копии отказавшего сообщения. У очереди отказов тоже нет магического устранения всех дубликатов.

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

Исчерпанная попытка сохраняет правильный конверт

Если v1-событие корректно, но временный отказ достиг attempt 3, используем обычный publish_event с адресом dead-очереди. Тело и eventId остаются прежними. Это позволяет после исправления зависимости снова применить тот же факт, а не изображать новую публикацию материала. Транспортный предел исчерпан, но идентичность предметной работы не поменялась.

Нельзя автоматически менять revision только ради повторного запуска. Она относится к редакции материала, а не к числу попыток обработки каталога. Если исходный владелец состояния не создавал новую редакцию, consumer не должен сам изобретать её номер. Иначе производная проекция начнёт жить отдельной историей, которую невозможно согласовать с API.

Также нельзя заменить eventId новым UUID и оставить сообщение прежним «для гарантированного повторения». Получатель увидит другой факт и не сможет воспользоваться сохранённой квитанцией предыдущего. Если прежний эффект уже выполнен, такое действие может создать второй побочный результат. Правило идентичности действует даже при ручной работе сопровождающего.

Перед повтором определить причину

Сначала установите, соответствует ли событие поддерживаемой схеме. Для неизвестной версии нужно обновить получателя либо преобразовать данные по согласованному договору. Для конфликтующего eventId потребуется выяснить, какая сторона повторно использовала идентификатор с другим телом. Для временной недоступности базы достаточно устранить инфраструктурную причину и сохранить прежний факт.

Исправление payload под прежним eventId не является обычным повтором. Оно меняет содержимое идентичного факта и должно вызвать конфликт fingerprints. Если владелец предметного состояния действительно исправляет публикацию, он создаёт новое событие следующей редакции. Так различаются восстановление доставки и новое редакционное изменение.

Рассмотрим ошибочный адрес /articles/http-cache.html и потребность поменять его. Если первоначальное событие было валидным, но адрес редакционно неверен, correction должна исходить от владельца материала. Если же байты повреждены транспортным или прикладным дефектом, нужно исследовать сохранённое исходное событие и способ его восстановления. Не угадывайте истину из единственной dead-копии.

Ожидаемый порядок решения:

Установить consumer и eventId, если конверт читается
Классифицировать: схема, конфликт, инфраструктура, исчерпанный лимит
Исправить причину или получить новое предметное событие
Для повтора доставки сохранить прежний конверт
Наблюдать результат конкретного consumer

Это процедура для читателя, а не выполненный разбор инцидента. Автор не проводил requeue и не утверждает, что все dead-сообщения успешно восстановлены. Исходники задают место хранения отказа; операционное решение остаётся осознанным действием, которое требует понимания причины.

Очередь отказов тоже нуждается в правилах хранения

Если её никогда не просматривать и не ограничивать, disk постепенно заполнится. Если задать слишком короткий срок хранения, важная работа исчезнет до расследования. Размер, срок и права доступа должны соответствовать обязанности сервисов и чувствительности данных. В нашем локальном примере такой production-policy не выбирается по выдуманной нагрузке.

Для наблюдения полезны число отказов, их возраст и распределение причин. Одно новое событие неизвестной версии может означать несовместимый выпуск издателя; множество временных отказов одного consumer — недоступную зависимость. Нельзя считать одинаковым сигналом любое ненулевое значение dead-очереди. В финальном уроке свяжем эти сведения с передачей проекта.

Не запускаем бесконечную автоматическую перепубликацию всего dead-набора. Она превратила бы постоянную ошибку в новый поток нагрузки и лишила бы ограничение попыток смысла. Для устойчивого массового восстановления нужен отдельный инструмент с отбором, журналом решений и пределом скорости. Этот инструмент не скрыт в нашем учебном consumer.

Для упражнения сопоставьте неизвестную schemaVersion и недоступную SQLite-базу. Решите, какой факт нужно изменить, а какой достаточно доставить повторно. Затем объясните падение после dead-confirm перед ack. Далее сделаем локальный эффект идемпотентным, чтобы допускаемые транспортные дубликаты не меняли результат второй раз.

Оглавление серии