Дубли в n8n при повторе: проверка с ИИ

Дубли в n8n при повторе: проверка с ИИ. Учебный вход, готовый промпт и пример. Проверка по исходным данным, ограничения метода и следующий шаг для своей задачи.

Если при повторе доставки в n8n одна и та же заявка может записаться дважды, корень проблемы обычно не в самом повторе, а в том, что приёмник не различает повтор и новое событие. Дубли в n8n при повторе возникают предсказуемо в одной ситуации: workflow отправил данные, получил таймаут, повторил отправку — а первая запись уже была сохранена. Разобрать это можно логикой, без развёрнутого n8n и без реального прогона workflow. Нужны постоянный ключ события, контракт атомарной записи на стороне приёмника и протокол с эталонными исходами. ИИ в этой задаче проектирует схему и проверки, но не исполняет workflow и не даёт гарантий за бизнес-приёмник.

Учебный пример ниже полностью вымышленный: это не клиентский кейс и не выполненный тест нейросети. Все входы и ожидаемые результаты приведены целиком.

Найдите неясный исход записи

Исходная точка — доставка D1: приёмник фиксирует заявку A, а ответ до n8n не доходит и превращается в таймаут. С точки зрения отправителя результат неизвестен. С точки зрения приёмника запись уже есть. Если после таймаута n8n запускает повтор, отсутствие ответа нельзя читать как отсутствие заявки.

Это и есть место, где появляются дубли. Повтор безопасен только тогда, когда приёмник умеет отличить «я это уже видел» от «это новое». Сам по себе повторный запуск не является ошибкой: без повторов временный сбой оставит заявку необработанной. Ошибка — повтор без проверки состояния записи.

n8n предлагает инструменты для обработки сбоев: для workflow можно задать error workflow в настройках, он запускается при неудачном исполнении, и такой workflow должен начинаться с Error Trigger (Handle errors gracefully | Build | n8n Docs). Это помогает узнать о сбое и разобрать его, но уведомление об ошибке не заменяет проверку того, была ли запись.

Зафиксируйте контракт приёмника

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

  1. Стабильный ключ события. У каждой заявки есть постоянный event_id, который не меняется между попытками доставки. Не порядковый номер запуска, не timestamp, а идентификатор самого события.
  2. Сравнение payload. Приёмник хранит сохранённое содержимое события и сравнивает его с новым. Совпадающий payload при том же ключе — тот же самый факт. Другой payload при том же ключе — конфликт, а не повод перезаписать.
  3. Постоянный уникальный ключ. Уникальность event_id обеспечивает хранилище приёмника, а не фильтр в памяти workflow.
  4. Атомарная операция. Проверка и запись выполняются как одна неделимая операция в приёмнике. Раздельные «сначала посмотреть, потом вставить» в двух параллельных исполнениях гонку не закрывают.

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

Узел Remove Duplicates в n8n работает в нескольких режимах: удаление повторов внутри текущего входа, сравнение с прошлыми исполнениями и очистка истории дедупликации (Remove Duplicates | Nodes | n8n Docs). Для режима сравнения с прошлыми исполнениями узел принимает параметр, по которому сравнивает, и при варианте «значение новое» это должно быть поле или набор полей с уникальным ID (Remove Duplicates | Nodes | n8n Docs). Это полезный фильтр, но история этого узла — не то же самое, что уникальная запись в бизнес-приёмнике. Её срок жизни, ротация и поведение при параллельных исполнениях требуют отдельной проверки, и полагаться на неё как на гарантию отсутствия второй записи нельзя.

Разберите четыре доставки и гонку

Контракт приёмника в примере: постоянная уникальность по event_id, атомарная операция «вставить или прочитать», сравнение сохранённого payload. Внешних записей в этом учебном разборе нет. Начальное состояние — пустая таблица.

Последовательность доставок:

Попытка event_id payload Что происходит
D1 A {lead: L1} приёмник фиксирует A, ответ теряется по таймауту
D2 A {lead: L1} повтор после таймаута, тот же payload
D3 A {lead: OTHER} тот же ключ, другой payload
D4 B {lead: L2} новое событие

