Une boutique vend le dernier exemplaire d'une référence à deux clients en même temps. L'incident paraît improbable et il se produit systématiquement dès qu'une opération commerciale attire du monde sur une référence limitée. Sa cause n'est jamais un bogue de logique métier mais une hypothèse implicite : celle que la vérification du stock et sa décrémentation forment une opération indivisible. Elles ne le sont pas, et la fenêtre qui les sépare suffit à laisser passer deux commandes. Les mécanismes qui ferment cette fenêtre sont bien connus, peu coûteux, et rarement mis en place avant le premier incident.

Pourquoi la vérification classique échoue

Comprendre précisément le mécanisme évite de chercher la cause du côté du code métier, où elle ne se trouve pas. Le problème est de nature concurrentielle et il ne se reproduit pas en développement, où les requêtes arrivent les unes après les autres. Les notions de base sur lesquelles reposent ces mécanismes sont rappelées dans notre article sur la base de données, sa définition et son fonctionnement.

La séquence fautive

Le code lit la quantité disponible, constate qu'elle est suffisante, crée la commande puis décrémente le stock. Entre la lecture et l'écriture s'écoulent quelques millisecondes, pendant lesquelles une seconde requête peut effectuer exactement la même lecture et parvenir à la même conclusion. Les deux commandes sont alors créées et le stock devient négatif. Cette séquence est la façon la plus naturelle d'écrire le traitement, ce qui explique sa prévalence. On la retrouve dans la plupart des développements sur mesure et dans plusieurs extensions du marché.

Le rôle de la charge

La probabilité d'occurrence croît avec le nombre de requêtes simultanées sur la même référence. Sur une boutique recevant dix commandes par jour, l'incident ne se produit jamais. Sur la même boutique pendant une opération commerciale attirant deux cents personnes sur un produit limité, il se produit plusieurs fois par heure. La conception doit donc anticiper la situation exceptionnelle plutôt que la situation ordinaire. C'est précisément lors des opérations importantes, où chaque commande compte, que le défaut se manifeste.

Les doubles soumissions

Un acheteur cliquant deux fois sur le bouton de validation produit deux requêtes quasi simultanées, ce qui reproduit exactement la condition d'échec. Ce cas est bien plus fréquent que la concurrence entre deux acheteurs distincts, et il est trivial à provoquer. Il justifie à lui seul de traiter le sujet, y compris sur une boutique à faible trafic. La désactivation du bouton côté navigateur ne constitue pas une protection, un rechargement suffisant à la contourner. Elle reste utile pour le confort et elle ne dispense d'aucune protection côté serveur.

Le stock affiché et le stock réel

La quantité affichée sur la fiche produit provient souvent d'un cache, avec quelques minutes de retard. Cet écart n'est pas la cause de la survente et il l'aggrave, en laissant des acheteurs ajouter au panier un article déjà épuisé. Distinguer clairement le stock d'affichage, tolérant, et le stock de décision, exact, permet d'accepter un cache sur le premier sans risque. Cette distinction est structurante pour toute l'architecture de la boutique. Elle autorise une mise en cache agressive des pages de catalogue tout en garantissant l'exactitude au moment de la validation.

Le panier n'est pas une réservation

Un article ajouté au panier reste disponible pour tout le monde, ce qui est le comportement par défaut de la quasi totalité des solutions. Un acheteur peut donc voir son panier refusé au moment du paiement, situation frustrante et parfaitement normale. Certains contextes justifient une réservation temporaire, dont les conséquences sont détaillées plus loin. Ce choix relève du commerce autant que de la technique. Il doit être tranché avec la personne responsable des ventes, la réponse dépendant du type de produits et de la fréquence des ruptures.

Les stocks partagés entre canaux

Une boutique vendant également en magasin ou sur une place de marché partage son stock entre plusieurs systèmes, avec une synchronisation nécessairement décalée. La survente devient alors probable même sans concurrence interne. Ce cas relève de l'architecture de synchronisation et il ne se règle pas par un verrou local. Il mérite d'être identifié comme un problème distinct plutôt que confondu avec le précédent. Le traiter suppose de désigner un système comme référence et d'accepter un décalage sur les autres.

Mécanismes de verrouillage empêchant la survente d’une référence

Les mécanismes qui fonctionnent

Trois techniques ferment réellement la fenêtre, avec des propriétés et des coûts différents. Le choix dépend surtout du volume et de la structure existante.

La décrémentation conditionnelle

La solution la plus simple consiste à décrémenter par une seule instruction portant sa propre condition : retirer une unité si et seulement si la quantité restante est suffisante. Le nombre de lignes affectées indique si l'opération a réussi. Une seule des deux requêtes concurrentes obtiendra une ligne modifiée, l'autre obtiendra zéro et devra refuser la commande. Ce mécanisme est atomique par construction, il ne demande aucun verrou explicite et il tient en une requête. C'est la réponse à privilégier dans la grande majorité des cas. Elle ne demande aucune modification de structure et elle s'ajoute sur un code existant en remplaçant une poignée de lignes.

Le verrou pessimiste

