Пользователь отправляет запрос на оплату. Сервер успевает провести операцию, но ответ теряется из-за сетевого сбоя.
Для клиента это выглядит так, будто ничего не произошло. Он повторяет запрос. Потом ещё раз.
В распределённой системе такой сценарий нормален: запрос может успешно выполниться, а ответ – не дойти обратно. Проблема начинается, если каждый retry повторно меняет состояние системы, то есть три запроса превращаются в три списания.
Здесь нужна идемпотентность: запрос можно повторить несколько раз, но бизнес-эффект останется таким же, как после одного выполнения.
Один из распространённых способов это реализовать – ключ идемпотентности. Клиент отправляет вместе с запросом уникальный ключ, а сервер связывает все повторы с одной и той же операцией.
❗️Но просто добавить поле ключ идемпотентности недостаточно.
Ключ должен видеть любой инстанс сервиса. Его регистрация должна быть атомарной, а повторный запрос должен получать статус или результат первой операции.
Есть и более неприятный сценарий: деньги уже списал внешний провайдер, а ваш сервис упал до того, как сохранил результат. Тогда одной локальной транзакции уже недостаточно – нужна идемпотентность на стороне провайдера или reconciliation.
На картинках как раз разобрали весь путь по шагам:
• как возникает проблема с повторными списаниями;
• что меняет ключ идемпотентности;
• где чаще всего ошибаются в реализации;
• и почему внешний платёжный провайдер добавляет ещё один сложный edge case.
Поэтому перед добавлением retry полезно задавать не только вопрос «сколько раз повторяем?», но и другой:
«Что произойдёт, если эта операция выполнится дважды?»
Какие ещё операции, кроме платежей, вы бы обязательно делали идемпотентными?