Отдельно гонка: два параллельных исполнения P1 и P2 с одним event_id C и одним payload {lead: L3} при гарантированном уникальном ограничении в приёмнике.

Эталонные исходы для этой последовательности:

  • D1 — запись A зафиксирована, ответ неизвестен. Перед любой новой бизнес-записью нужно прочитать состояние по ключу A.
  • D2 — по ключу A найдено событие с тем же payload. Возвращается исходная L1, вторая вставка не выполняется.
  • D3 — тот же ключ A с другим payload. Это конфликт: событие уходит в карантин на ручной разбор, сохранённая L1 не перезаписывается.
  • D4 — новый ключ B, вставка L2.
  • После D1–D4 в таблице две строки: {event_id: A, lead: L1} и {event_id: B, lead: L2}. Заявка A существует один раз, несмотря на таймаут и повтор.
  • P1/P2 — одно исполнение вставляет C, второе при конфликте уникальности читает уже существующую запись с тем же payload. В итоге одна C. Это верно только при гарантированном атомарном контракте приёмника; при раздельной проверке и вставке результат гонки не гарантирован.

Проверки, которые должны сойтись: A создаётся один раз несмотря на таймаут; другой payload под A не перезаписывает L1; D1–D4 дают две строки; P1/P2 дают одну C только при гарантированном контракте приёмника; история Remove Duplicates и состояние бизнес-приёмника не считаются одним хранилищем.

Ограничения примера: n8n не развёрнут и workflow реально не запускался. Это протокол, а не импортируемый JSON с необходимой инфраструктурой. Срок жизни истории, её ротация, параллельность и повтор побочных эффектов требуют отдельной проверки.

Составьте промпт и план проверки workflow

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

Ты помощник по задаче: спроектировать и проверить логикой журнал доставки заявки так, чтобы timeout после записи не приводил ко второй записи. Не выдавай один фильтр n8n за exactly-once. Работай только по полным inputs ниже. Не добавляй неизвестные значения и не выдавай учебные данные за результат реального сервиса. Ничего не публикуй и не изменяй во внешних системах.

ПРАВИЛА:
- У каждой заявки постоянный event_id; payload сравнивается с сохранённым.
- Уникальность event_id обеспечивает постоянное хранилище приёмника, а не фильтр в памяти workflow.
- Проверка и запись — атомарная операция в приёмнике; раздельные check-then-insert гарантии не дают.
- Совпадающий payload при том же ключе — то же событие, вторая запись не создаётся.
- Другой payload при том же ключе — конфликт, сохранённое значение не перезаписывается.
- Если контракт приёмника не гарантирует транзакционную уникальность и атомарность, exactly-once не заявлять.

INPUTS:
{"receiver_contract": "Persistent unique(event_id); atomic insert-or-read; stored payload comparison; no external writes in this tutorial", "initial_rows": [], "deliveries": [{"attempt": "D1", "event_id": "A", "payload": {"lead": "L1"}, "outcome": "receiver commits A then response times out"}, {"attempt": "D2", "event_id": "A", "payload": {"lead": "L1"}, "outcome": "retry after timeout"}, {"attempt": "D3", "event_id": "A", "payload": {"lead": "OTHER"}, "outcome": "same key conflicting payload"}, {"attempt": "D4", "event_id": "B", "payload": {"lead": "L2"}, "outcome": "new event"}], "parallel_case": {"attempts": ["P1", "P2"], "event_id": "C", "payload": {"lead": "L3"}, "receiver": "atomic UNIQUE enforced"}}

ЗАДАЧА:
1. Для каждой попытки D1–D4 определи исход и обоснуй его по правилам.
2. Определи итоговое состояние таблицы после D1–D4.
3. Разбери параллельный случай P1/P2 и укажи, при каком контракте приёмника результат гарантирован.
4. Отметь неизвестные и исключения; при противоречии не выдумывай ответ, укажи точный вход и требуемое уточнение.

ВЫХОДНАЯ СХЕМА: JSON с полями result (по каждой попытке: attempt, outcome, reason), final_rows, parallel_case, unknowns, exceptions.

