Qu'est-ce que l'idempotence ? Méthodes HTTP, clés d'idempotence, exemples

Idempotence : répéter une opération laisse le même état que la faire une fois. Quelles méthodes HTTP en relèvent et comment les clés sécurisent POST.

Qu'est-ce que l'idempotence ?

Une opération est idempotente si l'exécuter plusieurs fois laisse le système dans le même état que l'exécuter une seule fois. En HTTP, cela signifie qu'un client peut rejouer une requête après un timeout ou une connexion coupée sans provoquer un second débit, une seconde commande ou un second e-mail. GET, PUT et DELETE sont idempotentes par définition. POST ne l'est pas.

L'image habituelle est le bouton d'ascenseur : appuyer dix fois ne fait venir qu'un seul ascenseur. Un bouton "payer" sans protection est l'inverse, et dix clics peuvent donner dix débits. Une conception idempotente rend les nouvelles tentatives sûres.

L'idempotence des méthodes HTTP

La RFC 9110 (qui a remplacé la RFC 7231) établit que PUT, DELETE et toutes les méthodes sûres sont idempotentes :

MéthodeIdempotente ?Pourquoi
GET, HEAD, OPTIONS, TRACEOuiMéthodes sûres : elles lisent et ne changent pas l'état
PUTOuiRemplace la ressource par la même représentation à chaque fois
DELETEOuiLe premier appel supprime la ressource, les suivants n'ont plus rien à supprimer
POSTNonCrée en général une nouvelle ressource ou déclenche une action à chaque appel
PATCHNon garantieDépend du correctif : fixer une valeur est idempotent, incrémenter ne l'est pas

Idempotent ne veut pas dire "renvoie la même réponse". Un premier DELETE peut renvoyer 204 et une répétition 404, et pourtant l'état du serveur est identique. La propriété décrit l'effet voulu sur le serveur, pas la réponse. La RFC 5789 note qu'une requête PATCH peut être formulée de façon à être idempotente.

Exemples

  • PUT /users/123 avec {"name": "Alice"} fixe le nom à Alice. Dix appels, même résultat.
  • DELETE /sessions/abc ne laisse aucune session abc, quel que soit le nombre d'appels.
  • POST /orders crée une nouvelle commande à chaque fois : dix appels, dix commandes.
  • PATCH /counters/x avec {"increment": 1} ajoute un à chaque fois. Ce n'est pas idempotent.
  • POST /emails envoie un message par appel. Un effet de bord hors de votre base de données compte aussi.

Pourquoi l'idempotence compte

  • Timeouts et connexions coupées. Le client ne peut pas savoir si le serveur a traité la requête avant la coupure. Avec une opération idempotente, il la rejoue simplement.
  • Nouvelles tentatives automatiques. La RFC 9110 permet aux clients de rejouer automatiquement des requêtes idempotentes après un échec de connexion, et interdit à un proxy de rejouer automatiquement une requête non idempotente. Les SDK et les service meshes suivent des règles proches.
  • Livraison at-least-once. Les files de messages et les webhooks peuvent livrer le même message plus d'une fois : le destinataire doit tolérer les doublons.
  • Les humains. Les utilisateurs double-cliquent sur envoyer et rechargent des pages à moitié traitées.

Clés d'idempotence

Pour les opérations qui ne sont pas idempotentes par nature, comme créer une commande ou débiter une carte, le schéma courant est une clé d'idempotence (idempotency key) : une valeur unique que le client génère pour une opération logique et envoie à chaque tentative de celle-ci.

POST /charges HTTP/1.1
Idempotency-Key: 8e0a4e6b-1b8e-4e3a-b9f7-5e1d3f9c4b8a
Content-Type: application/json

{"amount": 5000, "currency": "usd"}

Le serveur enregistre le résultat sous cette clé. Une répétition avec la même clé renvoie le résultat enregistré au lieu d'exécuter à nouveau l'opération. Stripe est l'exemple le plus connu et documente les détails : l'en-tête Idempotency-Key, des clés jusqu'à 255 caractères, des UUID V4 recommandés et, pour l'API v1, le code de statut et le corps de la première requête sont enregistrés et rejoués, y compris les erreurs 500. Les clés peuvent être supprimées après au moins 24 heures, et réutiliser une clé avec d'autres paramètres renvoie une erreur. Stripe n'a pas besoin de clés pour GET et DELETE, car elles sont déjà idempotentes.

Un brouillon de l'IETF (draft-ietf-httpapi-idempotency-key-header) travaille à normaliser l'en-tête Idempotency-Key. C'est encore un Internet-Draft, pas une RFC. Il décrit une réponse 409 quand une nouvelle tentative arrive alors que la requête d'origine est toujours en cours, et une 422 quand une clé est réutilisée avec un autre payload.

