Как обеспечить идемпотентность запросов на создание виртуальных машин
Что проверяют
Понимаете ли вы, что сеть ненадёжна: клиент не дождался ответа, повторил запрос, а виртуальная машина уже создаётся. Интервьюер хочет услышать, как вы гарантируете одну машину на одно намерение клиента при нескольких репликах сервиса. Следом спрашивают, что будет, если сервис упадёт посреди создания.
Как отвечать
- ключ идемпотентности: клиент генерирует UUID на каждое намерение и передаёт его в заголовке
Idempotency-Key, при повторе отправляет тот же ключ. Альтернатива: клиент сам задаёт уникальное имя машины в проекте, и оно служит естественным ключом; - атомарный захват ключа: таблица
idempotency_keysс уникальным индексом по ключу и владельцу, чтобы ключи разных клиентов не пересекались. Запись черезINSERT ... ON CONFLICT DO NOTHING: кто вставил строку, тот и создаёт машину. Проверка «сначалаSELECT, потомINSERT» даёт гонку; - хэш запроса: вместе с ключом хранится хэш тела. Тот же ключ с другим телом это ошибка клиента, по черновику стандарта IETF ответ
422, а на повтор, пока первый ещё выполняется,409; - сохранённый ответ: после завершения в строку пишутся статус и результат, и повтор получает тот же ответ, что и первый вызов;
- асинхронность: создание машины занимает минуты, поэтому API отвечает
202с id операции, а работу делает воркер.
Отдельно про сбой посреди работы. Воркер тоже должен быть идемпотентным: машину в гипервизоре помечают id запроса (тегом или меткой) и перед созданием проверяют, нет ли уже такой. Иначе рестарт между «машина создана» и «статус записан» даст вторую машину. У ключей есть срок жизни, обычно сутки.
Где спотыкаются
Хранят ключи в map под sync.Mutex. Это работает на одном экземпляре до первого рестарта, а при двух репликах повтор попадает на соседнюю и создаёт дубль.
Записывают ключ только после успешного создания. Два параллельных повтора оба не находят записи и оба создают машину.
Не сравнивают тело запроса. Клиент по ошибке переиспользовал ключ для машины другой конфигурации и молча получил старую.