Bonnes pratiques pour l'utilisation des transactions et la configuration du Read/Write Concern dans ApsaraDB for MongoDB.
Contexte
MongoDB 4.0 a introduit les transactions autonomes (transactions sur un ensemble de réplicas) pour les opérations portant sur une ou plusieurs collections au sein d'un même ensemble de réplicas. MongoDB 4.2 a ajouté les transactions distribuées (transactions shardées) pour les opérations s'étendant sur plusieurs collections et shards.
Dans MongoDB, les opérations sur un seul document sont toujours atomiques. Les applications peuvent utiliser des documents intégrés et des tableaux pour modéliser les relations au sein d'un seul document, contrairement aux bases de données relationnelles qui nécessitent des collections normalisées et des jointures. Avec une modélisation des données appropriée, l'atomicité au niveau du document unique élimine le besoin de transactions distribuées dans la plupart des cas.
Cependant, les applications des domaines financier ou comptable peuvent toujours nécessiter des transactions distribuées. MongoDB 4.2 et versions ultérieures prennent entièrement en charge cette fonctionnalité.
Transactions
Informations de base
Les API de transaction MongoDB sont similaires à celles des bases de données relationnelles, ce qui rend la courbe d'apprentissage minimale.
L'exemple suivant illustre une transaction complète utilisant les API startTransaction/abortTransaction/commitTransaction, ainsi que les paramètres session et readConcern/writeConcern.
// Create collections.
db.getSiblingDB("mydb1").foo.insertOne(
{abc: 0},
{ writeConcern: { w: "majority", wtimeout: 2000 } }
)
db.getSiblingDB("mydb2").bar.insertOne(
{xyz: 0},
{ writeConcern: { w: "majority", wtimeout: 2000 } }
)
// Start a session.
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll1 = session.getDatabase("mydb1").foo;
coll2 = session.getDatabase("mydb2").bar;
// Start a transaction.
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
// Perform operations in the transaction.
try {
coll1.insertOne( { abc: 1 } );
coll2.insertOne( { xyz: 999 } );
} catch (error) {
// Abort the transaction if an error occurs.
session.abortTransaction();
throw error;
}
// Commit the transaction.
session.commitTransaction();
session.endSession();
Comportements clés des transactions :
Chaque transaction doit être associée à une session. Une session ne prend en charge qu'une seule transaction active à la fois. La fin d'une session entraîne l'annulation de toute transaction active.
Une transaction distribuée peut s'étendre sur plusieurs documents, collections et bases de données.
Une transaction peut lire ses propres écritures non validées, mais ces écritures ne sont pas visibles pour les opérations extérieures à la transaction.
Les écritures ne sont pas répliquées vers les nœuds secondaires tant que la transaction n'est pas validée. Après validation, les écritures sont automatiquement appliquées à tous les nœuds secondaires.
Les transactions verrouillent les documents qu'elles modifient, bloquant ainsi les autres opérations jusqu'à leur achèvement. Si un verrou ne peut pas être acquis dans un délai de 5 millisecondes (par défaut), la transaction est abandonnée avec une erreur de conflit d'écriture. Ce délai d'attente est contrôlé par le paramètre maxTransactionLockRequestTimeoutMillis.
Les transactions retent automatiquement en cas d'erreurs transitoires, telles que des interruptions réseau temporaires. Cette nouvelle tentative est transparente pour le client.
Toute transaction s'exécutant pendant plus de 60 secondes est forcement abandonnée. Ce délai d'attente est contrôlé par le paramètre transactionLifetimeLimitSeconds.
Limites
Il est impossible de créer de nouvelles collections ou index dans une transaction distribuée.
Les transactions ne peuvent pas écrire dans des collections plafonnées (capped collections).
Les transactions ne peuvent pas utiliser le niveau de lecture
snapshotpour lire à partir de collections plafonnées. Cette limite s'applique à MongoDB 5.0 et versions ultérieures.Dans une transaction, il est interdit de lire ou d'écrire dans les collections des bases de données
config/admin/local.Les transactions ne peuvent pas écrire dans les collections système, telles que les collections au format
system.*.Les transactions ne prennent pas en charge
explain.Il est impossible d'utiliser
getMoreà l'intérieur d'une transaction pour lire des curseurs créés en dehors de celle-ci. De même, vous ne pouvez pas utilisergetMoreen dehors d'une transaction pour lire des curseurs créés à l'intérieur.La première opération d'une transaction ne peut pas être une commande telle que killCursors ou
hello.Il est impossible d'exécuter des commandes non CRUD dans une transaction. Cela inclut listCollections, listIndexes, createUser, getParameter et
count.Pour les transactions distribuées, vous ne pouvez pas définir le paramètre writeConcernMajorityJournalDefault sur false dans les shards.
Les transactions distribuées ne prennent pas en charge les shards comportant des arbitres.
Bonnes pratiques
Privilégiez les transactions autonomes aux transactions distribuées
Les transactions distribuées offrent des performances inférieures à celles des transactions autonomes en raison de leur logique plus complexe. Dans MongoDB, les modèles de données dénormalisés (documents intégrés et tableaux) restent le meilleur choix. Avec une modélisation des données appropriée, les transactions autonomes répondent à la plupart des besoins transactionnels.
Évitez les transactions de longue durée
MongoDB abandonne automatiquement les transactions distribuées qui s'exécutent pendant plus de 60 secondes. Divisez les grandes transactions en parties plus petites et assurez-vous que les requêtes utilisent des index appropriés pour une exécution rapide.
Évitez de modifier trop de documents dans une seule transaction
Bien qu'il n'y ait pas de limite stricte au nombre de documents pouvant être lus dans une transaction, la modification de nombreux documents augmente la charge de synchronisation entre le primaire et les secondaires, ce qui peut entraîner un retard de réplication. Limitez les modifications à 1 000 documents par transaction. Pour des lots plus importants, divisez-les en plusieurs transactions.
Évitez les transactions volumineuses dépassant 16 Mo
Dans MongoDB 4.0, une transaction utilise une seule entrée oplog limitée à 16 Mo. Les opérations de mise à jour stockent les changements incrémentiels ; les insertions stockent les documents complets. Si l'oplog combiné dépasse 16 Mo, la transaction est abandonnée et annulée.
MongoDB 4.2 et versions ultérieures peuvent diviser les écritures de transaction sur plusieurs entrées oplog, supprimant ainsi la limite d'une seule entrée de 16 Mo. Cependant, maintenez la taille des transactions inférieure à 16 Mo pour éviter d'autres problèmes.
Gérez les annulations de transaction côté client
Lorsqu'une transaction est abandonnée, elle renvoie une exception et est annulée. Votre application doit intercepter ces exceptions et réessayer en cas d'erreurs transitoires (basculement primaire/secondaire, panne de nœud). Bien que les pilotes MongoDB retent automatiquement les validations via Retryable Writes, l'application doit toujours gérer les erreurs non retenantables telles que TransactionTooLarge, TransactionTooOld et TransactionExceededLifetimeLimitSeconds.
Évitez d'effectuer des opérations DDL dans les transactions
Les opérations DDL telles que createIndex ou dropDatabase sont bloquées par les transactions actives sur la même base de données ou collection. Les opérations DDL bloquées empêchent les nouvelles transactions d'acquérir des verrous, ce qui entraîne leur abandon.
MongoDB 4.4 a assoupli ces restrictions via le paramètre shouldMultiDocTxnCreateCollectionAndIndexes. Vous pouvez exécuter createCollection ou createIndex dans une transaction distribuée, avec les limites suivantes :
Les collections ne peuvent être créées qu'implicitement.
La collection cible ne doit pas déjà exister.
La collection cible doit être vide.
Par conséquent, évitez d'effectuer des opérations DDL dans les transactions.
Annulez rapidement les transactions non validées ou erronées
Les modifications de transaction non validées résident dans le cache WiredTiger. Plusieurs transactions concurrentes non validées ou erronées peuvent exercer une pression sur le cache et causer d'autres problèmes. Annulez les transactions inutiles dès que possible pour libérer les ressources.
Augmentez le délai d'attente du verrou si les transactions sont fréquemment annulées
Par défaut, une opération de transaction est annulée si elle ne peut pas acquérir un verrou dans un délai de 5 millisecondes. Les verrous sont libérés lors de la validation ou de l'annulation. Si les délais d'attente de verrouillage entraînent des annulations fréquentes, augmentez le paramètre maxTransactionLockRequestTimeoutMillis.
Si l'augmentation du délai d'attente n'aide pas, vérifiez s'il existe des opérations qui conservent les verrous pendant de longues périodes, telles que des opérations DDL ou des requêtes non optimisées, et optimisez-les.
Évitez les modifications concurrentes du même document à l'intérieur et à l'extérieur d'une transaction
Si une écriture externe modifie un document qu'une transaction en cours modifie également, la transaction est annulée en raison d'un conflit d'écriture. À l'inverse, si une transaction détient déjà un verrou sur un document, les écritures externes sur ce document attendent la fin de la transaction.
En cas de conflit d'écriture, l'écriture externe n'échoue pas. MongoDB la tente en interne, en incrémentant le compteur writeConflicts jusqu'à ce qu'elle réussisse. Le client perçoit une opération réussie mais plus lente.
Un petit nombre de conflits d'écriture a un impact minimal. Des conflits d'écriture fréquents dégradent les performances. Utilisez les journaux d'audit ou les journaux de requêtes lentes pour détecter les conflits d'écriture excessifs.
Risques liés aux bugs du noyau
Les transactions de longue durée ou de grande taille exercent une charge significative sur le cache WiredTiger. Depuis le début de la plus ancienne transaction non validée, WiredTiger doit maintenir les données et l'état pour toutes les écritures suivantes. Les transactions actives partagent le même instantané, de sorte que les nouvelles écritures s'accumulent dans le cache tout au long de la durée de vie de la transaction et ne peuvent pas être évincées tant que la transaction n'est pas validée ou abandonnée. Un cache surchargé (utilisation et utilisation dirty dépassant les seuils) provoque des bégaiements de la base de données, une augmentation de la latence, une utilisation totale du CPU et des interblocages potentiels. Problèmes connexes : SERVER-50365 et SERVER-51281.
ApsaraDB for MongoDB recommande la mise à niveau vers MongoDB 5.0 ou version ultérieure pour les applications qui utilisent intensivement les transactions.
Read Concern
Informations de base
Read Concern contrôle la cohérence et l'isolation avec les niveaux suivants :
"local": Valeur par défaut pour les lectures sur les nœuds primaires ou secondaires. Lit à partir du nœud local et peut renvoyer des données qui seront ultérieurement annulées."available": Valeur par défaut pour les lectures sur les secondaires dans un cluster shardé. Peut renvoyer des données qui seront ultérieurement annulées. Ne vérifie pas la version du shard et peut renvoyer des documents orphelins. Offre la latence d'accès la plus faible."majority": Lit les données confirmées par une majorité des membres de l'ensemble de réplicas. Ces données ne seront pas annulées."linearizable": Niveau le plus strict. Attend que toutes les écritures précédentes soient confirmées par une majorité de nœuds. Performances les plus faibles ; disponible uniquement sur le primaire."snapshot": Lit à partir d'un instantané de données confirmé par une majorité de nœuds. Peut être associé à un point dans le temps spécifique en utilisant atClusterTime.
Remarques d'utilisation :
Les dernières données sur un seul nœud mongod ne représentent pas nécessairement les données les plus récentes de l'ensemble de réplicas, quel que soit le niveau de Read Concern.
Différents niveaux de Read Concern peuvent être spécifiés par opération. MongoDB 4.4+ prend également en charge une valeur par défaut côté serveur, que les paramètres au niveau de l'opération remplacent.
Le Read Concern est ignoré lors de la lecture à partir de la base de données
local. Toutes les données de la base de donnéeslocalsont lisibles quel que soit le niveau.Les transactions distribuées ne prennent en charge que trois niveaux de Read Concern :
"local","majority"et"snapshot".Les sessions causalement cohérentes doivent utiliser le niveau de Read Concern
"majority".
Bonnes pratiques
Pour les transactions distribuées, définissez le Read Concern pour la transaction, et non pour les opérations individuelles
Spécifiez le Read Concern au niveau de la transaction. Le paramètre au niveau de la transaction remplace tous les autres paramètres ou valeurs par défaut de Read Concern.
Le Read Concern s'applique à toute requête de base de données, qu'il s'agisse d'une lecture sur un seul document, d'une lecture multi-documents ou d'une partie d'une transaction.
Utilisez le niveau de Read Concern "majority" pour les cas d'utilisation généraux
Définissez le Read Concern sur "majority" pour l'isolation et la cohérence. Cela garantit que l'application lit uniquement les données répliquées sur une majorité de nœuds, empêchant ainsi l'annulation lors de l'élection du primaire.
Pour les scénarios « Read Your Own Writes », lisez à partir du primaire et utilisez le niveau de Read Concern "local" ou "linearizable"
Lisez à partir du nœud primaire avec le Read Concern "local" ou "linearizable". Si le Write Concern est "majority", vous pouvez également utiliser le Read Concern "majority".
MongoDB 3.6+ prend également en charge les sessions causalement cohérentes pour ce scénario.
Pour les scénarios nécessitant la cohérence la plus forte, utilisez le Read Concern "linearizable" avec un délai d'attente maxTimeMS
Le niveau "linearizable" confirme que le nœud est toujours le primaire et que les données renvoyées ne seront pas annulées. Cependant, cela a un impact significatif sur la latence. Utilisez-le avec un délai d'attente maxTimeMS pour éviter un blocage indéfini si une majorité de nœuds devient indisponible.
Write Concern
Informations de base
Le Write Concern utilise le format suivant. Référence complète : Write Concern.
{ w: <value>, j: <boolean>, wtimeout: <number> }
-
Le Write Concern contrôle les garanties de persistance des données à ces niveaux :
{w: 0}: Aucune confirmation. Ne confirme pas l'achèvement ; une perte de données peut survenir.
{w: 1}: Confirme l'écriture en mémoire. Valeur par défaut avant MongoDB 5.0. Une perte de données peut encore se produire car les données ne sont pas encore persistées sur le disque.
{j: true}: Confirmation du journal. Confirme que l'écriture est vidée dans le journal des transactions anticipées (WAL) sur le stockage persistant. L'écriture ne sera pas perdue.
{ w: "majority" }: Confirmation par la majorité. Valeur par défaut pour MongoDB 5.0+. Attend que l'écriture soit répliquée sur une majorité de nœuds. Les données ne seront pas annulées.
Confirmation de réplica : Attend la réplication vers un nombre spécifié de nœuds avant de confirmer.
Confirmation personnalisée : Vous pouvez utiliser le paramètre settings.getLastErrorModes pour spécifier d'autres méthodes de confirmation personnalisées à l'aide de tags.
Remarques d'utilisation :
-
Le Write Concern peut être spécifié pour toute opération d'écriture ou transaction. S'il est omis, la valeur par défaut est utilisée.
RemarqueDans MongoDB 5.0+, la valeur par défaut globale du Write Concern pour un ensemble de réplicas standard à trois membres est passée de
{w:1}à{w:"majority"}. Cela peut entraîner une dégradation des performances après la mise à niveau. Les nœuds cachés, les nœuds retardés et les autres nœuds votants avec une priorité de 0 dans un ensemble de réplicas peuvent tous compter pour une confirmation
"majority".Différents niveaux de Write Concern peuvent être spécifiés par opération. MongoDB 4.4+ prend également en charge une valeur par défaut côté serveur, que les paramètres au niveau de l'opération remplacent.
Lorsque vous écrivez dans la base de données
local, tout Write Concern spécifié est ignoré.Les sessions causalement cohérentes doivent utiliser le Write Concern
"majority".
Bonnes pratiques
Pour les transactions distribuées, définissez le Write Concern pour la transaction, et non pour les opérations individuelles
La définition d'un Write Concern pour des opérations d'écriture individuelles au sein d'une transaction renvoie une erreur.
Utilisez le Write Concern "majority" pour les cas d'utilisation généraux
Le Write Concern "majority" garantit qu'une majorité des nœuds de l'ensemble de réplicas confirme l'écriture, empêchant ainsi la perte de données ou l'annulation même en cas de pannes de nœuds ou de basculements inattendus.
Pour les scénarios à forte intensité d'écriture, envisagez d'utiliser {w:1} et surveillez le retard de réplication des nœuds secondaires
{w:1} offre de meilleures performances d'écriture pour les scénarios à forte charge d'écriture. Cependant, surveillez le retard de réplication des nœuds secondaires. Un retard excessif peut amener le primaire à ROLLBACK. Si le retard dépasse la période de rétention de l'oplog, les secondaires entrent dans un état RECOVERING qui nécessite une intervention manuelle.
Lors du chargement par lot de données ou de la migration DTS sur une instance ApsaraDB for MongoDB exécutant une version antérieure à 5.0, utilisez le Write Concern "majority" si un retard excessif se produit.
Définissez le Write Concern le plus approprié pour différentes opérations
Le Write Concern peut être défini par opération. Par exemple, utilisez des transactions avec un Write Concern spécifique pour les données financières afin de garantir l'atomicité, "majority" pour les données principales des joueurs afin d'éviter l'annulation, et la valeur par défaut ou {w:1} pour les données de journalisation.
MongoDB offre cette flexibilité afin que les applications puissent choisir les paramètres appropriés en fonction de leurs exigences.