сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Интеграции

Идемпотентность и гарантии

Идемпотентность и гарантии

Самый взрослый блок темы. Здесь довольно чётко отделяют тех, кто разбирал реальные инциденты с двойными списаниями, от тех, кто читал главу про надёжность.

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

// Формулировки: «что такое идемпотентность?», «клиент получил таймаут - что дальше?», «как обеспечить доставку ровно один раз?»

Ключ идемпотентности

Операция идемпотентна, если её повтор с теми же данными оставляет систему в том же состоянии, что и однократное выполнение. Чтение идемпотентно само по себе, а вот создание платежа - нет, и его такой делают специально.

Механика простая. Клиент до первой попытки генерирует уникальный идентификатор операции и присылает его в заголовке. Сервер выполняет операцию и запоминает результат вместе с этим ключом. Пришёл повтор с тем же ключом - сервер не создаёт второй платёж, а отдаёт сохранённый ответ.

Определять дубли по совпадению суммы и получателя нельзя, хотя соблазн велик. Два одинаковых перевода одному человеку в один день - совершенно законная ситуация: перевёл тысячу, потом вспомнил и перевёл ещё тысячу. Отличить это от повтора по содержимому невозможно в принципе.

// Ключ генерирует именно клиент, а не сервер, и это принципиально. Только клиент знает, что вот эта попытка - повтор той же самой операции, а не новая. Сервер такого знания не имеет и получить не может.

Гарантии доставки

Гарантия «минимум один раз» означает, что сообщение не потеряется, но может прийти дважды: если подтверждение доставки не получено, отправитель шлёт повторно. Разбираться с дублями обязан получатель.

Гарантию «ровно один раз» на практике строят как «минимум один раз» плюс отсев повторов на приёме. И причина, по которой её не делают напрямую, вполне фундаментальна: отправитель не может отличить потерю самого сообщения от потери подтверждения о нём. Он видит одно и то же - тишину. Значит, выбирать приходится между риском потери и риском дубля, третьего варианта нет.

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

// Отсюда требование к получателю, и его надо записывать в контракт явно: либо обработка идемпотентна сама по себе, либо получатель хранит идентификаторы уже обработанных сообщений и молча отбрасывает повторы.

Повторы, размыкатель, компенсации

Повторять надо с растущей паузой и случайной добавкой к ней, ограничив число попыток. Ретраи каждые сто миллисекунд наращивают нагрузку ровно в тот момент, когда сервису и так плохо, и мешают ему подняться: он не успевает разгрести очередь, потому что клиенты добавляют новые запросы быстрее.

Случайная добавка нужна отдельно. Без неё тысяча клиентов, одновременно получивших ошибку, подождёт одну и ту же секунду и ударит синхронно - и так на каждой итерации.

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

// Согласованность между системами без распределённых транзакций обеспечивают сагой: это цепочка шагов, у каждого из которых есть своя компенсация. Не удалось начислить бонусы - возвращаем списанные деньги. Компенсацию описывает бизнес, и обязательно для каждого шага: шаг без компенсации превращает сагу в наполовину выполненную операцию.

Как отвечать: «Клиент отправил платёж и получил таймаут. Что должно быть в контракте?»

Метод проверки статуса операции по ключу клиента. Таймаут не означает провал: операция могла спокойно выполниться, а потеряться мог только ответ на обратном пути - и клиент этих двух случаев не различает, он видит одну и ту же тишину. Без способа спросить о судьбе операции ему остаётся либо гадать, либо повторять и рисковать двойным списанием. Поэтому в контракте нужны две вещи: ключ идемпотентности, по которому повтор безопасен и вернёт прежний результат, и метод запроса статуса, отвечающий однозначно - выполнена, отклонена или ещё в обработке. Автоматический повтор на стороне сервера тут не помогает вообще: сервер не знает, дошёл ли до клиента его ответ.

Кандидат разбирает саму природу неопределённости после таймаута и закрывает её двумя конкретными элементами контракта. Последняя фраза снимает популярное возражение «а пусть сервер сам повторит».