Une transaction peut verrouiller la ligne de stock en lecture, empêchant toute autre transaction de la lire jusqu'à la validation. Les requêtes concurrentes attendent leur tour et voient la valeur à jour. Cette approche est plus générale que la précédente et elle permet de mener plusieurs opérations dans la même transaction. Son coût tient à l'attente qu'elle impose, qui devient sensible lorsque de nombreuses requêtes portent sur la même référence. Une transaction longue tenant le verrou pendant plusieurs secondes bloque toutes les autres, ce qui peut faire basculer un pic en incident généralisé.

Le verrou optimiste

Une troisième approche ajoute un numéro de version sur la ligne et n'accepte la mise à jour que si la version n'a pas changé depuis la lecture. En cas de conflit, l'opération échoue et le code réessaie. Cette technique évite toute attente et elle demande de gérer les réessais proprement. Elle convient bien lorsque les conflits sont rares et coûteux à prévenir autrement. Le nombre de réessais doit être borné, faute de quoi une contention forte produit une boucle sans fin.

Le rôle de la clé primaire

Toutes ces techniques supposent une identification sans ambiguïté de la ligne concernée, ce qui rend la structure de la table déterminante. Une table de stock indexée sur la référence et l'emplacement se verrouille finement ; une table sans index approprié verrouille bien plus que nécessaire. Un index inadapté transforme un verrou de ligne en verrou de table, ce qui suffit à immobiliser la boutique entière. Ce point est développé dans notre article sur la clé primaire et son fonctionnement dans une base de données.

Ce qui ne fonctionne pas

Un verrou posé au niveau applicatif, dans un fichier ou en mémoire, ne protège que si un seul processus traite les commandes. Sur une infrastructure comportant plusieurs serveurs, ces verrous sont indépendants et ne protègent rien. De même, une vérification supplémentaire ajoutée juste avant l'écriture ne fait que réduire la fenêtre sans la fermer. Ces demi mesures donnent un faux sentiment de résolution et l'incident revient plus rarement, ce qui le rend plus difficile à diagnostiquer. Un problème qui survient une fois par trimestre est bien plus coûteux à traiter qu'un problème quotidien.

Le cas des paniers multi lignes

Une commande comportant cinq références demande de décrémenter cinq stocks, opération qui doit être atomique dans son ensemble. Si le troisième échoue, les deux premiers doivent être restitués. Une transaction englobant les cinq décrémentations, annulée en cas d'échec, règle ce point. L'ordre de traitement des lignes doit par ailleurs être déterministe, deux commandes traitant les mêmes références dans un ordre différent pouvant se bloquer mutuellement. Trier les lignes par identifiant de produit avant traitement suffit à écarter ce risque.

Mécanisme Attente Complexité Usage recommandé
Décrémentation conditionnelle Aucune Très faible Cas général
Verrou pessimiste Oui Moyenne Opérations composées
Verrou optimiste Aucune, réessais Moyenne Conflits rares
Verrou applicatif Oui Faible Aucun, ne protège pas
Réservation temporaire Aucune Élevée Ventes à quantité limitée
File d'attente de commandes Oui Élevée Pics très importants

Les réservations temporaires

Sur certaines ventes, laisser le stock disponible jusqu'au paiement produit trop de déceptions. Une réservation à l'ajout au panier répond à ce besoin, au prix d'une complexité supplémentaire qu'il faut mesurer.

Le principe

L'ajout au panier décrémente un stock réservé plutôt que le stock disponible, avec une date d'expiration. Le paiement transforme la réservation en vente, l'expiration la restitue. Ce mécanisme demande un troisième état en plus de disponible et vendu, et une tâche de libération des réservations expirées. Sa mise en place représente une journée de travail sur une boutique existante. Plusieurs extensions la proposent, avec des qualités très variables sur la gestion des expirations.

La durée de réservation

Une durée trop courte fait perdre des paniers en cours de finalisation, une durée trop longue immobilise du stock pour des paniers abandonnés. Quinze à trente minutes constituent un compromis courant sur les ventes ordinaires. Sur une vente à forte demande, une durée plus courte se justifie et elle doit être annoncée clairement à l'acheteur. Un compte à rebours visible transforme cette contrainte en information utile. Il doit refléter la durée réelle, un décompte purement décoratif relevant de la manipulation.

La libération des réservations

Une tâche planifiée doit restituer les réservations expirées, faute de quoi le stock disponible diminue sans raison. Cette tâche doit tourner fréquemment, toutes les minutes sur une vente tendue, et elle doit être surveillée. Un dysfonctionnement se manifeste par un stock qui paraît épuisé alors que rien n'a été vendu, situation difficile à diagnostiquer sans connaître le mécanisme. C'est le point de fragilité principal de cette approche. Une alerte lorsque le nombre de réservations actives dépasse un seuil attrape ce dysfonctionnement avant qu'il ne bloque les ventes.

Les paniers abandonnés

Un acheteur revenant sur son panier après expiration doit être informé que les articles ne sont plus réservés, plutôt que de découvrir le refus au paiement. Un message au retour, avec revérification du stock, évite cette mauvaise surprise. Ce comportement demande de distinguer clairement le panier et la réservation, qui ne sont plus la même chose. Un panier peut ainsi contenir des articles réservés et des articles qui ne le sont plus. Cette distinction rejoint les questions traitées dans notre article sur le panier d'un site e-commerce et son fonctionnement.

