ТестированиеТестирование API и интеграцийИнженер по интеграционному тестированию

Практическая ситуация: потребитель события возвращает ошибку обработки, поэтому брокер повторяет доставку о...

Практическая ситуация: потребитель события возвращает ошибку обработки, поэтому брокер повторяет доставку одного сообщения.

сообщение = получить()
если обработка(сообщение) завершилась ошибкой:
    вернуть_ошибку_брокеру()
иначе:
    подтвердить_обработку()

Какой контракт доставки должен проверить интеграционный тест, чтобы после исчерпания попыток сообщение не доставлялось бесконечно?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Интеграционный тест должен проверить контракт ограниченного числа доставок с маршрутизацией в карантин, обычно в dead-letter queue (DLQ). После заданного числа неуспешных попыток сообщение должно быть удалено из основной очереди, помещено в DLQ с причиной и метаданными исходного сообщения, а повторная доставка не должна продолжаться бесконечно.

Исторический контекст

Повторная доставка появилась как способ переживать временные сбои потребителя, сети или зависимой системы без потери сообщения. Однако универсальный бесконечный retry превращает постоянную ошибку данных или кода в бесконечный цикл обработки.

Механизмы максимального числа попыток и DLQ разделяют временные сбои и сообщения, требующие ручного разбора или исправления данных. Конкретные названия и параметры зависят от брокера, но проверяемая идея не меняется.

Постановка проблемы

Если потребитель постоянно отклоняет сообщение, оно может блокировать очередь, создавать лишнюю нагрузку и задерживать корректные сообщения. При неправильном подтверждении сообщение, наоборот, может исчезнуть после первой ошибки.

Тест должен установить наблюдаемые границы: сколько раз сообщение доставляется, что считается неуспешной обработкой, куда оно попадает после последней попытки и какие сведения сохраняются для расследования.

Подробное решение

Интеграционная проверка публикует уникальное заведомо невалидное событие, запускает потребителя и наблюдает доставки. После каждой неудачи подтверждение основного сообщения не отправляется; брокер увеличивает счётчик попыток и повторно доставляет сообщение.

После достижения лимита брокер или потребитель выполняет dead-letter routing. Тест проверяет, что сообщение присутствует в DLQ ровно один раз, отсутствует среди доступных сообщений основной очереди, а в метаданных указаны исходный идентификатор, причина отклонения и число попыток.

событие: id: "evt-42" тип: "ЗаказСоздан" данные: "некорректны" ожидание: максимум_доставок: 3 после_лимита: "DLQ" исходный_id_в_DLQ: "evt-42"

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

Тест не должен полагаться только на задержку или число циклов в коде теста. Надёжнее ждать наблюдаемое состояние через polling с общим тайм-аутом: появление сообщения в DLQ и отсутствие сообщения в основной очереди.

Компромисс состоит в выборе лимита. Маленький лимит быстрее изолирует неисправное сообщение, но может отправить в DLQ временно проблемное событие; большой лимит повышает вероятность успешного восстановления, но дольше задерживает обработку и увеличивает нагрузку.

Ситуация из практики

Потребитель события о платеже обращался к платёжному шлюзу. При некорректном формате события шлюз отклонял запрос, а брокер бесконечно возвращал сообщение в ту же очередь. В результате очередь почти не продвигалась, а журналы заполнялись одинаковыми ошибками.

Рассматривались три варианта. Полностью отключить повторы было просто, но создавало риск потери временно необработанных событий. Увеличить число повторов без карантина не устраняло бесконечный цикл. Добавить ограничение попыток с DLQ потребовало отдельного мониторинга и процедуры повторной отправки, зато изолировало дефект и сохранило сообщение.

Выбрали третий вариант: три попытки с контролируемой задержкой, затем DLQ. Интеграционный тест проверял доставку в DLQ, сохранение исходного идентификатора и отсутствие дальнейших доставок из основной очереди. Это позволило отличить ожидаемую изоляцию плохого сообщения от потери события.

Что кандидаты часто упускают

  1. Достаточно ли проверить только наличие сообщения в DLQ?

Нет. Нужно проверить также отсутствие бесконечных повторов в основной очереди, число попыток и связь с исходным сообщением. Иначе тест может пройти при сценарии, где копия попала в DLQ, но оригинал продолжает повторно доставляться.

  1. Почему тест лимита попыток может быть нестабильным?

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

  1. Нужно ли отправлять в DLQ сообщения с любой ошибкой?

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