Module 3 : Indexation

Pourquoi les index ?

Les index améliorent les performances des requêtes :


+----------------------------------------------------------+
|                   SANS INDEX                              |
+----------------------------------------------------------+
|   Collection Scan (COLLSCAN)                             |
|   - Parcourt TOUS les documents                          |
|   - O(n) - Linéaire                                      |
|   - Lent sur grandes collections                         |
+----------------------------------------------------------+

+----------------------------------------------------------+
|                   AVEC INDEX                              |
+----------------------------------------------------------+
|   Index Scan (IXSCAN)                                    |
|   - Structure B-tree                                     |
|   - O(log n) - Logarithmique                            |
|   - Rapide même sur millions de docs                    |
+----------------------------------------------------------+
            

Index par défaut

// MongoDB cree automatiquement un index sur _id
{
    "v": 2,
    "key": { "_id": 1 },
    "name": "_id_"
}

// Voir les index d'une collection
db.users.getIndexes()

Types d'index

Index simple (Single Field)

// Créer un index sur un champ
db.users.createIndex({ email: 1 })      // Ascendant
db.users.createIndex({ age: -1 })       // Descendant

// Avec options
db.users.createIndex(
    { email: 1 },
    {
        unique: true,           // Valeurs uniques
        name: "idx_email",      // Nom personnalisé
        background: true        // Création en arrière-plan
    }
)

// Index sur champ imbriqué
db.users.createIndex({ "adresse.ville": 1 })

Index composé (Compound)

// Index sur plusieurs champs
db.users.createIndex({ ville: 1, age: -1 })

// Ordre des champs important !
// Cet index supporte:
// - find({ ville: "Paris" })
// - find({ ville: "Paris", age: 30 })
// - find({ ville: "Paris" }).sort({ age: -1 })

// Mais PAS:
// - find({ age: 30 })  // ville doit être en premier
Règle ESR (Equality, Sort, Range) :
  1. Equality - Champs avec égalité en premier
  2. Sort - Champs de tri ensuite
  3. Range - Champs avec range ($gt, $lt) en dernier
// Exemple ESR
// Requête: ville = "Paris", age > 25, tri par nom
db.users.createIndex({ ville: 1, nom: 1, age: 1 })
//                      E         S       R

Index multikey (tableaux)

// Index sur un champ tableau
db.users.createIndex({ competences: 1 })

// Document
{ nom: "Dupont", competences: ["Python", "MongoDB", "Azure"] }

// MongoDB crée une entrée d'index pour chaque élément
// Requête optimisée:
db.users.find({ competences: "Python" })

Index texte (Full-text search)

// Créer un index texte
db.articles.createIndex({ titre: "text", contenu: "text" })

// Avec poids
db.articles.createIndex(
    { titre: "text", contenu: "text" },
    { weights: { titre: 10, contenu: 1 } }
)

// Recherche texte
db.articles.find({ $text: { $search: "mongodb database" } })

// Avec score de pertinence
db.articles.find(
    { $text: { $search: "mongodb" } },
    { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

// Recherche exacte (phrase)
db.articles.find({ $text: { $search: "\"base de donnees\"" } })

// Exclure un mot
db.articles.find({ $text: { $search: "mongodb -sql" } })

Index geospatial

// Index 2dsphere (coordonnées GPS)
db.locations.createIndex({ position: "2dsphere" })

// Document avec GeoJSON
{
    nom: "Tour Eiffel",
    position: {
        type: "Point",
        coordinates: [2.2945, 48.8584]  // [longitude, latitude]
    }
}

// Recherche à proximité
db.locations.find({
    position: {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [2.3522, 48.8566]  // Paris centre
            },
            $maxDistance: 5000  // 5km
        }
    }
})

// Recherche dans une zone
db.locations.find({
    position: {
        $geoWithin: {
            $centerSphere: [[2.3522, 48.8566], 10 / 6378.1]  // 10km
        }
    }
})

Index TTL (Time-To-Live)

// Suppression automatique après un délai
db.sessions.createIndex(
    { createdAt: 1 },
    { expireAfterSeconds: 3600 }  // 1 heure
)

// Document
{
    sessionId: "abc123",
    userId: ObjectId("..."),
    createdAt: new Date()  // Doit être une date
}

// Le document sera supprimé 1h après createdAt

Index partiel

// Index sur un sous-ensemble de documents
db.users.createIndex(
    { email: 1 },
    {
        partialFilterExpression: {
            actif: true
        }
    }
)

// Index utilisé seulement pour:
db.users.find({ email: "...", actif: true })

// Pas pour:
db.users.find({ email: "..." })  // actif non spécifié

Index sparse

// Index uniquement sur documents ou le champ existe
db.users.createIndex(
    { telephone: 1 },
    { sparse: true }
)

// Documents sans "telephone" ne sont pas indexés

Analyser les performances

// explain() - Plan d'exécution
db.users.find({ ville: "Paris" }).explain()
db.users.find({ ville: "Paris" }).explain("executionStats")

// Résultats clés
{
    "queryPlanner": {
        "winningPlan": {
            "stage": "IXSCAN",        // Bon! (vs COLLSCAN)
            "indexName": "ville_1"
        }
    },
    "executionStats": {
        "totalDocsExamined": 50,      // Documents examinés
        "totalKeysExamined": 50,      // Entrées d'index
        "executionTimeMillis": 2       // Temps d'exécution
    }
}

// Objectif: totalDocsExamined proche de nReturned

Gestion des index

// Lister les index
db.users.getIndexes()

// Statistiques d'utilisation
db.users.aggregate([{ $indexStats: {} }])

// Supprimer un index
db.users.dropIndex("email_1")
db.users.dropIndex({ email: 1 })

// Supprimer tous les index (sauf _id)
db.users.dropIndexes()

// Reconstruire les index
db.users.reIndex()

Covered Queries

Covered Query = requête où tous les champs demandés sont dans l'index. MongoDB n'a pas besoin de lire les documents.
// Index
db.users.createIndex({ email: 1, nom: 1 })

// Covered query (projection = champs de l'index)
db.users.find(
    { email: "dupont@test.com" },
    { _id: 0, email: 1, nom: 1 }
)

// explain montre:
// "totalDocsExamined": 0  // Aucun document lu!

Bonnes pratiques

  • Indexer les champs fréquemment requêtés
  • Suivre la règle ESR pour les index composés
  • Éviter trop d'index (ralentit les écritures)
  • Utiliser explain() pour valider
  • Surveiller les index inutilisés
  • Préférer les index partiels si applicable
À éviter :
  • Index sur champs à forte cardinalité (ObjectId, timestamps) sauf si nécessaire
  • Trop d'index composés avec de nombreux champs
  • Index multikey sur plusieurs tableaux dans le même document