Hai una collection MongoDB piena di documenti che contengono sia descrizioni testuali da cercare sia coordinate GPS da filtrare per distanza. Se hai creato un indice text separato e un indice 2dsphere separato, le query che combinano $text e $near partono lente o ti restituiscono errori di performance. Il problema è che MongoDB non usa due indici diversi nella stessa query: deve sceglierne uno e filtrare l'altro lato in memoria. La soluzione? Un indice compound che unisce text e geospatial in un unico indice.
Noi, di Meteora Web, abbiamo visto decine di progetti MongoDB dove un indice separato costringeva il database a scandire migliaia di documenti per soddisfare una semplice ricerca "locali aperti vicino a me che parlano di pizza". Con un indice compound text-geospatial la stessa query va in pochi millisecondi. In questa guida ti spieghiamo come crearli, quando usarli e quali limiti tenere a mente.
Perché un indice MongoDB compound text geospatial accelera le ricerche ibride?
Quando MongoDB esegue una query che combina $text e un operatore geospaziale (es. $near, $geoWithin), il motore di query deve decidere quale indice utilizzare per primo. Di default sceglie l'indice che stima più selettivo, ma l'altro campo viene filtrato dopo, senza indice. Il risultato: scansione completa di un secondo set di documenti, magari enorme.
Un indice compound text geospatial risolve il problema perché MongoDB può soddisfare entrambi i filtri esplorando un'unica struttura dati. L'indice combina un campo text index (con parole chiave, pesi e lingua) e un campo 2dsphere (coordinate). La query esegue una sola scansione index, riducendo drasticamente i documenti da esaminare.
Sponsored Protocol
Esempio pratico: supponiamo di avere una collection locali con campi nome (stringa indicizzata come text) e posizione (GeoJSON Point). Senza indice compound, una query tipo "trova locali entro 1 km da una coordinate che contengono la parola 'pizza'" deve scegliere: o usa l'indice text per trovare tutti i documenti con 'pizza' (magari centinaia) e poi filtra geograficamente, oppure usa l'indice geospaziale per trovare i locali vicini (migliaia) e poi filtra per testo. Con l'indice compound, MongoDB applica entrambi i filtri contemporaneamente nell'indice.
Come creare un indice compound text e geospatial su una collection esistente?
La sintassi è semplice, ma ci sono regole precise da rispettare. In un indice compound MongoDB, il tipo di indice text deve essere specificato come "text" e il campo geospaziale come "2dsphere". Non puoi invertire l'ordine a piacere: l'indice text deve essere il primo campo nell'ordine di definizione dell'indice se vuoi che il $text venga usato per primo (cosa quasi sempre desiderabile).
Ecco il comando per creare un indice compound su una collection locali:
db.locali.createIndex(
{ nome: "text", posizione: "2dsphere" },
{
name: "idx_text_geo",
default_language: "italian",
weights: { nome: 10 }
}
)Spiegazione:
Sponsored Protocol
- nome: "text" — dichiara che il campo
nomeviene indicizzato come text index (puoi anche specificare più campi text usandonomeedescrizioneseparati da virgola, ma uno di essi deve essere il primo dell'array). - posizione: "2dsphere" — indica che
posizioneè un campo GeoJSON (o legacy coordinate pair). - default_language: "italian" — per gestire stemming e stop word in italiano (fondamentale per query in italiano).
- weights — assegna un peso maggiore al campo
nomeper il ranking text.
Attenzione: MongoDB non permette di creare un indice compound con più campi text (es. nome: "text", descrizione: "text", posizione: "2dsphere")? Sì, è possibile, ma il primo campo deve essere un text index e gli altri text index vanno concatenati con virgola. Ecco l'esempio con due campi text:
db.locali.createIndex(
{ nome: "text", descrizione: "text", posizione: "2dsphere" },
{
name: "idx_nome_desc_geo",
default_language: "italian",
weights: { nome: 10, descrizione: 5 }
}
)Questa restrizione è importante: non puoi inserire un campo non text tra due text. L'indice compound text deve iniziare con uno o più campi text, seguiti dagli altri tipi (geospatial, ordinario, etc.).
Quali limiti ha un indice MongoDB compound text geospatial nella pratica?
Non tutto è rose e fiori. Ci sono limiti che devi conoscere per non incappare in sorprese:
Sponsored Protocol
- Non puoi usare
$textcon$nearsu un indice compound se l'ordine dei campi non è quello giusto. L'indice deve avere il campo text come primo nell'ordine di definizione per poter utilizzare$textnella query. Inoltre, la query deve includere obbligatoriamente una condizione$textaffinché l'indice venga usato. - Il geospatial index 2dsphere all'interno di un compound non supporta
$geoNearcome aggregazione stage separata.$geoNearrichiede un indice geospaziale standalone; non può essere eseguito su un indice compound. Per query aggregate con$geoNeardevi creare un indice 2dsphere separato. - Il campo text index non può essere specificato come first se vuoi fare una query solo geospaziale senza
$text. In tal caso l'indice compound non verrà usato; meglio avere un indice 2dsphere separato per coprire anche quel caso. - Dimensione dell'indice: un indice text occupa spazio, e se combinato con dati geospaziali può diventare molto grande. Monitora l'uso della RAM.
Noi, di Meteora Web, consigliamo di mantenere sempre un indice 2dsphere standalone per le query puramente geospaziali e un indice compound text-geospatial per le query ibride. Non esiste un indice che faccia tutto.
Come verificare la performance di un indice text-geospatial con explain()?
Non fidarti dell'intuito. Usa .explain("executionStats") per vedere se MongoDB sta usando l'indice compound e quanti documenti esamina. Ecco una query tipica:
Sponsored Protocol
db.locali.explain("executionStats").find(
{
$text: { $search: "pizza" },
posizione: {
$near: {
$geometry: { type: "Point", coordinates: [14.268, 40.8518] },
$maxDistance: 1000
}
}
}
)Nel risultato di explain(), controlla:
- winningPlan.inputStage.stage — deve essere
IXSCAN(index scan) sull'indiceidx_text_geo. - executionStats.totalDocsExamined — dovrebbe essere basso, idealmente uguale al numero di documenti restituiti (
nReturned). - executionStats.executionTimeMillis — sotto i 10ms per collection piccole/medie.
Se vedi COLLSCAN o FETCH con tanti documenti esaminati, l'indice non viene usato correttamente. Possibili cause: ordine dei campi errato, assenza di $text nella query, o indice non presente.
Cosa fare quando l'indice compound text geospatial non è sufficiente?
A volte, anche con l'indice giusto, la performance non è accettabile per volumi altissimi (milioni di documenti con molti termini text). In questi casi considera:
- Sharding: partiziona la collection su più nodi usando una chiave di shard che bilanci il carico. Attenzione: gli indici compound text-geospatial non possono essere usati come shard key direttamente.
- Pre-filtraggio geografico con
$geoWithinanziché$nearse non ti serve l'ordinamento per distanza.$geoWithinè più efficiente in combinazione con$text. - Cambiare modello dati: se il testo da cercare è limitato (es. categorie fisse), potresti usare un campo enumerato (con indice normale) invece di text index, e combinarlo con l'indice geospaziale in un compound regolare (non text).
- Utilizzare Atlas Search: se sei su MongoDB Atlas, il servizio di full-text search basato su Lucene è spesso più performante degli indici text nativi per query ibride.
In ogni caso, la regola d'oro è: misura sempre con explain() prima e dopo l'ottimizzazione.
Sponsored Protocol
Cosa fare adesso
- Controlla le tue collection: esegui
db.collection.getIndexes()e cerca indici compound che iniziano contexte contengono2dsphere. - Identifica le query lente: attiva il log delle query lente (slowms) e cerca query con
$textcombinato a$nearo$geoWithin. - Crea l'indice compound seguendo la sintassi sopra, verificando che la lingua di default sia quella giusta.
- Testa con explain() per confermare che l'indice venga usato e che i numeri siano soddisfacenti.
- Non eliminare l'indice geospaziale standalone: ti serve per le query solo-geo o per
$geoNearin aggregazione.
Noi, di Meteora Web, affrontiamo queste ottimizzazioni quotidianamente. Se hai una collection con dati reali e vuoi un parere, nella nostra guida pillar su MongoDB trovi approfondimenti su tutto il ciclo di vita del database. Per dubbi specifici, scrivici.