Автоматизация

Защита от дублей в автопостинге: ключ, ON CONFLICT и очередь

30 октября 2026 · 3 мин чтения

Кратко

Дубли убирают на уровне базы, а не сценария: каждому предложению присваивают уникальный ключ и вставляют его через ON CONFLICT DO NOTHING. Очередь на отправку забирают с FOR UPDATE SKIP LOCKED, а статусы new, sending и sent позволяют безопасно повторять любой шаг.

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

Откуда берутся дубли

  • Повторный запуск по расписанию или вручную.
  • Повторная доставка вебхука: отправитель не получил ответ и прислал запрос ещё раз.
  • Пересечение источников: одно и то же предложение приходит из двух мест.
  • Сбой посередине: сообщение ушло, а отметка об этом не сохранилась, поэтому при повторе оно уйдёт снова.

Шаг 1: уникальный ключ предложения

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

CREATE TABLE offers (
    id           bigserial PRIMARY KEY,
    dedup_key    text NOT NULL UNIQUE,
    payload      jsonb NOT NULL,
    status       text NOT NULL DEFAULT 'new',   -- new, sending, sent, failed
    created_at   timestamptz NOT NULL DEFAULT now(),
    published_at timestamptz
);

Ограничение UNIQUE не даст вставить второй раз то же самое, даже если два сценария запущены одновременно.

Шаг 2: вставка без дублей

INSERT INTO offers (dedup_key, payload)
VALUES ($1, $2)
ON CONFLICT (dedup_key) DO NOTHING
RETURNING id;

Если запись новая, запрос вернёт её id. Если такой ключ уже есть, вернётся пустой результат, и вы просто пропускаете предложение. Это работает без отдельной проверки «есть ли уже такое», которая сама подвержена гонкам.

Шаг 3: очередь на отправку

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

UPDATE offers
SET status = 'sending'
WHERE id = (
    SELECT id FROM offers
    WHERE status = 'new'
    ORDER BY id
    FOR UPDATE SKIP LOCKED
    LIMIT 1
)
RETURNING id, payload;

FOR UPDATE SKIP LOCKED блокирует выбранную строку и пропускает те, что уже взяты другим обработчиком. Каждый обработчик получает свою запись.

Шаг 4: статусы и повтор

После отправки отметьте результат:

UPDATE offers SET status = 'sent', published_at = now() WHERE id = $1;
-- при ошибке:
UPDATE offers SET status = 'failed' WHERE id = $1;

Теперь любой шаг можно безопасно повторить:

  • записи со статусом failed можно отправить снова;
  • записи, зависшие в sending дольше нескольких минут, возвращаются в new отдельным заданием (так вы не теряете сообщения при падении обработчика).

Нюансы

  • Сообщение могло уйти, а отметка нет. Если процесс упал между отправкой и обновлением статуса, при повторе сообщение уйдёт снова. Для критичных случаев храните идентификатор отправленного сообщения и проверяйте перед повтором. Для постов в канал обычно достаточно небольшой возможной повторной публикации.
  • Ключ должен быть стабильным. Не включайте в него поля, которые меняются при каждой выгрузке (время запроса).
  • Старые записи. Чистите или архивируйте таблицу по расписанию: ограничение уникальности работает, пока запись существует. Если нужно помнить ключ дольше, храните только ключи.
  • Пауза между отправками защищает от лимитов Telegram.

Как это встроено в n8n

В сценарии n8n это узлы PostgreSQL: вставка с ON CONFLICT, выбор из очереди с блокировкой, отправка в Telegram и обновление статуса. Состояние лежит в базе, поэтому перезапуск сценария ничего не ломает. Общая схема конвейера описана в статье автопостинг в Telegram на n8n и PostgreSQL, а приём данных с сайта в защите webhook в n8n. Такой подход использован в проектах HotTrip и Da Nang House. Услуга: «Контент и соцсети».

Частые вопросы

Почему нельзя проверять дубли в сценарии?

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

Что делать, если у предложений нет естественного ключа?

Сформируйте его из набора полей (источник, идентификатор у источника, дата) и храните в виде хеша или строки. Главное, чтобы ключ не менялся при повторной выгрузке.

Как обработать «зависшие» записи?

Периодическим заданием возвращайте записи со статусом `sending` старше заданного времени обратно в `new` и пишите об этом в лог.

Подходит ли это для других баз?

Идея универсальна, но синтаксис отличается. `ON CONFLICT` и `SKIP LOCKED` есть в PostgreSQL, в других базах используют свои аналоги.

PostgreSQLдедупликацияавтопостингn8nочередь

Версия для ИИ и чтения без оформления: Markdown.

Читайте также

Telegram · 30 октября 2026

Уведомления в Telegram Mini App: как слать пуши через бота

У Mini App нет собственных пушей: уведомления приходят как сообщения бота, а писать пользователю можно только после его запуска бота или запроса разрешения requestWriteAccess. Чтобы открыть нужный экран, используйте кнопку web_app или прямую ссылку с параметром startapp. Пользователей, заблокировавших бота, нужно отмечать и не беспокоить.

Telegram · 30 октября 2026

Telegram-бот на n8n: inline-кнопки, меню и кнопка для открытия Mini App

В n8n бот строится из Telegram Trigger, ветвления по типу события и узла отправки. Нажатия inline-кнопок приходят как callback_query и требуют ответа. Кнопку, открывающую Mini App, проще отправить через HTTP Request, а у бота может быть только один webhook.

Бизнес · 30 октября 2026

Сколько стоит Telegram-бот и Mini App: из чего складывается цена

Цена бота зависит от сценариев, интеграций и числа ролей, цена Mini App ещё и от интерфейса, API и данных. Простой бот стоит заметно меньше каталога с подписками. Экономия достигается запуском минимальной версии и ясным описанием задачи.

Расскажите о задаче

Опишите, что нужно сделать, даже в паре строк. Отвечу с вопросами или оценкой.

Обсудить проект →