Идемпотентность и повторы
Три разных механизма безопасного повтора в 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 |
нет | уменьшите тело запроса |
Как считать задержку
- Если пришёл
Retry-After— ждите ровно столько. Он приходит в секундах. - Иначе — экспоненциальная задержка: первая пауза около секунды, дальше удвоение до потолка в 30 секунд. Так устроен браузерный SDK, и это разумное значение по умолчанию для серверного клиента.
- Добавляйте случайное отклонение (jitter): без него все ваши процессы постучатся в одну и ту же секунду.
- Ограничивайте число попыток и не повторяйте параллельно одним ключом приёма — бюджет частоты у него общий.
- После исчерпания попыток складывайте события в свой буфер и логируйте факт. Подробнее — в ограничениях частоты и квотах.
Что проверить в своём клиенте
event_idгенерируется в момент факта, сохраняется рядом с ним и не меняется между попытками.- Значение
event_id— канонический UUID; на кодinvalid_event_idесть реакция в логах. - Досылка из буфера укладывается в окно 48 часов, иначе метка времени и дедупликация перестают работать как задумано.
- Повторная отправка идёт тем же типом ключа, что и первая.
- Шаг CI/CD передаёт стабильный
Idempotency-Keyна деплой и неизменноеtsпри повторе; ответ200сIdempotency-Replayedсчитается успехом, а не ошибкой. - Повтор обычных мутаций не выполняется автоматически: сначала чтение состояния.
503не трактуется как «ничего не произошло».