Comprendre la Cohérence de MongoDB : Le Modèle BASE Expliqué pour les Développeurs
Découvrez le modèle de cohérence de MongoDB avec ce guide approfondi destiné aux développeurs. Apprenez comment le modèle BASE favorise l'évolutivité de MongoDB, en contraste avec les bases de données ACID traditionnelles. Nous démystifions la cohérence éventuelle, explorons les préoccupations de lecture et d'écriture flexibles de MongoDB, et fournissons des exemples pratiques pour optimiser votre base de données en termes de performance et d'intégrité des données. Comprenez pourquoi ces choix sont essentiels pour construire des applications résilientes et performantes sur une plateforme NoSQL distribuée.
Comprendre la Cohérence de MongoDB : Le Modèle BASE Expliqué pour les Développeurs
La cohérence de MongoDB prête à confusion car les gens la réduisent à une seule phrase : "MongoDB est éventuellement cohérent." C'est trop simpliste pour être utile. Un ensemble de réplicas MongoDB peut offrir un comportement fort pour certaines opérations et des lectures obsolètes pour d'autres, en fonction de la préoccupation d'écriture, de la préoccupation de lecture, de la préférence de lecture, et de la survenue ou non d'un basculement.
Si vous venez de PostgreSQL ou MySQL, le plus grand ajustement est que la cohérence n'est pas un paramètre fixe pour toute l'application. Vous choisissez la garantie dont vous avez besoin pour chaque chemin. Un flux de paiement, un fil d'actualités et un tableau de bord analytique n'ont pas tous besoin de la même fraîcheur ou durabilité.
ACID vs. BASE : Deux Approches de la Cohérence
Avant de plonger dans le modèle de MongoDB, il est utile de comprendre les deux paradigmes principaux de la cohérence des bases de données : ACID et BASE.
Les Propriétés ACID (SGBDR Traditionnels)
Les systèmes de gestion de bases de données relationnelles traditionnels (SGBDR) comme PostgreSQL ou MySQL adhèrent généralement aux propriétés ACID, garantissant la fiabilité des données, en particulier dans les charges de travail transactionnelles. ACID signifie :
- Atomicité : Chaque transaction est traitée comme une unité unique et indivisible. Elle se termine entièrement (validation) ou ne se produit pas du tout (annulation). Il n'y a pas de transactions partielles.
- Cohérence : Une transaction fait passer la base de données d'un état valide à un autre. Elle garantit que les données écrites dans la base de données sont valides selon toutes les règles et contraintes définies.
- Isolation : Les transactions concurrentes s'exécutent en isolation, semblant s'exécuter séquentiellement. Le résultat des transactions concurrentes est le même que si elles étaient exécutées l'une après l'autre.
- Durabilité : Une fois qu'une transaction a été validée, elle reste validée même en cas de perte de courant, de crash ou d'autres défaillances système. Les modifications sont stockées de manière permanente.
ACID garantit une forte cohérence, ce qui les rend idéaux pour les applications nécessitant une intégrité stricte des données, comme les transactions financières.
Les Propriétés BASE (Bases de Données NoSQL comme MongoDB)
En revanche, de nombreuses bases de données NoSQL, y compris MongoDB, privilégient la disponibilité et la tolérance au partitionnement par rapport à la cohérence immédiate, s'alignant souvent sur le modèle BASE. BASE signifie :
- Fondamentalement Disponible : Le système garantit la disponibilité, ce qui signifie qu'il répondra à toute requête, même s'il ne peut pas garantir la version la plus récente des données.
- État Souple : L'état du système peut changer au fil du temps, même sans entrée. Cela est dû au modèle de cohérence éventuelle où les données se propagent de manière asynchrone dans le système.
- Cohérence Éventuelle : Si aucune nouvelle mise à jour n'est effectuée sur un élément de données donné, tous les accès à cet élément finiront par renvoyer la dernière valeur mise à jour. Il y a un délai avant que les modifications soient visibles sur tous les nœuds d'un système distribué.
Les systèmes conformes à BASE sont conçus pour une haute disponibilité et une évolutivité dans des environnements distribués, ce qui les rend adaptés aux applications qui peuvent tolérer une certaine latence dans la propagation des données.
Comprendre la Cohérence Éventuelle dans MongoDB
MongoDB peut montrer un comportement de cohérence éventuelle lorsque les lectures sont servies à partir de secondaires ou lorsque les écritures ne se sont pas encore répliquées partout. Cela signifie que lorsque vous écrivez des données dans un ensemble de réplicas MongoDB, le nœud primaire accusera réception de l'écriture, puis répliquera de manière asynchrone cette écriture vers ses nœuds secondaires. Alors que le primaire garantit la durabilité de l'écriture, il n'attend pas que tous les secondaires aient rattrapé leur retard avant d'accuser réception du succès au client. Par conséquent, une lecture ultérieure à partir d'un nœud secondaire peut ne pas refléter immédiatement la dernière écriture, bien qu'elle devienne éventuellement cohérente.
Ce choix de conception est fondamental pour la capacité de MongoDB à évoluer horizontalement et à maintenir une haute disponibilité. En n'exigeant pas que tous les nœuds soient parfaitement synchronisés pour chaque opération, MongoDB peut continuer à servir des lectures et des écritures même si certains nœuds sont temporairement indisponibles ou en retard.
Les Compromis de la Cohérence Éventuelle
- Avantages : Disponibilité plus élevée, meilleure performance (latence plus faible pour les écritures) et plus grande évolutivité pour les systèmes distribués.
- Inconvénients : Les applications doivent être conçues pour gérer la possibilité de lire des données obsolètes. Cela est particulièrement pertinent pour les opérations où la cohérence immédiate sur tous les réplicas est critique.
Les Préoccupations de Lecture et d'Écriture de MongoDB : Ajuster la Cohérence
Bien que MongoDB utilise par défaut la cohérence éventuelle, il fournit des mécanismes puissants – Préoccupations de Lecture et Préoccupations d'Écriture – qui permettent aux développeurs d'ajuster le niveau de cohérence par opération. Cela vous permet d'équilibrer la cohérence, la disponibilité et les performances selon les besoins de votre application.
Préoccupations d'Écriture
Une Préoccupation d'Écriture décrit le niveau d'accusé de réception demandé à MongoDB pour une opération d'écriture. Elle dicte combien de membres de l'ensemble de réplicas doivent confirmer l'écriture avant que l'opération ne retourne un succès.
Options clés de la Préoccupation d'Écriture :
w: Spécifie le nombre d'instancesmongodqui doivent accuser réception de l'écriture.w: 0: Aucun accusé de réception. Le client n'attend aucune réponse de la base de données. Cela offre le débit le plus élevé mais risque une perte de données si le primaire plante immédiatement après l'écriture.w: 1(Par défaut) : Accusé de réception du nœud primaire uniquement. Le primaire confirme avoir reçu et traité l'écriture. C'est rapide mais ne garantit pas que l'écriture a été répliquée sur les secondaires.w: "majority": Accusé de réception de la majorité des membres de l'ensemble de réplicas (y compris le primaire). Cela offre des garanties de durabilité plus fortes, car l'écriture est validée sur une majorité de nœuds. Si le primaire tombe en panne, les données sont garanties d'exister sur une majorité des autres nœuds.
j: Spécifie si l'instancemongoddoit écrire dans le journal sur disque avant d'accuser réception de l'écriture. Activer la journalisation (j: true) assure la durabilité même si le processusmongodplante.wtimeout: Un délai pour que la préoccupation d'écriture soit satisfaite. Si la préoccupation d'écriture n'est pas satisfaite dans ce délai, l'opération d'écriture retourne une erreur.
Exemple de Préoccupation d'Écriture (avec w: "majority" et journalisation) :
db.products.insertOne(
{ item: "ordinateur portable", qty: 50 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);
Astuce : Pour les données critiques qui doivent être durables et hautement disponibles,
w: "majority"avecj: trueest recommandé. Pour les données moins critiques ou la journalisation à haut débit,w: 1ou mêmew: 0peuvent être acceptables.
Préoccupations de Lecture
Une Préoccupation de Lecture vous permet de spécifier le niveau de cohérence et d'isolation pour les opérations de lecture. Elle détermine quelles données MongoDB retourne à vos requêtes, en particulier dans un environnement répliqué.
Options clés de la Préoccupation de Lecture :
local: Retourne les données de l'instance (primaire ou secondaire) à laquelle le client est connecté. C'est la valeur par défaut pour les instances autonomes et les secondaires. Pour les ensembles de réplicas, cela offre la latence la plus faible mais peut retourner des données obsolètes.available: Retourne les données de l'instance sans garantir que les données ont été écrites sur une majorité de l'ensemble de réplicas. Similaire àlocal, il privilégie la disponibilité et la faible latence.majority: Retourne les données qui ont été accusées réception par une majorité des membres de l'ensemble de réplicas. Cela garantit que les données sont durables et ne seront pas annulées. Il offre une cohérence plus forte quelocalouavailableau prix d'une latence potentiellement plus élevée.linearizable: Garantit que les données retournées reflètent la dernière écriture accusée réception globalement. C'est la préoccupation de lecture la plus forte, garantissant que les lectures voient toutes les écritures qui ont été accusées réception par une préoccupation d'écrituremajority. Cela peut entraîner une surcharge de performance significative et n'est disponible que pour les lectures à partir du primaire.snapshot(pour les transactions multi-documents) : Garantit que la requête retourne les données d'un point spécifique dans le temps, permettant aux lectures d'être cohérentes entre plusieurs documents au sein d'une transaction.
Exemple de Préoccupation de Lecture (avec majority) :
db.products.find(
{ item: "ordinateur portable" },
{ readConcern: { level: "majority" } }
);
Avertissement : Bien que
linearizableoffre une forte cohérence, cela a des implications sur les performances. Utilisez-le avec parcimonie pour les scénarios où un ordre strict et une visibilité globale des écritures sont critiques.
Pourquoi BASE et la Cohérence Éventuelle Comptent pour la Montée en Charge
Le modèle BASE et la cohérence éventuelle sont des facilitateurs essentiels de l'évolutivité et de la haute disponibilité de MongoDB :
- Montée en Charge Horizontale (Sharding) : En assouplissant la cohérence immédiate, MongoDB peut distribuer les données sur plusieurs shards (clusters d'ensembles de réplicas). Chaque shard fonctionne relativement indépendamment, permettant à la base de données de s'étendre horizontalement pour gérer des ensembles de données massifs et un débit élevé, sans exiger que chaque nœud de l'ensemble du système distribué soit parfaitement synchronisé à tout moment.
- Haute Disponibilité et Tolérance aux Pannes : Dans un ensemble de réplicas, si le nœud primaire devient indisponible, un nouveau primaire peut être élu parmi les secondaires. La cohérence éventuelle signifie que même pendant les basculements, les nœuds secondaires peuvent continuer à servir des lectures (selon la préoccupation de lecture), et le système reste disponible. Si le primaire devait attendre tous les secondaires pour chaque écriture, un seul secondaire en retard pourrait ralentir l'ensemble du système.
- Performance : Des exigences de cohérence moins strictes signifient une latence plus faible pour les opérations d'écriture et un débit global plus élevé, car le système n'a pas besoin de bloquer et d'attendre les accusés de réception de tous les nœuds avant de continuer.
En offrant une cohérence ajustable via les préoccupations de lecture et d'écriture, MongoDB permet aux développeurs de prendre des décisions éclairées. Les applications qui privilégient une haute disponibilité et un débit élevé (par exemple, l'ingestion de données IoT, l'analyse en temps réel) peuvent opter pour une cohérence plus faible. Inversement, les applications qui nécessitent une intégrité des données plus forte (par exemple, les transactions financières, les mises à jour d'inventaire) peuvent choisir des niveaux de cohérence plus forts, en acceptant les compromis de performance associés.
Considérations Pratiques et Meilleures Pratiques
- Identifier les Données Critiques : Déterminez quelles données nécessitent absolument une forte cohérence (par exemple, les soldes de comptes) par rapport aux données qui peuvent tolérer une cohérence éventuelle (par exemple, les mises à jour de profil utilisateur, les données de session).
- Concevoir pour l'Idempotence : Lors de l'utilisation de préoccupations d'écriture plus faibles, il est possible qu'une écriture réussisse sur le primaire mais échoue avant la réplication vers les secondaires, entraînant une annulation ultérieure et le client croyant que l'écriture a échoué. Si le client réessaie l'opération, cela pourrait entraîner des doublons. Concevez vos opérations pour être idempotentes lorsque cela est possible.
- Lecture de ses Propres Écritures Côté Client : Si un utilisateur effectue une écriture et tente immédiatement de la lire, il pourrait voir des données obsolètes s'il lit à partir d'un secondaire avec une préoccupation de lecture faible. Pour garantir qu'un utilisateur lise toujours ses propres écritures récentes, envisagez de diriger ces lectures vers le primaire ou d'utiliser une préoccupation de lecture
majority, éventuellement couplée à une préoccupation d'écrituremajoritypour ces opérations spécifiques. - Surveillance : Gardez un œil sur le retard de l'ensemble de réplicas en utilisant
rs.printReplicationInfo()ou les métriques MongoDB Atlas. Un retard de réplication élevé peut exacerber les problèmes de cohérence éventuelle.
Une Façon Plus Utile de Penser à la Cohérence de MongoDB
MongoDB n'est pas simplement "éventuellement cohérent" dans toutes les situations, et le traiter ainsi conduit à des conceptions bâclées. Une lecture à partir du primaire après une écriture accusée réception peut se comporter très différemment d'une lecture routée vers un secondaire qui a quelques secondes de retard. Une écriture accusée réception avec w: 1 a un profil de risque différent d'une écriture accusée réception avec w: "majority". L'histoire de la cohérence dépend de votre préférence de lecture, de votre préoccupation de lecture, de votre préoccupation d'écriture, de votre topologie, et de la survenue ou non d'un basculement au mauvais moment.
Pour une page produit normale, la cohérence éventuelle peut être acceptable. Si un administrateur modifie la description d'un produit et qu'un client voit l'ancienne description pendant un court moment à partir d'un secondaire, l'impact commercial est généralement faible. Pour une page de confirmation de commande, la tolérance est différente. Si un client soumet une commande et que l'écran suivant ne la trouve pas, même brièvement, le système semble défectueux. C'est là que le comportement de lecture de ses propres écritures compte plus que le débit brut.
Un modèle pratique consiste à utiliser des paramètres plus forts pour les chemins de confirmation orientés utilisateur et des paramètres plus souples pour les chemins d'arrière-plan ou analytiques. Par exemple, une écriture de commande pourrait utiliser w: "majority", et la lecture de confirmation immédiate pourrait aller au primaire. Un tableau de bord qui agrège l'activité de la veille peut lire à partir des secondaires car un peu de retard est généralement acceptable. Un pipeline d'ingestion de journaux peut accepter un accusé de réception plus faible qu'un grand livre de facturation, mais il doit toujours être honnête sur ce qui peut être perdu lors d'un crash ou d'un basculement.
Soyez prudent avec le mot "disponible" également. Une base de données distribuée peut continuer à servir certaines requêtes pendant les pannes, mais cela ne signifie pas que chaque requête peut réussir avec les mêmes garanties. Une élection primaire interrompt les écritures pendant une courte période. Un secondaire ne peut servir des lectures que si votre préférence de lecture le permet. Une partition réseau peut forcer MongoDB à choisir la sécurité plutôt que d'accepter des écritures sur un nœud qui n'appartient plus à la majorité. Ce ne sont pas des défauts ; ce sont les compromis qui empêchent les données répliquées de se diviser en deux historiques conflictuels.
Voici la décision que j'écrirais dans une note de conception d'application :
Mutations critiques des comptes, commandes et inventaires :
- writeConcern: majority
- relire depuis le primaire lors de la confirmation de l'action de l'utilisateur
- utiliser des écritures réessayables là où le pilote les supporte
- rendre les opérations d'écriture idempotentes avec des ID de requête ou des clés uniques
Pages de recherche, pages de fil d'actualité, analyses et affichage de profil non critique :
- les lectures secondaires peuvent être acceptables
- tolérer des résultats obsolètes dans l'interface utilisateur
- afficher les horodatages lorsque la fraîcheur est importante
Ce genre de note est plus utile que de dire "MongoDB est BASE" et de passer à autre chose. Il indique aux futurs ingénieurs où les lectures obsolètes sont acceptables et où elles ne le sont pas.