Escape Game SQL
Phase 2 : Cohérence et Intégrité des données
Durée45 minutesMission
Suite à votre identification des activités suspectes dans la Phase 1, vous devez maintenant gérer les conséquences de l’attaque en restaurant les transactions légitimes tout en maintenant l’intégrité du système. Cette phase est cruciale car une erreur dans la gestion des données pourrait créer des incohérences dans le système de e-commerce.
Commencez par créer un notebook, copiez/collez le code suivant dans la première cellule et exécutez-la :
%reload_ext sql
%sql postgresql://user_tp:ue_info_reseaux_db@localhost/ue_info_reseaux_db
%config SqlMagic.displaylimit = None
Énigme 4 : Restauration des transactions
Objectif 1 : Gestion des états incohérents
Situation : Le service client a identifié que certaines transactions ont été marquées à tort comme frauduleuses lors de la réponse initiale à l’attaque. La transaction 738
en particulier a été confirmée comme légitime par le client. Cependant, la restauration d’une transaction implique non seulement de changer son statut mais aussi de s’assurer que les stocks des produits sont correctement ajustés.
Mission : Vous devez analyser et corriger l’état de la transaction 738
en vous assurant que tous les systèmes (statuts et stocks) restent cohérents. Votre défi est d’identifier pourquoi une simple mise à jour peut créer des problèmes et comment les éviter.
Dans un système de e-commerce réel, la cohérence entre les différents états (transaction, stock, paiement) est critique. Une incohérence peut mener à des problèmes graves comme la vente de produits non disponibles ou des pertes financières. C’est pourquoi les modifications d’état doivent être gérées de manière atomique et cohérente.
Commençons par examiner l’état actuel de la transaction :
-- 1. État initial : transaction, produits et stocks par marchand
SELECT
t.transaction_id,
t.status,
p.product_name,
td.merchant_id,
td.quantity,
mp.available_quantity,
mp.price as prix_unitaire,
td.unit_price as prix_payé
FROM transactions t
JOIN transaction_details td ON t.transaction_id = td.transaction_id
JOIN products p ON td.product_id = p.product_id
JOIN merchant_products mp ON td.merchant_id = mp.merchant_id
AND td.product_id = mp.product_id
WHERE t.transaction_id = 738;
Une fois l’état initial analysé, nous pouvons tenter de restaurer la transaction :
-- 2. Changement du statut de la transaction
UPDATE transactions
SET status = 'COMPLETED'
WHERE transaction_id = 738;
Maintenant, nous devons mettre à jour les stocks :
-- 3. Mise à jour du stock par marchand
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 = 738;
Mission : L’exécution de la dernière requête produit une erreur. Prenez le temps d’analyser le message d’erreur et réfléchissez aux implications. Ecrivez un compte-rendu de quelques lignes et consultez ensuite les indices un à un pour voir si vous n’avez rien oublié.
Pour valider cet objectif, notez la quantité restante en stock après la dernière requête UPDATE
.
Avant de passer à l’objectif suivant, remettez la transaction 738
dans son état initial :
UPDATE transactions
SET status = 'CANCELED'
WHERE transaction_id = 738;
Objectif 2 : Maintien de l’intégrité des données avec des transactions SQL
Situation : L’échec de notre tentative précédente met en évidence un risque majeur : si une des opérations échoue, le système peut se retrouver dans un état incohérent. Par exemple, une transaction pourrait être marquée comme complétée alors que les stocks n’ont pas été ajustés, ou vice versa.
Mission : Implémentez une solution utilisant les transactions SQL pour garantir que la restauration de la transaction 738
sera soit entièrement réussie, soit entièrement annulée, maintenant ainsi la cohérence du système.
Les transactions SQL sont cruciales dans les systèmes de e-commerce. Imaginez un client qui achète le dernier exemplaire d’un produit : nous devons garantir que la commande est enregistrée ET que le stock est mis à jour, ou qu’aucune des deux opérations n’est effectuée. C’est le principe du “tout ou rien” (atomicité).
Avant de résoudre notre problème, voyons un exemple simple de transaction SQL pour effectuer un virement bancaire :
-- Début de la transaction
BEGIN;
-- Débiter le compte source
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Créditer le compte destinataire
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- Si le compte 2 n'existe pas, le SGBD annule automatiquement la transaction
-- et toutes les modifications précédentes sont annulées
COMMIT;
Dans cet exemple :
BEGIN
démarre la transaction et regroupe les opérations qui suivent- Si une erreur survient (compte inexistant, contrainte violée…), le SGBD effectue automatiquement un
ROLLBACK
COMMIT
valide définitivement les modifications si tout s’est bien passé
La transaction garantit ainsi l’intégrité des données : soit toutes les opérations réussissent, soit aucune modification n’est appliquée. Dans notre exemple, si le compte destinataire n’existe pas, le compte source ne sera pas débité.
A noter que l’utilisation de %%sql
dans un Jupyter Notebook ne permet pas d’executer des transactions. Vous allez donc devoir utiliser un peu de code python pour vous connecter directement à la base de données :
import psycopg2
# Connexion
conn = psycopg2.connect(
host="localhost",
database="ue_info_reseaux_db",
user="user_tp",
password="ue_info_reseaux_db"
)
cur = conn.cursor()
# Transaction
try:
cur.execute("""
--ECRIVEZ ICI VOTRE REQUETE--
""")
except Exception as e:
print("Erreur:", e)
conn.rollback()
finally:
cur.close()
conn.close()
Vous devez maintenant créer une transaction SQL qui met à jour le statut de la transaction et ajuste les stocks et ne valide les changement que si tous les étapes réussissent.
Que se passe-t-il ? Vérifiez l’état après la transaction avec la requête de l’état initial. Les deux valeurs (statut et stock) devraient avoir changé de manière cohérente.
Pour valider cet objectif, combinez le statut de la transaction et la nouvelle quantité en stock après l’exécution de la transaction en utilisant le format suivant : statut,quantité
.
Validez vos réponses
Énigme 5 : Gestion de la concurrence d’accès
Situation : Suite à votre restauration de la transaction 738, le marchand a reçu un réapprovisionnement et dispose maintenant de 2 unités en stock du produit concerné. Cette information a déclenché une alerte automatique vers tous les clients qui surveillaient ce produit.
Afin de simuler cette situation exécutez la requête suivante :
UPDATE merchant_products mp
SET available_quantity = 2
FROM transaction_details td
WHERE mp.merchant_id = td.merchant_id
AND mp.product_id = td.product_id
AND td.transaction_id = 738;
Mission : Votre tâche est d’analyser ce qui se passe lorsque deux clients tentent d’accéder simultanément au même stock. Pour cela, vous allez simuler deux sessions d’achat concurrentes et observer leur comportement.
Dans un système e-commerce en production, de nombreux clients peuvent tenter d’acheter simultanément les derniers articles disponibles. Cette situation est particulièrement critique lorsqu’un produit recherché revient en stock en quantité limitée. Sans une gestion appropriée de la concurrence, le système pourrait vendre plus d’articles qu’il n’en a réellement en stock.
Pour simuler cette situation de concurrence d’accès, nous allons utiliser deux sessions distinctes représentant deux clients différents tentant d’accéder simultanément au même stock. Pour cela, vous devez :
- créer deux nouveaux notebooks Jupyter,
- les nommer respectivement “Client A” et “Client B” et
- copier le code correspondant à chaque client dans son notebook avant de l’executer (vous devrez respecter un timing précis pour executer les scripts).
Pensez à ajouter l’entête suivant dans la première celulle de chaque notebook :
%reload_ext sql
%sql postgresql://user_tp:ue_info_reseaux_db@localhost/ue_info_reseaux_db
La simulation nécessite une exécution précise et coordonnée des deux scripts. Voici la procédure à suivre pas à pas :
- Lancez d’abord le script du Client A
- Dans les 5 secondes qui suivent, exécutez le script du Client B
- Observez attentivement le comportement des deux sessions pendant l’exécution
- Patientez jusqu’à la fin complète de l’expérience (environ 30 secondes)
Code pour le Client A (Premier acheteur) :
Le script du Client A simule un acheteur qui initie une transaction longue : il commence par réserver le stock, effectue un traitement qui prend du temps (simulé par une pause de 30 secondes), puis finalise son achat en mettant à jour le statut de sa transaction.
# Notebook A (Premier Client)
import psycopg2
import time
# Connexion
conn_a = psycopg2.connect(
host="localhost", database="ue_info_reseaux_db",
user="user_tp", password="ue_info_reseaux_db"
)
cur_a = conn_a.cursor()
try:
# Démarrage de la transaction du client A
cur_a.execute("BEGIN;")
print("Transaction A démarrée!")
# Mise à jour du stock
print("Mise à jour du stock...")
cur_a.execute("""
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 = 738;
""")
print("Premier UPDATE effectué. Simulation d'un traitement long...")
time.sleep(30)
# Mise à jour de la transaction
cur_a.execute("""
UPDATE transactions
SET status = 'COMPLETED'
WHERE transaction_id = 738;
""")
cur_a.execute("COMMIT;")
print("Transaction A terminée avec succès!")
except Exception as e:
print("Erreur transaction A:", e)
conn_a.rollback()
finally:
cur_a.close()
conn_a.close()
- Code pour le Client B (Second acheteur) :
Le script du Client B représente un second acheteur qui tente d’acheter au même vendeur le même produit que le client A. Cependant il veut immédiatement acheter les 3 unités disponibles ce qui déclenche une nouvelle transaction mettant à jour le stock dans la foulée.
# Notebook B (Second Client)
import psycopg2
conn_b = psycopg2.connect(
host="localhost", database="ue_info_reseaux_db",
user="user_tp", password="ue_info_reseaux_db"
)
cur_b = conn_b.cursor()
try:
cur_b.execute("BEGIN;")
cur_b.execute("""
UPDATE merchant_products
SET available_quantity = available_quantity - 3
WHERE merchant_id = 1 AND product_id = 1;
""")
cur_b.execute("""
INSERT INTO transactions (customer_id, amount, ip_address, status, merchant_id, transaction_type)
VALUES (115, 2599.98, '192.168.1.2', 'COMPLETED', 1, 'PURCHASE');
""")
cur_b.execute("COMMIT;")
print("Transaction réussie")
except Exception as e:
print(f"Erreur: {e}")
conn_b.rollback()
finally:
cur_b.close()
conn_b.close()
Ecrivez un compte-rendu de quelques lignes pour répondre aux questions suivantes.
- Que se passe-t-il lors de l’exécution du script B ?
- Pourquoi le script B reste-t-il en attente ?
- Que se passe-t-il après les 30 secondes ?
- Quelle est la valeur finale du stock ?
- Cette situation protège-t-elle réellement contre la survente ?
Consultez ensuite les indices un à un pour voir si vous n’avez rien oublié.
Pour valider cet objectif :
- Notez le statut de la transaction de l’utilisateur A (après l’exécution des deux scripts)
- Indiquez la valeur finale du stock (après l’exécution des deux scripts)
Code objectif n°1 : statut_transaction
Code objectif n°2 : stock_final
Validez vos réponses
Finissez-en maintenant avec la Phase 3 pour analyser et corriger les droits d’accès des utilisateurs du système.