Quand cela ne se justifie pas

Sur un catalogue aux stocks confortables, la réservation ajoute une complexité sans bénéfice : personne ne se voit jamais refuser sa commande. Elle se justifie sur les références à faible stock, les ventes événementielles et les produits uniques. Réserver l'ensemble du catalogue par principe est un mauvais calcul. Une activation ciblée par famille de produits constitue le bon compromis. Il se règle par un simple champ sur la fiche produit, activé sur les seules références concernées.

Le cas des produits uniques

Un article vendu à l'exemplaire, brocante ou création artisanale, impose une réservation dès l'ajout au panier, sans quoi la moitié des paniers seront refusés. Ce cas justifie pleinement le mécanisme et il en simplifie même l'écriture, la quantité étant toujours de un. Le retrait immédiat de l'article du catalogue, plutôt qu'une gestion de quantité, constitue souvent la solution la plus simple. Elle demande de prévoir ce qu'il advient de la page après le retrait, une adresse disparaissant brutalement posant des questions de référencement.

Origine des surventes constatées sur des boutiques en activité
Vérification puis écriture non atomique
41 %
Double soumission du formulaire
24 %
Synchronisation entre canaux de vente
18 %
Réservation expirée non libérée
11 %
Erreur de saisie du stock initial
6 %

Répartition des causes identifiées lors d'analyses menées sur des boutiques ayant signalé des ventes en excès.

Gérer ce qui passe malgré tout

Aucun dispositif n'est parfait, notamment lorsque plusieurs canaux de vente coexistent. La procédure de traitement des surventes fait partie du dispositif.

Détecter le stock négatif

Une alerte déclenchée dès qu'une quantité passe sous zéro signale immédiatement le problème. Cette surveillance est triviale à mettre en place et elle est presque toujours absente. Elle permet de traiter la commande concernée avant que le client ne s'aperçoive de rien, ce qui change complètement la perception de l'incident et son coût réel. Le délai de réaction est ici le facteur décisif. Une alerte envoyée vers la messagerie de l'équipe, plutôt que consignée dans un journal, garantit qu'elle sera vue.

Prévenir vite et bien

Un client informé le jour même d'un problème d'approvisionnement, avec une proposition, réagit très différemment d'un client qui découvre l'annulation une semaine plus tard. Le message doit être court, honnête et proposer une option : attendre, remplacer, annuler avec remboursement immédiat. Ce traitement rapide transforme régulièrement un incident en démonstration de sérieux, plusieurs clients revenant précisément à cause de la façon dont le problème a été traité. Il suppose une procédure écrite plutôt qu'une improvisation. Un modèle de message préparé à l'avance permet de répondre en quelques minutes plutôt qu'en une demi journée.

Le choix des priorités

Lorsque deux commandes portent sur un exemplaire unique, il faut décider laquelle est honorée. La règle la plus défendable est l'antériorité de la commande, à la seconde près. Elle doit être écrite et connue du service client, faute de quoi les décisions varient selon l'interlocuteur. Cette cohérence compte davantage que le choix lui même. Un client informé d'une règle appliquée à tous accepte nettement mieux la décision qu'un client soupçonnant l'arbitraire.

Mesurer la fréquence

Le nombre de surventes par mois indique si le dispositif tient ou s'il faut le renforcer. Un incident isolé sur une opération exceptionnelle est acceptable ; une récurrence signale un mécanisme défaillant. Ce comptage se fait par une requête sur les commandes annulées pour ce motif, ce qui suppose que ce motif soit effectivement enregistré plutôt que noyé dans une catégorie générique. Il oriente directement les investissements techniques. Le rapprocher du montant des commandes concernées transforme un problème technique en argument budgétaire.

Tester la concurrence

Un test lançant plusieurs requêtes simultanées sur la même référence vérifie que le mécanisme tient. Un outil de test de charge simple suffit et le test prend cinq minutes à préparer comme à exécuter. C'est le seul contrôle qui détecte réellement ce type de défaut, aucun test séquentiel ne le révélant. Il doit être rejoué après toute modification du traitement des commandes, y compris après une mise à jour d'extension touchant au tunnel.

Documenter le mécanisme retenu

Une note indiquant quelle technique est employée, où elle est implantée et comment elle se comporte en cas de conflit évite qu'un intervenant ultérieur ne la remplace par une version naïve. Ce cas se produit régulièrement lors des reprises de code, la décrémentation conditionnelle ressemblant à une simplification abusive pour qui ne connaît pas son rôle. Un commentaire au bon endroit suffit parfois. Cette précaution coûte deux minutes et elle protège un travail dont l'effet est invisible tant que tout va bien. C'est le propre des protections contre la concurrence : elles ne se remarquent qu'au moment où elles disparaissent. Il vaut donc mieux consigner le test qui a permis de la valider, avec la commande exacte utilisée pour simuler les accès simultanés, afin de pouvoir le rejouer après chaque évolution du tunnel.