Mettre en œuvre les clés côté serveur

  1. Lire la clé dans la requête et l'enregistrer avec une empreinte du corps de la requête.
  2. Rechercher la clé. S'il existe un résultat terminé et que l'empreinte correspond, le renvoyer sans effets de bord.
  3. Si l'empreinte diffère, rejeter la requête (422 dans le brouillon).
  4. S'il n'y a pas d'entrée, noter la clé comme en cours, exécuter l'opération, enregistrer la réponse et la marquer comme terminée. Une contrainte d'unicité sur la colonne de la clé empêche deux requêtes simultanées de s'exécuter toutes les deux.
  5. Si la clé est en cours, attendre ou renvoyer 409.
  6. Faire expirer les clés après une fenêtre fixe pour que le stockage ne croisse pas sans limite.

Erreurs courantes

  • Croire que PATCH est idempotent. {"set": 5} l'est ; {"increment": 1} ne l'est pas.
  • Une nouvelle clé à chaque tentative. La clé appartient à l'opération, pas à la tentative. Si chaque nouvelle tentative génère une clé neuve, rien n'est dédoublonné.
  • Oublier les effets de bord hors base de données. Un endpoint "idempotent" qui envoie un e-mail à chaque appel n'est pas idempotent.
  • Pas d'expiration. Garder chaque clé pour toujours fait croître le stockage sans limite. Choisissez une fenêtre qui couvre les nouvelles tentatives de vos clients.
  • Conditions de concurrence. Deux requêtes simultanées avec la même clé peuvent s'exécuter toutes les deux si vous ne verrouillez pas ou n'utilisez pas de contrainte d'unicité.
  • Confondre sûr et idempotent. Sûr signifie sans changement d'état (GET). PUT et DELETE sont idempotentes mais pas sûres.

L'idempotence en test de charge et en supervision

  • Les endpoints non idempotents accumulent des données. Un test avec POST crée des enregistrements à chaque itération : prévoyez un nettoyage ou utilisez un environnement de test. Avec un intervalle de 5 minutes, un contrôle de supervision qui fait un POST s'exécute 288 fois par jour ; préférez un contrôle en lecture seule ou un endpoint de test dédié.
  • Générez une clé unique par utilisateur virtuel et par itération. Si tous les utilisateurs virtuels réutilisent une clé, ils reçoivent tous la première réponse enregistrée et votre test n'a chargé que la première requête.
  • Testez le mécanisme lui-même. Envoyez la même requête deux fois, et aussi en parallèle, puis vérifiez que les réponses sont identiques et qu'un seul enregistrement existe. Voir tests d'API.
  • Surveillez les nouvelles tentatives sous charge. Les clients qui réessaient après un timeout ajoutent du trafic justement quand le serveur est lent, ce qui fait notamment atteindre les limites de débit (voir Common Causes of API Rate Limiting, en anglais).

LoadFocus exécute des scripts JMeter et k6 depuis 25 régions cloud (tests de charge), ce qui convient aux tests de nouvelles tentatives concurrentes. Pour donner à chaque utilisateur virtuel ses propres clés, voir data-driven load testing scripts, et pour un premier test d'API, Load Testing RESTful APIs with LoadFocus (tous deux en anglais). Pour des contrôles planifiés en lecture seule, il y a la surveillance d'API. Les verbes CRUD derrière ces règles sont expliqués dans opérations CRUD.

Questions fréquentes sur l'idempotence

GET est-il toujours idempotent ?

Par définition oui : GET est une méthode sûre et ne devrait pas changer l'état. Certaines API détournent GET pour des actions comme le suivi ou la désinscription, ce qui brise la règle et laisse le préchargement et les nouvelles tentatives déclencher l'action.

PATCH est-il idempotent ?

Pas par définition. Cela dépend de l'opération : fixer un champ à une valeur est idempotent, incrémenter un compteur ne l'est pas.

Quelle différence entre idempotent et sûr ?

Sûr signifie que la requête n'est pas censée changer l'état du serveur (GET, HEAD, OPTIONS). Idempotent signifie que la répéter a le même effet voulu que de l'envoyer une fois. PUT et DELETE sont idempotentes mais pas sûres.

Comment rendre une requête POST idempotente ?

Envoyez une clé d'idempotence, une valeur unique pour l'opération que vous réutilisez à chaque nouvelle tentative. Le serveur enregistre le premier résultat sous cette clé et le renvoie pour les répétitions.

Combien de temps une clé d'idempotence doit-elle durer ?

Assez pour couvrir la fenêtre de nouvelles tentatives de vos clients. Stripe supprime les clés de l'API v1 après au moins 24 heures. Choisissez votre propre fenêtre et documentez-la.

L'idempotence s'applique-t-elle à GraphQL et gRPC ?

Oui. Les requêtes GraphQL lisent des données, tandis que les mutations demandent leur propre protection, par exemple une clé dans l'entrée. gRPC permet de marquer une méthode comme idempotente dans sa définition, ce qui indique aux clients quand une nouvelle tentative est sûre.

Quelle est la vitesse de votre site web?

Augmentez sa vitesse et son référencement naturel de manière transparente avec notre Test de Vitesse gratuit.

Test gratuit de vitesse du site Web

Analyser la vitesse de chargement de votre site Web et améliorer ses performances avec notre outil gratuit de vérification de la vitesse de la page.