Dans les débuts des bases de données relationnelles, les propriétés ACID — Atomicité, Cohérence, Isolation et Durabilité — étaient la référence absolue pour l'intégrité des données. Pendant des décennies, les développeurs pouvaient supposer qu'une transaction était une unité de travail immuable. Cependant, à mesure que les systèmes évoluaient vers une architecture horizontale pour gérer des charges massives et assurer une haute disponibilité, les garanties rigides de l'ACID ont commencé à entrer en conflit avec les réalités du calcul distribué. Aujourd'hui, les ingénieurs de bases de données doivent naviguer dans le paysage complexe des compromis entre une forte cohérence et une haute disponibilité, souvent guidés par le théorème CAP.
L'évolution de l'ACID vers le BASE
Le passage des SGBD relationnels traditionnels aux solutions NoSQL modernes représente un changement de philosophie. Alors que l'ACID met l'accent sur l'exactitude à tout prix, le modèle BASE (Basically Available, Soft state, Eventual consistency — essentiellement disponible, état souple, cohérence éventuelle) privilégie la disponibilité du système et la tolérance aux partitions. Cela ne signifie pas que le NoSQL manque de fiabilité ; il accepte plutôt que des incohérences temporaires soient un prix acceptable pour maintenir le système en fonctionnement lors de partitions réseau.
Comprendre ce changement est crucial car il dicte la façon dont nous concevons nos modèles de données. Lorsque vous choisissez entre une base de données fortement cohérente comme PostgreSQL et un magasin à cohérence éventuelle comme Cassandra ou DynamoDB, vous ne choisissez pas simplement un outil ; vous prenez une décision architecturale fondamentale concernant l'expérience utilisateur et l'intégrité des données.
Naviguer dans le théorème CAP
Le théorème CAP stipule qu'un système distribué ne peut garantir que deux propriétés sur trois : la Cohérence, la Disponibilité et la Tolérance aux partitions. Puisque les partitions réseau sont inévitables dans les environnements distribués, nous devons généralement choisir entre la Cohérence et la Disponibilité.
Les systèmes CP (Cohérence et Tolérance aux partitions) sacrifieront la disponibilité pour s'assurer que tous les nœuds voient les mêmes données au même moment. Si un nœud ne peut pas vérifier la cohérence des données, il cesse de servir les requêtes. Cela est vital pour les systèmes financiers où le double dépense est inacceptable.
Les systèmes AP (Disponibilité et Tolérance aux partitions) continueront de servir les requêtes même s'ils ne peuvent pas garantir que les données sont à jour. Cela est idéal pour les fils d'actualité des réseaux sociaux ou les couches de cache où des données périmées sont préférables à l'absence de données.
Stratégies pratiques pour gérer les compromis
L'ingénierie des bases de données modernes repose rarement sur un choix binaire. Au lieu de cela, nous utilisons des modèles sophistiqués pour atténuer les inconvénients des deux approches. Une stratégie courante consiste à utiliser la cohérence éventuelle avec résolution de conflits. En permettant aux écritures de se poursuivre sur différents nœuds et en réconciliant les conflits ultérieurement, nous maintenons la disponibilité. Cependant, cela nécessite une conception minutieuse de la logique de résolution des conflits, utilisant souvent des algorithmes tels que Last-Write-Wins (Dernière écriture gagne) ou des horloges vectorielles.
Une autre technique puissante est l'utilisation de modèles Saga pour les transactions distribuées. Au lieu d'une seule transaction de longue durée qui verrouille les ressources, une Saga décompose la transaction en une séquence de transactions locales, chacune ayant une action de compensation correspondante pour annuler les modifications si une étape ultérieure échoue.
Voici un exemple conceptuel de la manière dont un orchestrateur Saga pourrait gérer un virement de fonds :
class FundTransferSaga {
async execute(transactionId, from, to, amount) {
try {
// Étape 1 : Débiter le compte utilisateur (transaction locale)
await accountService.debit(from, amount);
// Étape 2 : Créditer le compte du bénéficiaire (transaction locale)
await accountService.credit(to, amount);
// Si l'étape 2 échoue, exécuter la transaction de compensation pour l'étape 1
} catch (error) {
console.error("Le transfert a échoué, initiation de la compensation...", error);
await accountService.refund(from, amount);
throw error;
}
}
}
Cette approche garantit l'atomicité sans la surcharge du verrouillage distribué, mais elle introduit une complexité dans la garantie d'une sémantique exactement une fois (exactly-once) et dans la gestion des pannes partielles.
Conclusion
Il n'existe pas de solution unique pour la cohérence et la disponibilité dans les systèmes distribués. Le choix « approprié » dépend entièrement de vos exigences commerciales. Pour la banque, le CP est non négociable. Pour l'inventaire du commerce électronique, vous pourriez tolérer de brèves incohérences au nom de la disponibilité pendant les pics de ventes. En tant qu'ingénieur de bases de données, votre rôle est de comprendre profondément ces compromis, de mettre en œuvre les niveaux d'isolation appropriés et de communiquer les implications à vos équipes produit. En équilibrant la rigueur de l'ACID avec la flexibilité distribuée, vous pouvez construire des systèmes à la fois résilients et fiables.