Corso
Con qualsiasi database, MongoDB incluso, gli indici sono una parte fondamentale del processo di sviluppo. Senza di essi, man mano che il database cresce, anche le prestazioni continueranno a diminuire. Con indici validi, all’aumentare dei dati, le prestazioni di accesso rimarranno ottimali.
In questo articolo esploreremo i vari tipi di indici disponibili in MongoDB, alcuni consigli pratici per ottenere la migliore esperienza e istruzioni per assicurarti di sfruttare davvero gli indici che crei. Se stai ancora imparando MongoDB, ti consiglio di dare un’occhiata al corso Introduction to MongoDB.
Dove sono importanti gli indici in MongoDB e perché?
Quando si parla di indici, si parla di prestazioni. Gli indici fanno la differenza tra guardare ogni documento di una collezione per vedere se corrisponde alla query, oppure un sottoinsieme più specifico di documenti che la soddisfano. Se la collezione è piccola e non ci sono molti documenti da esaminare, le prestazioni saranno buone in entrambi i casi. Ma se abbiamo 100.000 documenti in una collezione e 100 corrispondono alla query, ci vorrà del tempo per analizzare tutti i 100.000 documenti per trovare quei 100, se non esiste un indice basato su quei 100 documenti.
Esistono numerosi tipi di indici in MongoDB:
- Singolo campo: Un indice su un campo specifico all’interno del documento. L’indice predefinito del campo
_idpuò essere considerato un indice a singolo campo. - Composto: Un indice basato su più campi di un documento, in cui l’ordine dei campi usato nella query e nell’indice è importante.
- Multikey: Un indice usato su campi array.
- Wildcard: Un indice che supporta campi che possono avere nomi leggermente diversi tra documenti. Questo indice non avrà le stesse prestazioni di altri tipi di indice.
- Geospaziale: Un indice per migliorare le prestazioni delle query geospaziali che usano coordinate di latitudine e longitudine.
- Hashed: Un indice che dà il meglio con le shard key.
Per maggiori informazioni sui vari tipi di indice, consulta la documentazione di MongoDB.
Nella maggior parte dei casi utilizzerai indici a singolo campo, composti o multikey, ma alla fine dipende davvero dal tipo di applicazione, dalle query e dai dati.
Gli indici sono importanti in qualsiasi interazione con il database che recupera documenti. Questo non si limita al recupero di documenti usando un’operazione find o findOne. Anche quando aggiorni ed elimini documenti, utilizzi criteri di corrispondenza che il database userà per trovare i documenti prima di eseguire l’update o il delete.
Gli operatori find e findOne non sono gli unici operatori di recupero che possono beneficiare di un indice. Quando usi l’operatore $match come primo stage della pipeline di aggregazione, un indice velocizzerà drasticamente le cose.
Consigli e trucchi per l’indicizzazione in MongoDB
Creare indici a caso può essere inefficiente quanto non usare indici in MongoDB. Invece, ci sono alcuni aspetti da considerare che ti aiuteranno a costruire un’applicazione molto più efficiente.
Equality, sort, range (ESR) per indici composti
La regola generale quando si crea un indice composto è determinare come verranno usati i campi nella query e assicurarsi che seguano la best practice ESR.
Cosa significa ESR? Vediamolo nel dettaglio:
- Equality: Campi che saranno confrontati in modo esatto, non approssimativo. Ad esempio, cercare il nome esatto di una persona nella query corrisponde a uno stage di eguaglianza.
- Sort: Campi che userai per ordinare in ordine crescente o decrescente.
- Range: Campi su cui farai corrispondenza senza un match esatto. Ad esempio, usare gli operatori
$lto$gtfarà combaciare documenti entro un intervallo, ma non uno specifico valore.
Per capire meglio cosa significa tutto questo, guarda la seguente query:
db.employee.find(
{
"department": "Developer Relations",
"salary": {
"$lt": 80000
}
}
).sort(
{
"name": 1
}
);
La query qui sopra fa un match di eguaglianza su department, un match di intervallo su salary e ordina i risultati per name.
Seguendo le regole ESR, l’indice dovrebbe essere qualcosa del genere:
db.employee.createIndex({
"department": 1,
"name": 1,
"salary": 1
});
Vogliamo seguire le regole ESR per evitare ordinamenti in memoria e ridurre lo spazio di ricerca per un’esecuzione efficiente della query.
Query coperte
L’indicizzazione in generale crea un sottoinsieme più piccolo di documenti da scansionare quando esegui una query e, di conseguenza, migliora le prestazioni. Ma se vuoi portare le prestazioni al livello successivo, dovresti esplorare le query coperte. Con una query coperta, ottieni tutti i risultati direttamente dall’indice, senza dover scansionare alcun documento nella collezione.
Ci sono però alcune condizioni che possono rendere le query coperte possibili o meno nella tua applicazione:
- I criteri di match e l’ordinamento devono corrispondere esattamente a ciò su cui è stato creato l’indice.
- Anche i campi richiesti nel result set devono corrispondere all’indice.
- In caso di sharding, la shard key deve far parte dell’indice.
Cosa significa tutto questo, in pratica?
Prendi ad esempio il seguente indice:
db.department.createIndex({
"country": 1,
"salary": 1
});
Nell’esempio sopra, vogliamo indicizzare i documenti per il campo country e il campo salary. Possiamo quindi eseguire la seguente query:
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1
}
);
La query qui sopra, che include una proiezione, userà il nostro indice, ma non sarà una query coperta. Il campo _id viene restituito per impostazione predefinita in MongoDB e in questo esempio non è stato aggiunto al nostro indice. Possiamo quindi cambiare l’indice o modificare la query. Nel caso della query, possiamo fare così:
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1,
"_id": 0
}
);
Ricorda: se vuoi usare una query coperta, l’indice deve essere valido e i campi richiesti devono essere inclusi nell’indice. In caso contrario, stai solo trovando documenti gestiti dall’indice, non nell’indice stesso.
Puoi verificare se viene utilizzata una query coperta usando l’argomento executionStats nell’operatore explain citato più avanti in questo articolo. Il risultato totalDocsExamined lo indicherà: se il valore è zero, stai usando una query coperta.
Ridurre o eliminare indici non necessari
Gli indici non sono gratuiti in termini di risorse server. Ogni indice deve essere mantenuto dal server e questa manutenzione usa CPU, RAM e disco. Man mano che crei più indici, aumenti l’overhead, che può tradursi in costi più alti o prestazioni ridotte nel caso di un server sottodimensionato. Queste prestazioni ridotte possono riguardare anche scritture ed eliminazioni, non solo letture, perché MongoDB consuma più risorse.
È una buona idea creare indici solo per le query ad alta frequenza. Quella query che viene eseguita una volta al mese ha davvero bisogno di un indice? Probabilmente no. Quella query che viene eseguita ogni 10 secondi ha bisogno di un indice? Assolutamente sì!
Scopri altre best practice per l’indicizzazione sul blog di MongoDB.
Il tuo indice MongoDB sta funzionando?
Il solo fatto di aver creato un indice in MongoDB non significa che la query lo stia effettivamente usando. In MongoDB possiamo diagnosticare quale indice viene usato per una determinata query.
Prendiamo ad esempio il seguente indice:
db.people.createIndex({
"name": 1,
"is_active": 1
});
Nell’esempio sopra, stiamo indicizzando sul campo name e sul campo is_active. Ora vogliamo eseguire la seguente query:
db.people.find({
"is_active": true
});
La query qui sopra verrà eseguita con o senza un indice valido e, se il dataset è piccolo, sarà veloce comunque. Tuttavia, se volessimo vedere se sta usando il nostro indice, possiamo modificare la query così:
db.people.find({
"is_active": true
}).explain();
Questa volta usiamo il metodo explain per ottenere il piano di esecuzione della query. Nel piano di esecuzione possiamo individuare quanto segue:
winningPlan: {
isCached: false,
stage: 'COLLSCAN',
filter: { is_active: { '$eq': true } },
direction: 'forward'
}
Per questa query specifica, MongoDB ha usato un "COLLSCAN", il che significa che non ha utilizzato il nostro indice anche se includeva uno dei nostri campi. Se cambiamo la query in qualcosa del genere:
db.people.find({
"name": "Nic Raboy",
"is_active": true
}).explain();
Il risultato del nostro explain è cambiato:
winningPlan: {
isCached: false,
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { name: 1, is_active: 1 },
indexName: 'name_1_is_active_1',
isMultiKey: false,
multiKeyPaths: { name: [], is_active: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: {
name: [ '["Nic Raboy", "Nic Raboy"]' ],
is_active: [ '[true, true]' ]
}
}
},
Questa volta il piano di esecuzione indica che è stato usato un "IXSCAN" e specifica il nome dell’indice utilizzato, che in questo caso è name_1_is_active_1. Il nome è stato definito automaticamente quando abbiamo creato l’indice.
Per un’operazione di update, il processo è simile, con l’eccezione di dove si posiziona la funzione explain.
Un’operazione di update potrebbe essere simile alla seguente:
db.people.explain().update(
{
“name”: “Nic Raboy”,
“Is_active”: true
},
{
“$set”: { “is_active”: false }
}
);
Nota che nell’esempio sopra, explain viene chiamato prima della funzione update.
Se stai usando l’indice previsto nell’operazione di update, vedrai il valore "IXSCAN" insieme al nome dell’indice. Trova maggiori informazioni sulle operazioni di update e l’uso degli indici.
Un metodo alternativo per ottenere i dettagli del piano di esecuzione della query è usare runCommand sul database. Anche se meno elegante da leggere, è molto potente e potrebbe apparire così:
db.runCommand({
"explain": {
"update": "people",
"updates": [
{
"q": {
"name": "Nic Raboy",
"is_active": true
},
"u": {
"$set": { "is_active": false }
}
}
]
},
"verbosity": "allPlansExecution"
});
Quanto sopra funziona allo stesso modo dell’operazione di update con la funzione explain.
Se non ti senti sicuro a creare indici, o noti che le tue query sono più lente del previsto, valuta di esaminare i dettagli di esecuzione.
Conclusione
Hai appena visto alcune best practice sull’indicizzazione in MongoDB. Anche se non abbiamo esplorato ogni possibile scenario, puoi usare questo come punto di partenza per garantire ai tuoi utenti la migliore esperienza possibile nell’utilizzo dei tuoi dati.
Per ribadire alcuni punti:
- Segui la regola equality, sort, range (ESR) quando possibile.
- Se puoi, prova a usare una query coperta.
- Non esagerare creando indici: individua invece quali query saranno usate di più e quanta mole di dati recuperano.
- Fai affidamento sull’operatore
explainper testare i tuoi indici e le loro prestazioni.
Quanto sopra non è un elenco esaustivo di best practice sull’indicizzazione per migliorare le prestazioni dell’applicazione. Ci sono altri fattori, come le quantità di cardinalità.
Per ulteriore assistenza sull’indicizzazione, consulta la documentazione di MongoDB. Se usi un tier a pagamento in MongoDB Atlas, valuta l’uso degli strumenti di performance adviser integrati per raccomandazioni su indici e query.
MongoDB: domande frequenti sull’indicizzazione
Quanti indici dovrei creare in una collezione MongoDB?
Dipende. Devi trovare un equilibrio che funzioni per te, ma considera di indicizzare solo le query eseguite frequentemente rispetto a quelle occasionali.
Ho creato alcuni indici, ma le mie query sono ancora lente. Perché?
Sei sicuro che le tue query stiano davvero usando gli indici che hai creato? Usa l’operatore explain o alcuni degli strumenti di advisor in MongoDB Atlas per la diagnosi.
Per la regola ESR, l’ordine dei campi dell’indice deve essere esattamente eguaglianza, poi ordinamento, poi intervallo?
Sebbene MongoDB a volte possa usare un indice fuori ordine, è più efficace quando l’ordine delle chiavi dell’indice corrisponde alla sequenza della query: prima i filtri di eguaglianza, poi gli ordinamenti e infine gli intervalli.
Quali sono le diverse modalità di explain?
Per impostazione predefinita, explain() viene eseguito in modalità “queryPlanner”, ma se vuoi modalità diverse, esistono anche “executionStats” e “allPlansExecution”.

Nic Raboy è un Responsabile Developer Relations presso MongoDB, dove guida un team di sviluppatori Python, Java, C# e PHP che creano contenuti straordinari per aiutare gli sviluppatori a integrare con successo MongoDB nei loro progetti. Ha esperienza con Golang e JavaScript e spesso scrive delle sue molte avventure nello sviluppo.

