Bonnes pratiques : Éviter les pièges de performance courants avec MongoDB
Évitez les pièges de performance MongoDB grâce à des schémas ciblés, des index utiles, des projections, une pagination par clé et une surveillance des requêtes.
Bonnes pratiques : Éviter les pièges de performance courants avec MongoDB
Les pièges de performance MongoDB commencent généralement modestement : un tableau sans limite, un index composé manquant, ou une requête de tableau de bord qui analyse bien plus de documents que prévu. À mesure que vos données grandissent, ces choix peuvent se transformer en pages lentes, en utilisation élevée du CPU et en fenêtres de maintenance douloureuses.
Utilisez cette revue comme une liste de contrôle pour la conception de schémas, l'indexation, la forme des requêtes et les habitudes opérationnelles.
1. Conception du schéma : Le fondement de la performance
L'optimisation des performances commence bien avant que la première requête ne soit écrite. La façon dont vous structurez vos données impacte directement l'efficacité des lectures et des écritures.
Limiter la taille des documents et prévenir le gonflement
Les documents MongoDB ont une limite de taille de document BSON de 16 Mo. Vous devriez généralement rester bien en dessous de cela pour les données opérationnelles chaudes. Les documents très volumineux consomment plus de mémoire, nécessitent plus de bande passante réseau et rendent les mises à jour plus coûteuses.
Bonne pratique : Garder les documents ciblés
Concevez les documents pour qu'ils contiennent uniquement les données les plus essentielles et fréquemment consultées. Utilisez des références pour les grands tableaux ou les entités connexes rarement nécessaires en même temps que le document parent.
Piège : Stocker des journaux historiques massifs ou des fichiers binaires volumineux (comme des images haute résolution) directement dans les documents opérationnels.
Le compromis entre l'incorporation et la référence
Décider entre l'incorporation (stockage des données connexes dans le document principal) et la référence (utilisation de liens via _id et $lookup) est essentiel pour optimiser les performances de lecture.
| Stratégie | Meilleur cas d'utilisation | Impact sur les performances |
|---|---|---|
| Incorporation | Données petites, fréquemment consultées et étroitement couplées (par exemple, avis sur les produits, détails d'adresse). | Lectures rapides : Moins de requêtes/voyages réseau nécessaires. |
| Référence | Données volumineuses, rarement consultées ou changeant rapidement (par exemple, grands tableaux, données partagées). | Lectures plus lentes : Nécessite $lookup (équivalent de jointure), mais empêche le gonflement des documents et permet des mises à jour plus faciles des données référencées. |
Avertissement : Croissance des tableaux
Si un tableau dans un document incorporé peut croître indéfiniment, comme une liste de toutes les actions des utilisateurs, référencez ces actions à partir d'une collection distincte. Les tableaux illimités agrandissent les documents, ralentissent les mises à jour et peuvent éventuellement atteindre la limite de taille du document.
2. Stratégies d'indexation : Éliminer les analyses de collection
Les index sont le facteur le plus critique pour les performances de MongoDB. Une analyse de collection (COLLSCAN) se produit lorsque MongoDB doit lire chaque document d'une collection pour satisfaire une requête, ce qui est généralement lent sur de grands ensembles de données.
Création et vérification proactives des index
Assurez-vous qu'un index existe pour chaque champ utilisé dans la clause filter d'une requête, sa clause sort ou sa projection (pour les requêtes couvertes).
Utilisez la méthode explain('executionStats') pour vérifier que les index sont utilisés et pour identifier les analyses de collection.
// Vérifiez si cette requête utilise un index
db.users.find({ status: "active", created_at: { $gt: ISODate("2023-01-01") } })
.sort({ created_at: -1 })
.explain('executionStats');
La règle ESR pour les index composés
Les index composés (index construits sur plusieurs champs) doivent être ordonnés correctement pour être maximalement efficaces. Utilisez la règle ESR :
- Égalité (Equality) : Les champs utilisés pour les correspondances exactes viennent en premier.
- Tri (Sort) : Les champs utilisés pour le tri viennent généralement ensuite.
- Plage (Range) : Les champs utilisés pour les opérateurs de plage tels que
$gtet$ltviennent généralement en dernier.
Exemple de la règle ESR :
Requête : Trouver des produits par category (égalité), triés par price (tri), dans une plage de rating (plage).
// Structure d'index correcte basée sur ESR
db.products.createIndex({ category: 1, price: 1, rating: 1 })
Requêtes couvertes
Une requête couverte est une requête où l'ensemble des résultats—y compris le filtre de la requête et les champs demandés dans la projection—peut être satisfait entièrement par l'index. Cela signifie que MongoDB n'a pas besoin de récupérer les documents réels, réduisant considérablement les E/S et augmentant la vitesse.
Pour réaliser une requête couverte, chaque champ retourné doit faire partie de l'index. Le champ _id est implicitement inclus sauf s'il est explicitement exclu (_id: 0).
// L'index doit inclure tous les champs demandés (name, email)
db.users.createIndex({ name: 1, email: 1 });
// Requête couverte - ne retourne que les champs inclus dans l'index
db.users.find({ name: 'Alice' }, { email: 1, _id: 0 });
3. Optimisation des requêtes et efficacité de la récupération
Même avec un indexage parfait, des modèles de requêtes inefficaces peuvent encore gravement dégrader les performances.
Toujours utiliser la projection
La projection limite la quantité de données transférées sur le réseau et la mémoire consommée par l'exécuteur de requêtes. Ne sélectionnez jamais tous les champs ({}) si vous n'avez besoin que d'un sous-ensemble de données.
// Piège : Récupération de l'intégralité du document utilisateur volumineux
db.users.findOne({ email: '[email protected]' });
// Bonne pratique : Ne récupérer que les champs nécessaires
db.users.findOne({ email: '[email protected]' }, { username: 1, last_login: 1 });
Éviter les grandes opérations $skip (Pagination par clé)
Utiliser $skip pour une pagination profonde est très inefficace car MongoDB doit toujours analyser et jeter les documents ignorés. Lorsque vous traitez de grands ensembles de résultats, utilisez la pagination par clé (également connue sous le nom de pagination basée sur le curseur ou sans décalage).
Au lieu de sauter un numéro de page, filtrez en fonction de la dernière valeur indexée récupérée (par exemple, _id ou horodatage).
// Piège : Ralentit de manière exponentielle à mesure que la page augmente
db.logs.find().sort({ timestamp: -1 }).skip(50000).limit(50);
// Bonne pratique : Continue efficacement à partir du dernier _id
const lastId = '...id_from_previous_page...';
db.logs.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(50);
4. Pièges avancés dans les opérations et l'agrégation
Les opérations complexes comme les écritures et les transformations de données nécessitent des techniques d'optimisation spécialisées.
Optimisation des pipelines d'agrégation
Les pipelines d'agrégation sont puissants mais peuvent être gourmands en ressources. La règle de performance clé est de réduire la taille de l'ensemble de données le plus tôt possible.
Bonne pratique : Placer $match et $limit en amont
Placez les étapes $match (qui filtre les documents) et $limit (qui limite le nombre de documents traités) au tout début du pipeline. Cela garantit que les étapes ultérieures plus coûteuses comme $group, $sort ou $project opèrent sur le plus petit ensemble de données possible.
// Exemple de pipeline efficace
[
{ $match: { status: 'COMPLETE', date: { $gte: '2023-01-01' } } }, // Filtrer tôt (utiliser l'index)
{ $group: { _id: '$customer_id', total_spent: { $sum: '$amount' } } },
{ $sort: { total_spent: -1 } }
]
Gérer les préoccupations d'écriture
La préoccupation d'écriture dicte le niveau d'accusé de réception que MongoDB fournit pour une opération d'écriture. Choisir une préoccupation d'écriture trop stricte lorsque la durabilité élevée n'est pas strictement nécessaire peut gravement impacter la latence d'écriture.
| Paramètre de préoccupation d'écriture | Latence | Durabilité |
|---|---|---|
w: 1 |
Faible | Confirmé par le nœud primaire uniquement. |
w: 'majority' |
Élevée | Confirmé par la majorité des membres de l'ensemble de réplicas. Durabilité maximale. |
Conseil : Pour les opérations à haut débit non critiques (comme l'analyse ou la journalisation), envisagez d'utiliser une préoccupation d'écriture plus faible comme w: 1 pour privilégier la vitesse. Pour les transactions financières ou les données critiques, utilisez toujours w: majority.
5. Bonnes pratiques de déploiement et de configuration
Au-delà du schéma de base de données et des requêtes, les détails de configuration impactent la santé globale du système.
Surveiller les requêtes lentes
Vérifiez régulièrement le journal des requêtes lentes ou utilisez le pipeline d'agrégation $currentOp pour identifier les opérations qui prennent un temps excessif. Le profileur MongoDB est un outil essentiel pour cette tâche.
Gérer le regroupement de connexions
Assurez-vous que votre application utilise un pool de connexions efficace. Créer et détruire des connexions à la base de données est coûteux. Un pool de taille appropriée réduit la latence et les frais généraux. Définissez des tailles de pool de connexions minimales et maximales adaptées à vos modèles de trafic applicatif.
Utiliser les index Time-to-Live (TTL)
Pour les collections contenant des données transitoires (par exemple, sessions, entrées de journal, données en cache), implémentez des index TTL. Cela permet à MongoDB d'expirer automatiquement les documents après une période définie, empêchant les collections de croître de manière incontrôlée et de dégrader l'efficacité de l'indexation au fil du temps.
// Les documents de la collection session expireront 3600 secondes après leur création
db.session.createIndex({ created_at: 1 }, { expireAfterSeconds: 3600 })
Continuez à vérifier les plans de requête réels
Éviter les pièges de performance MongoDB consiste principalement à rester honnête avec l'optimiseur de requêtes. Gardez les documents ciblés, créez des index composés pour les modèles de requêtes réels, utilisez des projections, évitez les $skip profonds et vérifiez explain('executionStats') chaque fois qu'une requête devient importante pour l'application. À mesure que le trafic change, revisitez les plans au lieu de supposer que l'index d'hier est toujours le bon.