0001: nonce idempotency

ADR-0001: Идемпотентность сообщений через nonce + enforce_nonce

Дата: 2026-01-26 Статус: Accepted

Контекст

Клиентские ретраи (таймауты, повторная отправка, нестабильная сеть) приводят к созданию дублей сообщений: один и тот же “логический” send превращается в несколько записей в БД.

Нам нужно поведение, близкое к Discord: идемпотентность по nonce включается явно флагом enforce_nonce и действует ограниченное время (окно “несколько минут”), а не навсегда.

Также важно, чтобы HTTP и WebSocket давали одинаковый результат: WebSocket не должен обходить защиту от дублей.

Решение

  1. Принять контракт nonce + enforce_nonce для создания сообщений:

  • nonce — идентификатор попытки отправки (короткая строка/число).

  • enforce_nonce — включает идемпотентность по nonce.

  1. Реализовать дедупликацию через Redis с TTL:

  • Использовать ключ вида nonce:{author}:{nonce}.

  • Ставить ключ атомарно с TTL (окно идемпотентности).

  • При повторе с тем же nonce возвращать уже созданное сообщение (по сохранённому message_id).

  1. Централизовать создание сообщений:

  • HTTP и WebSocket должны вызывать один общий сервис создания сообщения, чтобы правила идемпотентности применялись одинаково.

Почему так

  • Уникальный индекс в Postgres даёт “вечную” идемпотентность, а нам нужно окно по времени.

  • Redis с TTL естественно моделирует “несколько минут” и сам очищает ключи.

  • Атомарная постановка ключа защищает от гонок при параллельных запросах.

  • Единый сервис исключает расхождение логики между HTTP и WebSocket.

Последствия

  • Для enforce-режима появляется зависимость от Redis (нужно отражать в инфраструктуре и проверках здоровья).

  • Нужно поддерживать одинаковую схему обработки в двух транспортных слоях (HTTP/WS).

  • В логах/трассировке полезно хранить nonce у сообщения (для отладки и расследования повторов).

Последнее обновление