Короткий ответ При at-least-once повторная доставка нормальна, поэтому уникальность нельзя строить на времени получения или surrogate id хранилища. Идемпотентный ключ должен одинаково идентифицировать одно логическое событие при каждом ретрае. Как это работает подробнее Сначала дедуплицируйте сам batch в staging, затем сопоставляйте его с целевой таблицей. Победителя выбирайте детерминированно по версии, времени обновления и дополнительному tie-breaker, иначе повторный запуск может дать другой результат. В ClickHouse ReplacingMergeTree(version) удаляет старые версии не мгновенно: до фонового merge запросы могут видеть несколько строк. Критичные чтения должны использовать агрегацию по версии или осознанно применять FINAL. Пример кода Что использовать на практике Закрепите контракт event_id. Опишите генерацию ключа, версию события и семантику поздних изменений. Храните сырой слой. Он позволяет повторить дедупликацию после исправления правила. Тестируйте ретраи. Повтор одного batch и параллельные загрузки должны