genmux API теперь доступен всем. Читать документацию →
genmux API

Блог

Ключи идемпотентности и честное списание в API генерации изображений

2 мин чтенияАвтор Unified Image API Team


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

Гарантия

Один кредит списывается максимум за одно доставленное изображение. Конкретно:

  • Отправка атомарно резервирует стоимость задания (условный single-row update — баланс не может уйти в минус, а два конкурентных запроса не потратят одни и те же последние кредиты).
  • Неудачное или отменённое задание автоматически возвращает резерв. Строка в леджере уникальна по паре (задание, тип) — сам возврат тоже exactly-once, даже если его попытаются сделать два воркера.
  • Вызов модели записывается заранее (write-ahead): прежде чем звать провайдера, мы фиксируем попытку; если процесс умирает посреди вызова, сверка разрешает «неизвестное» состояние по данным провайдера, а не наугад. Ни один неизвестный вызов не тарифицируется молча.

Ключи идемпотентности закрывают клиентскую половину

Серверные гарантии бесполезны, если повтор клиента создаёт второе задание. Для этого и нужен обязательный заголовок Idempotency-Key:

  • Тот же ключ + то же тело → возвращается исходное задание (200, а не 202). Нового списания не будет никогда.
  • Тот же ключ + другое тело → 422 idempotency_key_mismatch: молча вернуть старое задание значило бы скрыть баг в вашем коде.
  • Ключи хранятся достаточно долго, чтобы покрыть любой реалистичный интервал повторов.

Практическое правило: генерируйте ключ там, где рождается намерение пользователя (один ключ на «пользователь нажал сгенерировать»), а не на каждую HTTP-попытку. После этого HTTP-слой может повторять сколь угодно агрессивно.

text
намерение → ключ K
  попытка 1 (K) — таймаут сети, ответа вы не видели
  попытка 2 (K) — 200, возвращается задание из попытки 1

Что можно удалить из вашего кода

  • Джобы сверки вашей БД с нашей: благодаря леджеру возвратов равенство sum(списаний) == sum(доставленных изображений) × цена выполняется по построению.
  • Алёрты «не списал ли повтор дважды?» — повтор с тем же ключом не может.
  • Защитные проверки баланса перед каждой отправкой: 402 insufficient_credits безопасно обрабатывать по факту; частичных списаний не бывает.

Что всё же обрабатывать

  • Задания failed: кредиты возвращены, но в UX стоит показать error.code (например, content_policy).
  • 429: бэкофф по Retry-After; за отклонённые запросы резерв вообще не создаётся.
  • Истечение подписанных ссылок (15 мин): повторный GET задания даёт свежие — бесплатно.

Полное поведение описано в справочнике API. А если сравниваете провайдеров — спросите каждого, что происходит с вашими деньгами, когда задание умирает между их очередью и их моделью. Именно этот вопрос отличает метрируемые API, которым можно доверять, от тех, которые приходится ежемесячно сверять.

Об авторе

Автор Unified Image API Team — genmux API.

Подробнее о нас