Как избежать дубликатов виртуальных машин при повторных запросах
Что проверяют
Понимаете ли вы идемпотентность в распределённой системе, а не только умеете поставить мьютекс. Повтор приходит от клиента после таймаута, от ретраев балансировщика или на другую реплику сервиса, и локальная блокировка тут ничего не решает. Следом обычно спрашивают: что делать, если вызов гипервизора завис и неизвестно, создалась машина или нет.
Как отвечать
Основа ответа это ключ идемпотентности и уникальность на уровне хранилища:
- ключ запроса: клиент генерирует UUID и передаёт его в заголовке
Idempotency-Key. Повтор с тем же ключом возвращает ту же машину, а не создаёт новую; - уникальный индекс: таблица операций с
UNIQUE (request_id)и вставка черезINSERT ... ON CONFLICT DO NOTHING. Кто вставил строку, тот и создаёт VM, остальные читают статус. Гонку решает база, а неsync.Mutex; - машина состояний:
pending,creating,ready,failed. Повтор наcreatingполучает202 Acceptedи текущий статус, наreadyготовый результат; - метка на ресурсе: на создаваемую VM вешается
request_idкак label. После таймаута воркер сначала ищет машину по метке и только потом создаёт; - сверка: фоновый reconcile сравнивает записи в базе с реальными машинами и чистит сироты.
Одинаковые одновременные вызовы внутри процесса можно склеить через golang.org/x/sync/singleflight, но это оптимизация, а не гарантия: у соседней реплики свой singleflight. Ключ храните ограниченное время и сверяйте вместе с параметрами: тот же ключ с другим размером диска это ответ 422, а не молчаливый возврат старой машины.
Где спотыкаются
Предлагают map под мьютексом. Он живёт в памяти одного процесса, теряется при рестарте, а сервис почти всегда запущен в нескольких экземплярах.
Делают «сначала проверим, потом создадим» двумя запросами. Между SELECT и INSERT проскакивает второй запрос, нужна атомарная операция или уникальный индекс.
Забывают про неизвестный исход: создание отвалилось по таймауту, запись помечена ошибкой, клиент повторяет, и появляется вторая машина. Без поиска по метке перед повтором идемпотентность ломается ровно там, ради чего её вводили.