Transactions résilientes : Opérations idempotentes en pratique
Les transactions multiphases garantissent l'intégrité des données en cas de panne grâce à des opérations idempotentes et un journal d'états. Cette approche fonctionne avec n'importe quelle base de données, y compris MongoDB, et évite les pertes de données dues à des coupures de courant ou des plantages système.
Fonctionnement des transactions multiphases
Les transactions se décomposent en étapes séquentielles, chacune enregistrée dans une table TRANSACTIONS dédiée. États clés :
- blocking — verrouillage des comptes pour éviter les opérations concurrentes.
- save_balance — capture des soldes initiaux dans le journal.
- main — exécution du transfert principal de fonds.
- unlock — libération des verrous après achèvement.
- finished — état final pour les transactions réussies.
L'idempotence assure qu'une répétition d'étape après une panne ne corrompt pas les données. Par exemple, A := B + 1 est idempotent, contrairement à A := A + 1 — mais celle-ci peut se décomposer en deux étapes idempotentes.
Étapes d'implémentation avec exemples de code
Transférons 250 € du compte d'Assange à celui de l'Agent Y. Données initiales dans la table ACCOUNTS :
| ID | NAME | BALANCE | TR_ID |
|----|----------|---------|-------|
| 34 | Assange | 300 | null |
| 78 | Agent Y | 7 | null |
1. Verrouiller les comptes (STATUS='blocking')
D'abord, créer un enregistrement TRANSACTIONS :
| ID | FROM_ID | TO_ID | AMOUNT | FROM_BALANCE | TO_BALANCE | STATUS |
|----|---------|-------|--------|--------------|------------|----------|
| 172| 34 | 78 | 250 | null | null | blocking |
Le verrouillage utilise des commandes atomiques, par exemple en MongoDB :
db.accounts.updateOne(
{ ID: 34, TR_ID: null },
{ $set: { TR_ID: 172 } }
);
Si aucun document n'est mis à jour, le verrou a échoué — réessayer ou annuler. Trier les ID au préalable pour éviter les interblocages.
2. Capturer les soldes (STATUS='save_balance')
Après verrouillage, copier les soldes initiaux dans TRANSACTIONS — idempotent par conception :
db.transactions.updateOne(
{ ID: 172 },
{ $set: { FROM_BALANCE: 300, TO_BALANCE: 7, STATUS: 'save_balance' } }
);
3. Exécuter le transfert (STATUS='main')
Opération principale utilisant les soldes sauvegardés pour l'idempotence :
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { BALANCE: 50 } } // 300 - 250
);
db.accounts.updateOne(
{ ID: 78, TR_ID: 172 },
{ $set: { BALANCE: 257 } } // 7 + 250
);
Mettre à jour le statut en 'unlock' seulement après application des changements.
4. Déverrouiller et finaliser (STATUS='unlock')
Libérer les verrous avec vérification TR_ID :
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { TR_ID: null } }
);
Après déverrouillage des deux, passer le statut à 'finished'.
Avantages et limites
Avantages clés :
- Tolérance aux pannes : Récupération de toute défaillance sans perte de données.
- Idempotence : Réessais sécurisés, essentiels en systèmes distribués.
- Polyvalence : Compatible avec toutes les bases, pas seulement MongoDB.
- Cohérence : Intégrité des données à chaque étape.
- Performance : Journalisation supplémentaire peut ralentir.
Inconvénients : complexité accrue et surcharge du journal. Pour les apps financières ou systèmes critiques, cela en vaut la peine.
Conseils pour développeurs
- Exploiter les opérations atomiques de votre SGBD pour verrous et mises à jour.
- Implémenter une logique de réessai pour les verrous échoués.
- Tester les scénarios de crash, comme des arrêts brutaux de processus.
- Trier les ID pour minimiser les risques d'interblocage.
Cela offre une fiabilité au niveau 2PC avec la flexibilité NoSQL.
— Editorial Team
Aucun commentaire pour le moment.