На чём валятся

  • − Не дают клиенту метода проверки статуса по его ключу и оставляют его гадать после таймаута.
  • − Ищут дубли по сумме и получателю, хотя два одинаковых платежа бывают законными.
  • − Повторяют запросы без паузы и лимита попыток, продлевая чужой сбой.
  • − Ставят распределённую транзакцию между системами вместо саги с компенсациями.
  • − Описывают шаги саги, но забывают компенсацию хотя бы для одного из них.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 13, остальные разбираются в тренажёре.

  1. #ana_int_reliability1 / 5
    Что означает гарантия доставки «хотя бы один раз»?
    A)Сообщение придёт ровно однажды
    B)Сообщение может продублироваться
    C)Сообщение может потеряться
    D)Порядок сообщений сохраняется
    показать ответ и разбор
    +B)Сообщение может продублироваться

    // разбор: At-least-once означает, что потери исключены, но при неподтверждённой доставке сообщение отправят повторно. Дубликаты неизбежны, и разбираться с ними должен получатель — обычно через идемпотентную обработку или хранение уже обработанных идентификаторов.

  2. #ana_int_reliability2 / 5
    Внешний сервис отвечает медленно и с ошибками. Клиент повторяет запрос каждые 100 мс. Чем это опасно?
    A)Повторы исказят данные в ответе
    B)Повторы нарушат формат запроса
    C)Повторы добьют сервис и продлят сбой
    D)Повторы приведут к смене кода ответа
    показать ответ и разбор
    +C)Повторы добьют сервис и продлят сбой

    // разбор: Агрессивные повторы наращивают нагрузку ровно тогда, когда сервису плохо, и мешают ему подняться. Правильная схема — пауза с ростом интервала и случайной добавкой, ограниченное число попыток и размыкатель, который перестаёт долбить заведомо больной сервис и быстро отдаёт отказ.

  3. #ana_int_reliability3 / 5
    Что делает размыкатель цепи (circuit breaker)?
    A)Шифрует канал между сервисами
    B)Распределяет нагрузку между узлами
    C)Ограничивает размер сообщения
    D)Перестаёт слать запросы к сбойному сервису
    показать ответ и разбор
    +D)Перестаёт слать запросы к сбойному сервису

    // разбор: Накопив долю ошибок, размыкатель переходит в открытое состояние и сразу отвечает отказом, не тратя время на заведомо провальные вызовы. Это спасает вызывающую сторону от исчерпания потоков и каскадного падения. Через паузу он пробует пропустить единичный запрос и при успехе возвращается к работе.

  4. #ana_int_reliability4 / 5
    Списание в одной системе прошло, начисление в другой упало. Как это лечат без распределённых транзакций?
    A)Общей транзакцией на две базы
    B)Компенсирующей операцией
    C)Блокировкой обеих систем на время
    D)Ежедневной ручной сверкой
    показать ответ и разбор
    +B)Компенсирующей операцией

    // разбор: Распределённая транзакция через две системы дорога и хрупка, поэтому применяют сагу: последовательность шагов, у каждого из которых есть компенсация. Не удалось начислить — выполняется возврат списанного. Бизнес обязан описать компенсацию для каждого шага, иначе система застревает в промежуточном состоянии.

  5. #ana_int_reliability5 / 5
    Клиент отправил платёж, получил таймаут и не знает, прошёл ли он. Что должен предусмотреть контракт?
    A)Автоматический повтор на стороне сервера
    B)Увеличенный таймаут для платёжных операций
    C)Метод проверки статуса по ключу операции
    D)Уведомление на почту о результате
    показать ответ и разбор
    +C)Метод проверки статуса по ключу операции

    // разбор: Таймаут не означает провал: операция могла выполниться, а потеряться мог ответ. Клиенту нужен способ спросить о судьбе операции по своему ключу и получить однозначный ответ — выполнена, отклонена или в обработке. Без такого метода остаётся гадать либо рисковать повторным списанием.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.