Correction Phase 2
Phase 2 : Cohérence et intégrité des données
Énigme 4
Objectif 1 : Gestion des états incohérents [Code réponse 1
]
Le but de cet exercice est que la requête de changement de quantité ne marche pas. Si la requête fonctionne c’est que la version de votre base de données n’est pas la bonne. Le mieux est de suivre le protocole pour faire un resetdb
. Une alternative est de changer manuellement la quantité :
UPDATE merchant_products mp
SET available_quantity = 1
FROM transaction_details td
WHERE mp.merchant_id = td.merchant_id
AND mp.product_id = td.product_id
AND td.transaction_id = 733;
Lors de l’exécution des requêtes de mise à jour, nous obtenons l’erreur suivante :
RuntimeError: (psycopg2.errors.CheckViolation) new row for relation "merchant_products"
violates check constraint "merchant_products_available_quantity_check"
DETAIL: Failing row contains (1, 2, 1234.99, -1, 2024-11-06 13:49:24.125974).
Analyse du Problème :
-
Cause de l’Erreur :
- La requête tente de mettre à jour le stock à une valeur négative (-1)
- Une contrainte CHECK dans la table
merchant_products
empêche les quantités négatives - Cette contrainte est une protection essentielle pour maintenir l’intégrité des données
-
Séquence des Événements :
- La première requête a modifié le statut de la transaction à
COMPLETED
- La deuxième requête a échoué en essayant de décrémenter le stock
- Nous nous retrouvons dans un état incohérent : transaction validée mais stock non ajusté
- La première requête a modifié le statut de la transaction à
-
Implications :
- Les deux opérations sont interdépendantes mais non atomiques
- Un échec de la deuxième opération laisse la base dans un état invalide
- Le système ne vérifie pas la disponibilité du stock avant de valider la transaction
Objectif 2 [Code réponse CANCELED,1
]
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="ue_info_reseaux_db",
user="user_tp",
password="ue_info_reseaux_db",
options="-c search_path=game"
)
cur = conn.cursor()
try:
cur.execute("""
BEGIN;
UPDATE transactions
SET status = 'COMPLETED'
WHERE transaction_id = 733;
UPDATE merchant_products mp
SET available_quantity = mp.available_quantity - td.quantity
FROM transaction_details td
WHERE mp.merchant_id = td.merchant_id
AND mp.product_id = td.product_id
AND td.transaction_id = 733;
COMMIT;
""")
except Exception as e:
print("Erreur:", e)
conn.rollback()
finally:
cur.close()
conn.close()
Retourne :
Erreur: new row for relation "merchant_products" violates check constraint
"merchant_products_available_quantity_check"
DETAIL: Failing row contains (1, 2, 1234.99, -1, 2025-03-05 09:26:13.972379).
Cette solution :
- Regroupe les deux opérations dans une seule transaction atomique
- Si le stock est insuffisant, la contrainte
CHECK
déclenchera une erreur - Le
ROLLBACK
annulera automatiquement le changement de statut - La base reste dans un état cohérent
Avantages de cette approche :
- Pas besoin de vérifications explicites du stock
- Utilisation des contraintes natives de la base de données
- Gestion automatique des erreurs par PostgreSQL
- Garantie de l’atomicité des opérations
Énigme 5
Objectif 1 [Code réponse COMPLETED
]
Objectif 2 [Code réponse 1
]
Déroulement chronologique
-
T+0s : Démarrage Client A
- Le Client A démarre sa transaction
- Il acquiert un verrou exclusif sur la ligne de stock en exécutant l’UPDATE
- Il entre dans sa phase d’attente de 30 secondes
-
T+5s : Tentative Client B
- Le Client B démarre sa transaction
- Il tente d’acquérir un verrou sur la même ligne de stock
- Il est mis en attente car le verrou est déjà détenu par A
-
T+30s : Fin Client A
- Le Client A termine son attente
- Il valide sa transaction (
COMMIT
) - Les verrous sont libérés
-
T+30s : Échec Client B
- Le Client B tente de reprendre son exécution
- L’UPDATE échoue car le stock est maintenant insuffisant
- La transaction est annulée (
ROLLBACK
) - Message d’erreur :
new row for relation "merchant_products" violates check constraint
"merchant_products_available_quantity_check"
Explication des mécanismes
Le comportement observé illustre plusieurs concepts clés de la gestion de concurrence :
-
Verrouillage (Locking)
- PostgreSQL utilise un système de verrous pour garantir l’isolation des transactions
- L’
UPDATE
acquiert automatiquement un verrou exclusif - Les autres transactions doivent attendre la libération du verrou
-
Files d’attente
- Les transactions en attente sont mises en queue
- Elles sont réactivées dans l’ordre d’arrivée
- La première transaction conserve sa priorité
-
Contraintes d’intégrité
- La contrainte
CHECK
suravailable_quantity
reste active - Elle empêche les stocks négatifs même en cas de concurrence
- C’est elle qui provoque l’échec final de la transaction du Client B
- La contrainte
Protection contre la survente
Ce mécanisme protège effectivement contre la survente de plusieurs façons :
-
Isolation
- Une seule transaction peut modifier le stock à la fois
- Les modifications sont atomiques
- Pas de risque de lectures/écritures simultanées
-
Contraintes
- La contrainte
CHECK
forme une seconde ligne de défense - Même en cas de bug, le stock ne peut pas devenir négatif
- La contrainte
-
Ordre de traitement
- Les transactions sont traitées séquentiellement
- Premier arrivé, premier servi
Valeurs finales
À la fin de l’expérience :
- Stock final :
0
(toutes les unités ont été réservées par le Client A) - Statut transaction A :
COMPLETED
- Statut transaction B :
ROLLBACK
Cette solution de gestion de la concurrence est robuste mais présente aussi des limitations :
- Temps d’attente potentiellement long
- Risque de deadlock dans des scénarios plus complexes
- Impact sur les performances en cas de forte concurrence
Dans un système réel, on pourrait envisager des améliorations comme :
- Des timeouts sur les verrous
- Un système de réservation temporaire
- Une file d’attente applicative plutôt que base de données