> Как обеспечить идемпотентность запросов на создание виртуальных машин (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Альфа-банк
Стек: Go
> Пример ответа
Короткий ответ
Идемпотентность создания VM обеспечивается через клиентский идемпотентный ключ (Idempotency-Key), который сервер сохраняет вместе с результатом запроса. При повторном запросе с тем же ключом возвращается сохранённый ответ, а не создаётся новая VM. Ключ должен быть уникальным для каждой логической операции, генерироваться клиентом и передаваться в заголовке. Сервер хранит пару ключ-ответ в распределённом хранилище с TTL и блокировкой на время выполнения.
Подробное объяснение
Идемпотентность - это свойство операции, при котором многократное выполнение с одинаковыми параметрами даёт тот же результат, что и однократное. Для создания VM это критично: повторный POST без идемпотентности создаст лишние машины.
Основные подходы:
-
Идемпотентный ключ в заголовке - клиент генерирует UUID для каждой логической операции создания. Сервер проверяет: если ключ уже есть в хранилище - возвращает сохранённый ответ, иначе выполняет создание и сохраняет результат.
-
Естественная идемпотентность через уникальные поля - например, если у VM есть уникальное имя или идентификатор в рамках проекта, повторный запрос с тем же именем вернёт ошибку конфликта или существующую VM. Но это не покрывает все сценарии (например, разные имена для одной логической операции).
-
Оптимистичная блокировка - при создании VM используется версия ресурса или conditional request (If-Match). Если версия изменилась - запрос отклоняется.
-
Компенсирующие операции - если создание не идемпотентно, можно отслеживать состояние и при повторе откатывать предыдущее создание.
Для Go-сервиса с высоким RPS ключевые моменты:
- Хранилище ключей должно быть распределённым (Redis, etcd, DynamoDB) с TTL, обычно 24 часа.
- Нужна атомарная операция "вставить если отсутствует" (SETNX в Redis) для защиты от гонок.
- Ответ должен содержать статус операции (создана или уже существовала) и идентификатор VM.
- Если создание VM асинхронное (через очередь), ключ хранится до завершения операции, а клиент получает ссылку на статус.
На практике
В реальном API (например, облачный провайдер) схема такая:
- Клиент генерирует UUID и передаёт его в заголовке
Idempotency-Key. - Сервер проверяет ключ в Redis: если есть - возвращает сохранённый ответ (200 или 201 с телом).
- Если нет - создаёт запись в Redis со статусом
processing, затем запускает создание VM. - После завершения обновляет запись: статус
done, тело ответа, идентификатор VM. - Если клиент повторит запрос - получит тот же ответ.
- Если создание упало - запись удаляется или помечается
failed, клиент может повторить с новым ключом.
Важно: ключ должен быть привязан к конкретному телу запроса. Если клиент шлёт тот же ключ, но другое тело - это ошибка клиента, сервер должен вернуть 422.
Пример кода
GOtype CreateVMRequest struct {Name string `json:"name"`CPU int `json:"cpu"`Memory int `json:"memory"`}type VMResponse struct {ID string `json:"id"`Status string `json:"status"`}type IdempotencyStore interface {Get(key string) (*VMResponse, error)SetIfAbsent(key string, resp *VMResponse) (bool, error)Update(key string, resp *VMResponse) error}func CreateVMHandler(store IdempotencyStore, vmService VMService) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {idemKey := r.Header.Get("Idempotency-Key")if idemKey == "" {http.Error(w, "missing Idempotency-Key", http.StatusBadRequest)return}// проверяем, есть ли уже результатif resp, err := store.Get(idemKey); err == nil && resp != nil {writeJSON(w, http.StatusOK, resp)return}var req CreateVMRequestif err := json.NewDecoder(r.Body).Decode(&req); err != nil {http.Error(w, "invalid body", http.StatusBadRequest)return}// атомарно резервируем ключplaceholder := &VMResponse{Status: "processing"}inserted, err := store.SetIfAbsent(idemKey, placeholder)if err != nil {http.Error(w, "internal error", http.StatusInternalServerError)return}if !inserted {// другой запрос уже выполняетсяresp, _ := store.Get(idemKey)writeJSON(w, http.StatusAccepted, resp)return}// создаём VMvm, err := vmService.Create(r.Context(), req)if err != nil {store.Update(idemKey, &VMResponse{Status: "failed"})http.Error(w, err.Error(), http.StatusInternalServerError)return}resp := &VMResponse{ID: vm.ID, Status: "created"}store.Update(idemKey, resp)writeJSON(w, http.StatusCreated, resp)}}
Как отвечать на собеседовании
Начни с определения идемпотентности и почему это важно для создания ресурсов. Затем опиши основной механизм - идемпотентный ключ. Упомяни, что ключ хранится в распределённом хранилище с TTL, и что нужна атомарная операция вставки. Приведи пример из реального API (Stripe, AWS). Обязательно скажи про обработку гонок и про то, что ключ должен быть привязан к телу запроса. Если спросят про асинхронность - объясни, что ключ хранится до завершения операции, а клиент получает статус.
Что проверяет интервьюер
- Понимание проблемы идемпотентности и её важности для POST-запросов.
- Знание механизма идемпотентного ключа и его реализации.
- Умение проектировать распределённые системы: хранилище, TTL, атомарность.
- Понимание гонок и способов их предотвращения (SETNX, conditional writes).
- Умение объяснить trade-off между синхронным и асинхронным созданием VM.
- Знание HTTP-статусов и заголовков.
Типичные ошибки
- Предложение использовать только уникальное имя VM - это не покрывает все сценарии.
- Игнорирование гонок: два параллельных запроса с одним ключом создадут две VM.
- Хранение ключа в памяти одного инстанса - при рестарте или нескольких репликах всё сломается.
- Не привязывать ключ к телу запроса - клиент может случайно переиспользовать ключ с другими параметрами.
- Не обрабатывать случай, когда операция упала после создания VM, но до сохранения ответа - нужна компенсация или повторная проверка.
- Возвращать 500 при повторном запросе с тем же ключом, но другим телом - должен быть 422.
> Похожие задачи по Go
Что такое consumer group в брокерах сообщений
Насколько знаком с Kubernetes на уровне пользователя
Что делать первым шагом внутри сервиса создания виртуальной машины
Как определяется, куда положить переменную в памяти, и кто это определяет
> Похожие задачи по backend
Что такое consumer group в брокерах сообщений
Насколько знаком с Kubernetes на уровне пользователя
Что делать первым шагом внутри сервиса создания виртуальной машины
Как определяется, куда положить переменную в памяти, и кто это определяет
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью