5202 sujets

Le Bar du forum

Modérateur
Bonjour à tous,

Je souhaitais partager avec la communauté un problème récurrent que je vois passer aussi bien chez les débutants que chez les développeurs confirmés. Il concerne la gestion des ressources partagées, typiquement le stock dans une boutique en ligne, mais aussi les places de spectacle, les réservations, ou tout système où une quantité limitée est disponible.

Le code qui semble parfaitement logique
Prenons un exemple simplifié, dans un pseudo-code compréhensible par tous :


fonction passerCommande(idProduit, quantite) {
    // 1. On récupère le produit
    produit = recupererProduit(idProduit)
    
    // 2. On vérifie le stock
    si (produit.stock < quantite) {
        retourner ERREUR "Stock insuffisant"
    }
    
    // 3. On crée la commande
    commande = creerCommande(idProduit, quantite)
    
    // 4. On met à jour le stock
    produit.stock = produit.stock - quantite
    sauvegarder(produit)
    
    retourner commande
}


Ce code est clair, logique, et fonctionne parfaitement quand on le teste seul.

Le problème : la race condition

Imaginez maintenant un produit très demandé. Il ne reste que 1 seul exemplaire en stock.

- Utilisateur Alice (10h00m00.001s) : La fonction est appelée. Le stock lu est 1. La vérification stock < quantité est fausse (1 < 1), donc on passe à la suite.
- Utilisateur Bob (10h00m00.050s) : La fonction est appelée 50 millisecondes plus tard, alors qu'Alice n'a pas encore fini de sauvegarder. Bob lit lui aussi stock = 1. Sa vérification passe également.

Résultat final :
- Alice obtient sa commande. Le stock passe à 0.
- Bob obtient aussi sa commande. Le stock passe à -1.

Vous venez de vendre deux fois le même produit unique.

Pourquoi ce bug est-il si courant ?


Parce qu'il est silencieux. En développement local, avec un seul utilisateur, il est impossible à reproduire. Il ne se manifeste qu'en production, sous charge, souvent au pire moment (lancement d'un produit, soldes, Black Friday).

Techniquement, c'est un problème de concurrence (race condition) et de pattern « Check-then-Act » (vérifier puis agir). Entre la vérification et l'action, l'état du système a changé.

La solution : atomicité et verrouillage

Pour résoudre ce problème de manière universelle, quel que soit le langage ou le framework utilisé, deux mécanismes doivent être combinés :

1. La transaction atomique

Une transaction garantit que toutes les opérations d'un bloc réussissent ensemble ou échouent ensemble. Si une erreur survient à n'importe quelle étape, tout est annulé (ROLLBACK).


DEBUT TRANSACTION
    // Toutes les opérations ici
FIN TRANSACTION (COMMIT si tout va bien, ROLLBACK sinon)


2. Le verrouillage pessimiste

Avant même de vérifier le stock, il faut verrouiller la ligne concernée dans la base de données. Cela empêche toute autre connexion de lire ou modifier cette ligne tant que la transaction n'est pas terminée.

En SQL, cela se traduit par SELECT ... FOR UPDATE.

DEBUT TRANSACTION
    // Verrouiller la ligne du produit
    produit = SELECT * FROM produit WHERE id = idProduit FOR UPDATE
    
    // Maintenant, personne d'autre ne peut toucher à cette ligne
    SI produit.stock < quantite ALORS
        ROLLBACK
        RETOURNER ERREUR
    FIN SI
    
    // Création de la commande
    INSERT INTO commande ...
    
    // Mise à jour atomique du stock (calcul fait par le SGBD)
    UPDATE produit SET stock = stock - quantite WHERE id = idProduit
    
COMMIT


3. La mise à jour atomique

Plutôt que de faire stock = stock - quantite dans le code applicatif, il est préférable de laisser le SGBD faire le calcul :


UPDATE produit SET stock = stock - 1 WHERE id = 42;


Cela évite de relire une valeur potentiellement déjà modifiée.

Ce qu'il faut retenir

Dès que votre application manipule une ressource partagée et limitée (stock, places, crédits, solde), vous devez :

1. Identifier le bloc critique qui lit puis écrit.
2. L'encapsuler dans une transaction.
3. Utiliser un verrou pessimiste sur la ressource convoitée.
4. Privilégier les mises à jour atomiques en base de données.

Ce n'est pas une question de langage ou de framework. Que vous codiez en PHP, Python, Java, Ruby ou JavaScript, le problème et la solution sont les mêmes. Seule la syntaxe change.

J'ai rédigé un article détaillé sur mon blog qui applique ces principes à un cas concret avec Django et cas Django REST Framework, mais les concepts restent universels.

Note : J'ai hésité à publier ce sujet ici, sachant qu'Alsacréations est historiquement plus orienté frontend. Mais les problématiques de concurrence et d'intégrité des données touchent aussi les développeurs full-stack, et je pense que le forum a toute sa place pour ce type de partage backend. N'hésitez pas à me dire si ce format « article » est le bienvenu ou s'il aurait été plus pertinent ailleurs.
Modifié par Niuxe (19 Apr 2026 - 23:46)