1 / 25

Optimisation avec les Index MongoDB

Performance et scalabilitĂ© de vos requĂȘtes

Module NoSQL - École d'IngĂ©nieurs

1 / 12

Pourquoi les index sont critiques ?

Sans Index

3.2s

Collection scan complet
1 million de documents

Avec Index

0.003s

AccĂšs direct
~1000x plus rapide

// Sans index : MongoDB examine TOUS les documents
db.products.find({ sku: "CAF-2024" })
// executionStats.totalDocsExamined: 1,000,000
// executionStats.executionTimeMillis: 3200

// Avec index sur 'sku'
db.products.createIndex({ sku: 1 })
// executionStats.totalDocsExamined: 1
// executionStats.executionTimeMillis: 3
Les index sont comme l'index d'un livre : ils permettent d'aller directement Ă  l'information sans parcourir toutes les pages.
2 / 12

Types d'index MongoDB

Type d'Index Usage Exemple
Single Field RequĂȘtes sur un champ unique { sku: 1 }
Compound RequĂȘtes sur plusieurs champs { category: 1, price: -1 }
Text Recherche textuelle { name: "text" }
2dsphere RequĂȘtes gĂ©ospatiales { location: "2dsphere" }

Index Unique

// Garantit l'unicité des valeurs
db.customers.createIndex(
    { email: 1 },
    { unique: true }
)

Index Partiel

// Index seulement les documents qui matchent le filtre
db.orders.createIndex(
    { customerId: 1 },
    { partialFilterExpression: { status: "active" } }
)
3 / 12

Index Composés : L'ordre compte !

// Création d'un index composé
db.orders.createIndex({ 
    status: 1, 
    customerId: 1, 
    createdAt: -1 
})

Cet index supporte ces requĂȘtes :

✅ Optimal find({ status: "pending", customerId: "CLT-10234" })
✅ Utilise le prĂ©fixe find({ status: "pending" }).sort({ customerId: 1 })
❌ N'utilise PAS l'index find({ customerId: "CLT-10234" })
RĂšgle du prĂ©fixe : MongoDB peut utiliser un index composĂ© seulement si la requĂȘte inclut les premiers champs de l'index dans l'ordre.

Stratégie ESR (Equality, Sort, Range)

  1. Equality : Champs testés avec des valeurs exactes
  2. Sort : Champs utilisés pour le tri
  3. Range : Champs avec des conditions de plage ($gt, $lt, etc.)
4 / 12

Analyser les performances avec explain()

// Analyser l'exĂ©cution d'une requĂȘte
db.products.find({ 
    category: "VĂȘtements",
    price: { $gte: 1000 }
}).explain("executionStats")

Métriques clés à surveiller

totalDocsExamined Documents scannés
totalKeysExamined Clés d'index examinées
executionTimeMillis Temps d'exécution (ms)
// Résultat explain() - SANS INDEX
{
    "winningPlan": {
        "stage": "COLLSCAN"  // ❌ Collection Scan
    },
    "executionStats": {
        "totalDocsExamined": 500000,
        "totalKeysExamined": 0,
        "executionTimeMillis": 2341
    }
}
Objectif : totalDocsExamined ≈ nReturned (nombre de documents retournĂ©s)
5 / 12

Stratégies d'indexation efficaces

1. Analyser les requĂȘtes frĂ©quentes

// Activer le profiling pour identifier les requĂȘtes lentes
db.setProfilingLevel(1, { slowms: 100 })

// Analyser les requĂȘtes lentes
db.system.profile.find({
    millis: { $gt: 100 }
}).sort({ ts: -1 }).limit(5)

2. Créer des index stratégiques

// Pour une boutique en ligne marocaine
// RequĂȘte frĂ©quente : produits par ville et catĂ©gorie
db.products.createIndex({ 
    "warehouse.city": 1, 
    category: 1, 
    price: 1 
})

// Support pour recherche textuelle en français/arabe
db.products.createIndex(
    { name: "text", description: "text" },
    { default_language: "french" }
)

3. Index de couverture (Covered Query)

// L'index contient TOUS les champs nécessaires
db.orders.createIndex({ 
    customerId: 1, 
    status: 1, 
    total: 1 
})

// Cette requĂȘte est entiĂšrement couverte par l'index
db.orders.find(
    { customerId: "CLT-10234", status: "pending" },
    { total: 1, _id: 0 }
)
6 / 12

Index géospatiaux pour la localisation

// Structure pour stocker les coordonnées
{
    "name": "Restaurant La Mamounia",
    "location": {
        "type": "Point",
        "coordinates": [-7.6192, 33.5731]  // [longitude, latitude]
    },
    "city": "Marrakech"
}

Création et utilisation

// Créer un index 2dsphere
db.restaurants.createIndex({ location: "2dsphere" })

// Trouver les restaurants dans un rayon de 2km
db.restaurants.find({
    location: {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [-7.6192, 33.5731]
            },
            $maxDistance: 2000  // en mĂštres
        }
    }
})
Les index 2dsphere supportent les requĂȘtes $near, $geoWithin, et $geoIntersects pour des calculs gĂ©ographiques prĂ©cis.

