К содержанию
ActionPulse

Идемпотентность и повторы

Три разных механизма безопасного повтора в ActionPulse — дедупликация событий по event_id, заголовок Idempotency-Key у отметки релиза и обычные повторы по коду ответа.

Кому
Разработчикам, которым важна безопасность повторной отправки
Проверено
На этой странице

Повтор запроса — обычное дело: оборвалась сеть, истёк таймаут, перезапустился процесс. Вопрос всегда один: не создаст ли повтор второй объект. В ActionPulse на этот вопрос отвечают три разных механизма, и путать их дорого.

Три механизма, которые нельзя смешивать

Где Чем обеспечивается Что гарантирует
Приём событий (/v1/track) поле event_id в элементе пакета повтор с тем же event_id не создаёт второе событие
Отметка релиза из CI/CD заголовок Idempotency-Key повтор с тем же ключом возвращает уже созданную отметку
Остальные методы приложения ничем повтор может создать второй объект

Приём событий: ключ дедупликации — event_id

Дедупликация приёма работает по одному полю: event_id внутри элемента пакета. Это не заголовок и не поле запроса целиком — у каждого события в пакете свой ключ.

Требования к значению:

  • канонический UUID; значение, которое не разбирается как UUID, отклоняет элемент пакета с кодом invalid_event_id, а не запрос;
  • регистр и форма приводятся к каноническому виду сервером, так что 8B0D3F2E-… и 8b0d3f2e-… — один и тот же ключ;
  • поле необязательное. Если его не передать, сервер сгенерирует значение сам, и дедупликация станет невозможной: каждая попытка отправки создаст новое событие.

Что важно знать про границы этой защиты:

  • Окно. Дедупликация действует в пределах окна приёма — 48 часов. Повтор через большее время создаст второе событие.
  • Источник. Идентичность события привязана к типу ключа, которым оно отправлено. Один и тот же event_id, отправленный браузерным токеном и серверным ключом, — это два разных события. Повторяйте отправку тем же типом ключа.
  • Подтверждение. 202 возвращается только после того, как очередь приёма подтвердила запись всех элементов. Ответ 500 означает, что не принято ничего: повторяйте весь пакет целиком, дубликатов не будет.
  • Отказ по элементу — не повод повторять. Элементы из массива rejected отклонены по содержанию. Повтор без исправления вернёт тот же отказ.

Связывание личности и запись сессий

/v1/identify и /v1/alias устанавливают связь анонимного идентификатора с пользователем. Сама связь идемпотентна: повторный вызов с той же парой anon_id/user_id оставляет то же состояние. Но /v1/identify дополнительно порождает событие identify, и у этого события нет вашего event_id — значит, каждый вызов добавит ещё одно такое событие. Если вам нужна только связь без события, используйте /v1/alias (браузерным токенам он недоступен по замыслу — только серверным и старым ключам).

Запись сессий (/v1/replay) идемпотентна по построению: ключ — тройка «проект, сессия, номер чанка». Повторная отправка того же чанка отвечает 202 с признаком duplicate, а не создаёт второй чанк.

Заголовок Idempotency-Key: отметка релиза из CI/CD

Единственная клиентская операция, которая объявляет этот заголовок, — создание отметки релиза деплоем: POST /api/v1/projects/YOUR_PROJECT_ID/release-markers/deploy. Она специально сделана для CI/CD, где повторный запуск шага — норма. Полное описание метода — в разделе Alerts.

Аутентификация здесь тоже отдельная: не токен доступа пользователя, а узкий проектный CI/CD-токен с единственным правом «писать отметки релизов». Он выпускается запросом к API от имени администратора проекта и показывается один раз; приём событий его не принимает, а обычные ключи приёма не принимает эта операция.

Требования к значению ключа:

Требование Значение
Длина от 8 до 128 символов
Допустимые символы видимые символы ASCII, кроме пробела и запятой
Количество заголовков ровно один; два заголовка Idempotency-Key — это 422
Обязательность обязателен, без него запрос не выполняется

UUID или идентификатор задания сборки подходят под эти требования. Хорошее значение — стабильное для одного деплоя и разное для разных: если вы возьмёте константу, второй деплой не создаст отметку.

Что происходит при повторе:

Ситуация Ответ
Новый ключ 201 и созданная отметка
Тот же ключ, то же тело 200, заголовок Idempotency-Replayed: true и та же отметка
Тот же ключ, другое тело 409, код validation_failed, сообщение о повторном использовании ключа и details.header

Тела сравниваются целиком в нормализованном виде: достаточно отличия в ts или version, чтобы получить 409. Поэтому не пересчитывайте ts в момент повторной попытки — берите одно и то же значение из состояния сборки.

Ни один другой клиентский метод этот заголовок не читает. Отправить его можно — он просто не даст никакого эффекта, и полагаться на это нельзя.

Обычные повторы: что повторять, а что нет

Всё остальное API идемпотентности не обещает. Решение о повторе принимается по коду ответа.

Ответ Повторять Как
408 и 504 query_timeout да сузьте диапазон дат или фильтры, потом повторите
429 rate_limited да по Retry-After, иначе с растущей задержкой
429 quota_exceeded, trial_quota_exceeded нет до следующего периода или до оплаты тарифа
500 internal да с задержкой, ограниченным числом попыток
503 да, осторожно часть таких ответов означает, что изменение уже применено
409 privacy_job_active да ответ помечен details.retryable = true
409 в остальных случаях нет конфликт состояния: прочитайте состояние и решите заново
400, 422 нет некорректный запрос, повтор вернёт то же
401 нет обновите токен доступа и вызовите заново — это не повтор
402, 403 нет доступ приостановлен или недостаточно прав
404 нет объекта нет либо он не в вашей организации
413 нет уменьшите тело запроса

Как считать задержку

  1. Если пришёл Retry-After — ждите ровно столько. Он приходит в секундах.
  2. Иначе — экспоненциальная задержка: первая пауза около секунды, дальше удвоение до потолка в 30 секунд. Так устроен браузерный SDK, и это разумное значение по умолчанию для серверного клиента.
  3. Добавляйте случайное отклонение (jitter): без него все ваши процессы постучатся в одну и ту же секунду.
  4. Ограничивайте число попыток и не повторяйте параллельно одним ключом приёма — бюджет частоты у него общий.
  5. После исчерпания попыток складывайте события в свой буфер и логируйте факт. Подробнее — в ограничениях частоты и квотах.

Что проверить в своём клиенте

  • event_id генерируется в момент факта, сохраняется рядом с ним и не меняется между попытками.
  • Значение event_id — канонический UUID; на код invalid_event_id есть реакция в логах.
  • Досылка из буфера укладывается в окно 48 часов, иначе метка времени и дедупликация перестают работать как задумано.
  • Повторная отправка идёт тем же типом ключа, что и первая.
  • Шаг CI/CD передаёт стабильный Idempotency-Key на деплой и неизменное ts при повторе; ответ 200 с Idempotency-Replayed считается успехом, а не ошибкой.
  • Повтор обычных мутаций не выполняется автоматически: сначала чтение состояния.
  • 503 не трактуется как «ничего не произошло».