Что такое идемпотентность и как её реализовать в бэкенд-системе?

Ответ

Идемпотентность — это свойство операции, при котором повторный её вызов с теми же входными данными не изменяет состояние системы после первого успешного вызова. Проще говоря, f(f(x)) = f(x). Это критически важно для построения надёжных систем, особенно в условиях нестабильной сети, где клиент может повторно отправить запрос, не получив ответа.

Ключевые подходы к реализации:

  1. Идемпотентный ключ (Idempotency Key)

    Это самый распространённый и надёжный способ. Клиент генерирует уникальный идентификатор для каждой операции (например, UUID) и передаёт его в заголовке запроса (например, Idempotency-Key).

    Алгоритм на стороне сервера:

    1. При получении запроса извлечь идемпотентный ключ.
    2. Проверить, обрабатывался ли запрос с таким ключом ранее.
    3. Если ключ новый: начать обработку. Перед изменением состояния системы сохранить ключ и результат операции в хранилище (например, Redis или БД). После этого вернуть результат клиенту.
    4. Если ключ уже существует: не выполнять операцию повторно, а сразу вернуть сохранённый результат из хранилища.
  2. Хранилище для ключей

    • Redis: Идеально подходит благодаря атомарной операции SETNX (SET if Not eXists). Она позволяет установить значение, только если ключа ещё не существует.

      // Псевдокод с использованием Redis
      wasSet, err := redisClient.SetNX(ctx, idempotencyKey, "processing", 24*time.Hour).Result()
      if err != nil { /* обработка ошибки */ }
      
      if !wasSet {
          // Ключ уже существует, значит, запрос-дубликат
          // Нужно вернуть сохраненный ранее результат
          return getSavedResponse(idempotencyKey)
      }
      
      // Новая операция: выполняем логику, сохраняем результат и возвращаем его
      result := processRequest(request)
      saveResponse(idempotencyKey, result)
      return result
    • База данных (SQL): Создаётся таблица для идемпотентных ключей с UNIQUE ограничением на столбец ключа. Попытка вставить дубликат вызовет ошибку, которую можно обработать.
  3. Проверка состояния объекта

    Иногда операцию можно сделать идемпотентной по своей природе. Например, операция DELETE /users/123 идемпотентна: первый вызов удалит пользователя, а последующие просто вернут 404 Not Found, не меняя состояние системы. Операция SET user.status = 'active' также идемпотентна.