Cas d'usage : Livraison Ă  domicile

// Trouver les livreurs disponibles prĂšs d'une commande
db.delivery_drivers.find({
    status: "available",
    location: {
        $near: {
            $geometry: orderLocation,
            $maxDistance: 5000
        }
    }
}).limit(3)
7 / 12

Monitoring et maintenance des index

Visualiser tous les index

// Lister les index d'une collection
db.products.getIndexes()

// Taille des index
db.products.stats().indexSizes

Identifier les index inutilisés

// Statistiques d'utilisation des index (MongoDB 3.2+)
db.products.aggregate([
    { $indexStats: {} }
])

// Résultat
{
    "name": "category_1_price_1",
    "accesses": {
        "ops": 42341,      // Nombre d'utilisations
        "since": ISODate("2024-01-01")
    }
}

Rebuild d'index

// Reconstruire un index (maintenance)
db.products.reIndex()

// Supprimer un index inutilisé
db.products.dropIndex("old_index_name")
Ne jamais supprimer l'index _id ! Les index occupent de l'espace disque et ralentissent les écritures - gardez seulement ceux qui sont utiles.
8 / 12

Cas pratique : Plateforme e-commerce

Scénario : Souks.ma

Optimisons une plateforme de vente en ligne marocaine avec :

Index recommandés

// 1. Recherche produits (le plus fréquent)
db.products.createIndex({ 
    category: 1, 
    "stock.city": 1, 
    price: 1 
})

// 2. Recherche textuelle multilingue
db.products.createIndex(
    { name: "text", description: "text", tags: "text" },
    { 
        weights: { name: 10, tags: 5, description: 1 },
        default_language: "french"
    }
)

// 3. Commandes par client
db.orders.createIndex({ 
    customerId: 1, 
    createdAt: -1 
})

// 4. Produits populaires (avec TTL)
db.trending_products.createIndex(
    { lastViewed: 1 },
    { expireAfterSeconds: 604800 }  // 7 jours
)
RequĂȘte optimisĂ©e find({ category: "Artisanat", "stock.city": "FĂšs", price: { $lte: 500 } }) → Utilise l'index #1, temps: ~5ms
9 / 12

PiĂšges courants et solutions

❌ Piùge 1 : Trop d'index

// MAUVAIS : Un index par champ
db.products.createIndex({ name: 1 })
db.products.createIndex({ category: 1 })
db.products.createIndex({ price: 1 })
db.products.createIndex({ stock: 1 })

// MIEUX : Index composé stratégique
db.products.createIndex({ category: 1, price: 1, stock: 1 })

❌ PiĂšge 2 : Index sur des champs Ă  haute cardinalitĂ©

// ÉVITER : Index sur des champs avec peu de valeurs distinctes
db.users.createIndex({ isActive: 1 })  // Seulement true/false

// MIEUX : Utiliser un index partiel
db.users.createIndex(
    { lastLogin: -1 },
    { partialFilterExpression: { isActive: true } }
)

❌ Piùge 3 : Ignorer la direction du tri

// RequĂȘte frĂ©quente
db.orders.find().sort({ createdAt: -1, total: 1 })

// Index optimal (respecte les directions)
db.orders.createIndex({ createdAt: -1, total: 1 })
RÚgle d'or : Moins d'index bien conçus > Beaucoup d'index mal pensés
10 / 12

Best Practices MongoDB Index

✅ Checklist d'optimisation

Action Commande Fréquence
Profiler les requĂȘtes lentes db.setProfilingLevel(1, { slowms: 100 }) Continue
Analyser avec explain() .explain("executionStats") Avant production
Vérifier l'usage des index $indexStats Mensuel
Nettoyer les index inutilisés dropIndex() Trimestriel

📊 MĂ©triques de succĂšs

Objectifs

  • Ratio docs examined/returned < 10
  • Temps de requĂȘte < 100ms
  • Index selectivity > 0.95

🔧 Script de monitoring

// Script d'audit des performances
db.getMongo().getDBNames().forEach(dbName => {
    const db = db.getSiblingDB(dbName)
    db.getCollectionNames().forEach(coll => {
        const stats = db[coll].stats()
        if (stats.size > 1000000) {  // Collections > 1MB
            print(`${dbName}.${coll}: ${stats.indexSizes}`)
        }
    })
})
11 / 12

Points clés à retenir

1. Comprendre vos requĂȘtes Utilisez le profiler et explain() pour identifier les patterns d'accĂšs
2. StratĂ©gie ESR Equality → Sort → Range pour l'ordre des champs dans les index composĂ©s
3. Index de couverture Les meilleures performances : la requĂȘte ne touche que l'index
4. Maintenance réguliÚre Surveillez l'utilisation et supprimez les index obsolÚtes

🎯 Prochain chapitre

Aggregation Pipeline : Transformez et analysez vos données avec la puissance du framework d'agrégation MongoDB

12 / 12