Как использовать результат. Сверьте ответ модели с эталоном из раздела выше — совпадение D1–D4, двух строк после них и условия для P1/P2. Затем проверьте саму схему workflow, но не выводами модели, а наблюдаемыми фактами:

  • есть ли в workflow постоянный event_id от источника заявки, а не генерируемый при каждом запуске;
  • различает ли приёмник совпадающий payload и конфликтующий;
  • выполняет ли приёмник проверку и запись атомарно;
  • не остаётся ли состояние дедупликации единственной защитой от второй записи;
  • сохраняется ли разбор конфликтов и таймаутов в журнале, пригодном для ручного разбора.

Технические сверки схемы и повторов можно поручить агенту или коду по этим правилам. Человек нужен там, где не хватает бизнес-условий: что делать с конфликтом payload, каков срок хранения ключей и какие исключения допустимы. Это ручное решение, а не вычитка каждой строки.

Для соседних задач по устройству агентских систем полезны материалы про петлю обучения агентской системы и ограничение расходов ИИ-агента. Оба разбирают, как ошибки и стоимость превращаются в правила, но не заменяют контракт приёмника.

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

Учебный пример

Ниже — полный учебный набор из промпта: контракт, начальное состояние, доставки и параллельный случай, а затем эталонные исходы и проверки. Все данные вымышлены для этой задачи; это не импортируемый JSON и не выполненный тест workflow.

{
  "inputs": {
    "receiver_contract": "Persistent unique(event_id); atomic insert-or-read; stored payload comparison; no external writes in this tutorial",
    "initial_rows": [],
    "deliveries": [
      {
        "attempt": "D1",
        "event_id": "A",
        "payload": {"lead": "L1"},
        "outcome": "receiver commits A then response times out"
      },
      {
        "attempt": "D2",
        "event_id": "A",
        "payload": {"lead": "L1"},
        "outcome": "retry after timeout"
      },
      {
        "attempt": "D3",
        "event_id": "A",
        "payload": {"lead": "OTHER"},
        "outcome": "same key conflicting payload"
      },
      {
        "attempt": "D4",
        "event_id": "B",
        "payload": {"lead": "L2"},
        "outcome": "new event"
      }
    ],
    "parallel_case": {
      "attempts": ["P1", "P2"],
      "event_id": "C",
      "payload": {"lead": "L3"},
      "receiver": "atomic UNIQUE enforced"
    }
  },
  "expected_outputs": {
    "D1": "committed_but_response_unknown; lookup A before any new business record",
    "D2": "existing_same_payload; return original L1, no second insert",
    "D3": "conflict_quarantine; do not overwrite L1",
    "D4": "new_insert L2",
    "rows_after_D1_D4": [
      {"event_id": "A", "lead": "L1"},
      {"event_id": "B", "lead": "L2"}
    ],
    "parallel_case": "one insert C commits, the other reads existing same payload after unique conflict; final one C",
    "unsupported_claim": "no exactly-once guarantee if receiving system lacks transactional uniqueness/atomicity"
  },
  "checks": [
    "A создаётся один раз несмотря на таймаут",
    "Другой payload под A не перезаписывается",
    "D1-D4 дают 2 строки",
    "P1/P2 дают одну C только при гарантированном контракте приёмника",
    "RemoveDuplicates history и состояние бизнес-приёмника не считать одним хранилищем"
  ],
  "limitations": [
    "n8n не развёрнут и не реальный тест workflow",
    "Пример протокола, не импортируемый JSON без необходимой инфраструктуры",
    "TTL, ротация истории, параллельность и повтор побочных эффектов нуждаются в отдельной проверке"
  ]
}

Эталон — блок expected_outputs целиком: один раз A, конфликт D3 без перезаписи, две строки после D1–D4 и одна C в параллельном случае при гарантированном UNIQUE. Если ответ модели расходится с этим блоком, ошибка либо в логике модели, либо в контракте приёмника; разбирать нужно конкретный вход, а не переписывать эталон под ответ.

Валерий Курземнек

Автор метода «Машиночитаемый маркетинг»

Что ещё почитать