Как обеспечить идемпотентность API
Что проверяют
Понимаете ли вы, зачем нужна идемпотентность: сеть рвётся, клиент не получил ответ и повторяет запрос, а деньги не должны списаться дважды. Интервьюер ждёт механизм на сервере, а не только перечень HTTP-методов. Следом обычно спрашивают, что будет, если два одинаковых запроса придут одновременно.
Как отвечать
Начните с определения по RFC 9110: GET, HEAD, PUT, DELETE идемпотентны, POST и PATCH нет. Идемпотентность описывает состояние сервера, а не код ответа: повторный DELETE может вернуть 404, и это нормально.
Для неидемпотентных операций основной приём это ключ идемпотентности:
- заголовок
Idempotency-Key: клиент генерирует UUID на операцию и повторяет его при каждом ретрае. Стандарт пока в статусе черновика IETF; - таблица ключей: пара
(client_id, key)под уникальным индексом, хэш тела запроса, статус и сохранённый ответ, срок хранения, например сутки; - атомарный захват:
INSERT ... ON CONFLICT DO NOTHINGв PostgreSQL черезpgx. Строка вставилась, значит запрос первый. Не вставилась, смотрим статус: операция завершена, возвращаем сохранённый ответ; ещё выполняется, отдаём409 Conflict; тело другое при том же ключе, отвечаем422; - одна транзакция: запись ключа и бизнес-изменение коммитятся вместе, иначе падение между ними оставит ключ без результата;
- естественная идемпотентность: уникальное ограничение на номер заказа, upsert,
UPDATE ... SET status = 'paid' WHERE status = 'new'вместоbalance = balance + 100.
В Go это оформляют middleware вокруг http.Handler, который сохраняет ответ через обёртку над http.ResponseWriter. Та же логика нужна консьюмерам Kafka или NATS: доставка «хотя бы раз» означает дубли, их отсекают по ID сообщения.
Где спотыкаются
Делают «проверить, потом вставить» двумя запросами. Два параллельных ретрая оба не находят ключ и оба выполняют операцию, спасает только уникальный индекс или блокировка.
Хранят ключи в map внутри процесса. После рестарта или на второй реплике за балансировщиком защита исчезает.
Генерируют ключ на сервере или новый на каждый ретрай. Тогда повтор выглядит как новая операция, и смысл механизма теряется.