Представьте одну секунду из жизни worker. Он взял задание, дошёл до узла HTTP Request и отправил POST во внешнюю систему: создать счёт, списать бонусы, отправить письмо. Ответ ещё не пришёл. И именно в эту секунду связь с Redis пропала. Что теперь известно о задании? Что оно выполнено? Что провалено? На самом деле неизвестно ни то ни другое, и весь дальнейший разбор строится вокруг честного признания этого факта.
Красный индикатор Redis в мониторинге говорит о состоянии транспорта. Он ничего не говорит о судьбе конкретного выполнения. Чтобы решить, повторять его или нет, нужно разобраться, что где живёт, в какой момент линия оборвалась и какие следы остались.
Кто за что отвечает: main, Redis, база и worker
В queue mode у n8n несколько ролей, и путаница между ними порождает самые дорогие ошибки. По документации режима очереди n8n, main передаёт через Redis идентификатор выполнения, а сами данные worker получает из базы данных. Это ключевое разделение: Redis возит «талоны», а не содержимое workflow.
Из этого следуют практические выводы.
- Redis не является единственным хранилищем данных workflow. Определения workflow, учётные данные и сохранённые данные выполнений лежат в БД. Потеря связи с Redis не означает потерю самих workflow.
- Талон может потеряться отдельно от данных. Запись о выполнении в базе есть, а сообщения в очереди для неё нет, или наоборот: сообщение есть, а worker не смог до него дотянуться.
- Worker зависит от обоих каналов. Без Redis он не узнает о новых заданиях, без базы не получит их данные и не сохранит результат.
- Main тоже зависит от Redis, когда ему нужно поставить задание в очередь.
Отдельно стоит упомянуть бинарные данные: в документации указано, что хранение их в файловой системе в queue mode не поддерживается. Для нашей темы это важно так: если вы мысленно считаете, что worker может подобрать файл с диска main, этого не будет, и на разборе сбоя файлы нужно искать там, где они хранятся по настройкам, а не в локальной папке.
Если Redis вы вынесли на отдельную машину, добавляется ещё одно звено, сеть между ними. Для проверки таких сценариев удобно иметь тестовый контур на отдельном VPS: root-доступ позволяет закрыть порт файрволом, остановить сервис или оборвать маршрут и посмотреть, что остаётся в истории. Подробнее о размещении n8n в целом есть в материале про ИИ-автоматизации в n8n на VPS.
Временная линия одного выполнения
Возьмём условный workflow из четырёх узлов: Webhook, Set, HTTP Request (создание заказа во внешней CRM), запись результата в таблицу. Пройдём по моментам, в которые связь с Redis может исчезнуть.
| Момент | Что известно | Что не известно |
|---|---|---|
| T0. Main принял вызов и создал запись выполнения, но не поставил талон в очередь | Запись в БД есть, внешних действий не было | Дойдёт ли талон до worker |
| T1. Талон в очереди, worker его ещё не получил | Внешних действий нет | Кто и когда его заберёт после восстановления |
| T2. Worker получил талон и читает данные из БД | Внешних действий, скорее всего, нет | На каком узле остановился |
| T3. Worker отправил запрос в CRM, ответа нет | Исход неизвестен | Создала ли CRM заказ |
| T4. CRM ответила, но запись в таблицу не выполнена | Внешнее действие, вероятно, совершено | Зафиксирован ли ответ в данных выполнения |
Точка T3 самая неприятная. Запрос ушёл, CRM могла его обработать, а могла и не получить. Если оборвалась связь только с Redis, worker может ещё дождаться ответа и записать его в БД. Если же упал весь процесс, ответ потерян, хотя заказ в CRM уже мог появиться. Сама по себе очередь здесь ничего не гарантирует: подтвердить, что внешнее действие произошло ровно один раз, она не способна.
Чтобы эта неопределённость стала осязаемой, разложим T3 по времени. Допустим, worker отправил POST в CRM в 12:00:00.0. В 12:00:00.4 CRM приняла запрос и создала заказ. В 12:00:00.7 пропала связь worker с Redis. В 12:00:01.2 CRM отправила ответ. Дальше возможны три развития, и снаружи они выглядят почти одинаково: красный Redis и выполнение без данных узла HTTP Request.
- Процесс worker жив. Он дожидается ответа, и если база доступна, сохраняет данные узла. Тогда в истории появится след, и выполнение превратится в случай T4.
- Процесс worker упал вместе с соединением. Ответ CRM никому не адресован. Заказ в CRM есть, а в истории выполнения его нет.
- CRM запрос не получила. Заказа нет, история выглядит точно так же, как во втором варианте.
Второй и третий варианты невозможно различить по одной лишь истории n8n. Различает их только журнал или поиск по ключу в самой CRM. Поэтому отметка «исход неизвестен» на временной линии не слабость анализа, а честная запись того, чего данные не содержат. Пока вы не сверились снаружи, ни «выполнено», ни «не выполнено» писать в тикет нельзя.
Есть и обратная сторона: пока в базе есть запись о выполнении, а о завершении узла нет, ничто в самой записи не мешает вам нажать повтор. Кнопка не знает про CRM. Она знает только про сохранённые данные, о чём подробнее в разделе про Retry.
Как выглядят эти моменты в истории выполнений
Следы остаются в списке выполнений и в данных каждого узла. Логика чтения проста: смотрим, у каких узлов есть сохранённые выходные данные, а какой был последним с результатом.
- Выполнение со статусом «в ожидании» и без данных узлов чаще всего соответствует T0 или T1. Внешних действий не было, повтор безопасен с точки зрения побочных эффектов.
- Выполнение, у которого есть данные Set, но нет данных HTTP Request, находится на границе T2 и T3. Здесь нужно сверяться: данных об отправке нет, но это не значит, что запрос не уходил.
- Выполнение с данными HTTP Request, но без записи в таблицу, указывает на T4. Внешнее действие совершено, ответ у вас есть.
Обратите внимание на асимметрию. Наличие данных узла говорит «действие завершено и зафиксировано». Отсутствие данных не говорит «действия не было», оно говорит «записи о завершении нет». Это разные утверждения, и вся сверка строится на этой разнице.
Что на самом деле делает Retry
Кнопка Retry в списке выполнений выглядит как «запусти заново». Но по описанию в разделе о повторе выполнений она продолжает сохранённое выполнение, а не запускает всю цепочку с триггера. Это хорошая новость: узлы, уже отработавшие и сохранившие результат, повторно не выполняются.
Теперь вторая половина того же описания: упавший узел повторно получает все свои входные элементы, включая те, что были обработаны частично. Здесь и прячется ловушка.
Разберём пример. HTTP Request получил на вход 50 элементов, то есть 50 заказов для создания в CRM. На 31-м элементе связь оборвалась, выполнение осталось с ошибкой на этом узле. CRM успела создать 30 заказов, возможно, 31-й тоже. Retry вернёт на вход узла все 50 элементов. Если CRM не отличает дубль от нового заказа, вы получите 30 или 31 повтор.
Отсюда правило: Retry безопасен там, где узел идемпотентен по своей природе или по вашему замыслу, и опасен там, где каждое обращение создаёт новую сущность. Собственные инструменты защиты нужно строить сами. Например, передавать во внешнюю систему стабильный ключ, вычисленный из данных элемента (номер заказа в источнике), а не из номера попытки. Свежий идентификатор попытки сам по себе дубли не предотвратит: на каждый повтор он будет другим, и внешняя система увидит «новый» запрос. Принимает ли CRM такой ключ и честно ли им пользуется, нужно проверить в её документации.
Вернёмся к примеру с 50 заказами и посмотрим, что даёт стабильный ключ. Допустим, номер заказа в источнике (например, A-1001 ... A-1050) передаётся в CRM как внешний идентификатор. После сбоя на 31-м элементе Retry снова отправит все 50 запросов. Если CRM по этому идентификатору отвечает «такой заказ уже есть» или обновляет существующий, то 30 повторов станут безвредными обращениями. Если же ключ не передавался, CRM получит 50 запросов, неотличимых от новых.
Сравните два варианта по последствиям.
| Что передаётся в CRM | Что CRM видит при Retry | Итог для 30 уже созданных заказов |
|---|---|---|
| Номер заказа из источника | Тот же ключ, что и в первой попытке | Может распознать повтор, если поддерживает такую проверку |
| Идентификатор попытки или случайное значение | Каждый раз новый ключ | Новый запрос, дубль почти неизбежен |
| Ничего | Набор полей без метки | Зависит от того, ищет ли CRM совпадения по содержимому |
Условие в первой строке принципиально. Стабильный ключ ничего не защищает сам по себе: он лишь даёт внешней системе возможность узнать повтор. Поддерживает ли CRM такую проверку, видно только в её документации и в пробном запуске на тестовых данных.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Частичный успех: когда половина работы уже сделана
Пакетные узлы дают самый запутанный вид сбоя. Выполнение помечено ошибкой, но внешний мир уже изменился. Порядок разбора такой.
- Определить границу. Какой узел был последним, у которого есть сохранённый результат, и какой узел упал. Это даёт список элементов, которые могли быть затронуты.
- Составить список ключей. Для каждого входного элемента упавшего узла у вас должен быть ключ, по которому можно найти его след во внешней системе: номер заказа, e-mail, внешний идентификатор.
- Сверить с внешней системой. Запросить из CRM записи по этим ключам. Результат делит элементы на три группы: создано, не создано, спорно (запись есть, но неполная).
- Решить по группам. Для «не создано» повтор разумен. Для «создано» нужно только восстановить недостающие следы, например запись в таблицу. «Спорные» разбирает человек.
Практический приём: вместо Retry всего выполнения собрать небольшой отдельный workflow для добора. Он принимает только список «не создано» и выполняет действие для них. Выходит медленнее, чем нажать кнопку, но исключает ситуацию, когда вы повторили то, что уже случилось.
Разберём, как выглядит добор на тех же 50 заказах. Сначала по ключам A-1001 ... A-1050 вы запрашиваете из CRM список существующих записей. Допустим, найдено 31 запись: заказы с 1-го по 30-й и 31-й, который успел создаться перед обрывом. Остаётся 19 ключей без следа. Они и идут в отдельный workflow для добора.
Но на этом работа не заканчивается. У 31 найденной записи нужно проверить полноту: если CRM создала заказ, но не успела заполнить позиции, это спорный случай, и его лучше отдать человеку. Для 30 заказов, до которых цепочка могла не дойти, ещё нужно восстановить недостающий след, то есть запись в таблицу, которой в исходном выполнении не случилось. Итог добора принимает такой вид:
- 19 элементов: действие выполняется заново;
- 30 элементов: только запись результата в таблицу, без обращения к CRM за созданием;
- 1 элемент: ручной разбор по неполной записи.
Такой расклад показывает, почему «нажать Retry» и «разобраться» дают разный результат. Первое отправит 50 запросов, из которых 31 заведомо лишний. Второе отправит 19 нужных и не затронет остальные.
Связь вернулась: как не устроить второй сбой
Когда Redis снова доступен, возникает соблазн нажать «повторить» на всех красных строках разом или очистить очередь, чтобы «начать с чистого листа». Оба действия опасны по своим причинам.
Очистка Redis вслепую отбрасывает талоны, о которых вы ничего не знаете. Часть из них относилась к выполнениям на стадии T0 или T1, то есть к заданиям, которые никто не начинал, а теперь они потеряются без следа в очереди. Запись в БД останется, но двигать её будет некому. Массовый Retry, в свою очередь, прогонит через упавшие узлы все их входные элементы, включая уже обработанные.
Опасность очистки хорошо видно на временной линии. Допустим, перед обрывом было три выполнения. Первое на стадии T1: талон в очереди, worker его не взял. Второе на стадии T3: запрос в CRM отправлен, исход неизвестен. Третье на стадии T4: ответ CRM получен. Если очистить Redis, пропадёт талон первого выполнения, и в базе останется запись, которую никто не продолжит. Второе и третье выполнения уже не зависят от этого талона так, как первое, но их судьбу очистка не прояснит: исход T3 всё равно придётся выяснять сверкой с CRM.
Получается, что очистка не решает ни одну из задач. Для T1 она ломает то, что можно было бы продолжить, для T3 и T4 она бесполезна. Поэтому полезнее сначала составить перечень выполнений без завершения и сопоставить каждое со стадией, чем удалять данные транспорта.
Такой же довод относится к массовому Retry. Он выглядит милосердным, потому что продолжает сохранённые выполнения, а не начинает заново. Но на узле T3 он вернёт на вход все входные элементы, и если внешнее действие уже случилось, оно случится ещё раз. Поэтому порядок «сначала безопасные, затем внешние» в списке ниже не вопрос вкуса: он отражает разную цену ошибки на разных стадиях.
Спокойный порядок выглядит так:
- Убедиться, что связь стабильна: main и все worker видят Redis, а Redis видит себя же в порядке. Включать нагрузку на нестабильный канал значит получить второй обрыв посреди разбора первого.
- Отсортировать выполнения по следам, как в таблице выше: без данных узлов, с данными до внешнего узла, с данными внешнего узла.
- Начать с безопасных: выполнения без следов внешних действий. Их повтор не должен дублировать эффект.
- Выполнения вокруг внешних узлов разбирать вручную с проверкой во внешней системе.
- Только потом решать, нужно ли что-то менять в самой инфраструктуре.
О самой инфраструктуре: если Redis работает на отдельном VPS, разумно проверить, хватает ли ему памяти и не рвётся ли сеть между машинами. Тарифы MAATRIX различаются по ресурсам (от Ba с 1 vCPU и 2 ГБ RAM до Djed с 16 vCPU и 32 ГБ), но сами цифры не обещают определённую ёмкость очереди, её нужно измерять на своей нагрузке. Диски NVMe и root-доступ помогают развернуть и наблюдать тестовый контур, а сама устойчивость определяется вашей конфигурацией и повторяемостью проверок. В каталоге есть n8n с установкой, но состояние наличия и ОС проверяется в форме заказа.
Как устроить репетицию обрыва
Лучший способ узнать, что ваша система делает при потере Redis, провести обрыв на тестовом контуре до того, как он случится на боевом. Нужны отдельный n8n в queue mode, отдельный Redis и тестовый внешний приёмник, который записывает каждый входящий запрос с ключом. Затем:
- запустить workflow, у которого узел HTTP Request обращается к приёмнику с искусственной задержкой в несколько секунд;
- в это окно закрыть доступ к Redis файрволом или остановить сервис;
- вернуть доступ и сравнить три источника: список выполнений в n8n, данные узлов и журнал приёмника;
- повторить обрыв в разные моменты: до запроса, во время, после ответа.
Расхождения между журналом приёмника и историей выполнений и есть те самые зоны неизвестного исхода. Увидев их вживую, вы заранее поймёте, где в ваших workflow нужны ключи сверки, а где можно спокойно жать Retry.
Решение по следам, а не по индикатору
Когда перед вами зависшее выполнение, задайте ему несколько вопросов в таком порядке. Есть ли у него сохранённые данные узлов? Какой узел последний с результатом? Стоит ли за упавшим узлом внешнее действие, и есть ли у входных элементов ключ для поиска их следа снаружи? Если внешнего действия нет, повтор через Retry обычно безопасен. Если оно есть и ключ найден, сверяйте и добирайте только недостающее. Если ключа нет, считайте исход неизвестным и отдавайте человеку, у которого есть доступ к внешней системе.
Красный индикатор Redis объясняет, почему всё остановилось, но не подсказывает, что делать с каждым конкретным выполнением. Это подсказывают следы в истории и журнал той системы, куда worker успел постучаться.