Meteora Web

Indici MongoDB Composti Text e Geospaziali — Performance Reale per Query Miste

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...

Ing. Calogero Bono 6 min di lettura Read in English
Indici MongoDB Composti Text e Geospaziali — Performance Reale per Query Miste

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.

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:

  • nome: "text" — dichiara che il campo nome viene indicizzato come text index (puoi anche specificare più campi text usando nome e descrizione separati 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 nome per 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:

  • Non puoi usare $text con $near su 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 $text nella query. Inoltre, la query deve includere obbligatoriamente una condizione $text affinché l'indice venga usato.
  • Il geospatial index 2dsphere all'interno di un compound non supporta $geoNear come aggregazione stage separata. $geoNear richiede un indice geospaziale standalone; non può essere eseguito su un indice compound. Per query aggregate con $geoNear devi 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:

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'indice idx_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 $geoWithin anziché $near se 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.

Cosa fare adesso

  1. Controlla le tue collection: esegui db.collection.getIndexes() e cerca indici compound che iniziano con text e contengono 2dsphere.
  2. Identifica le query lente: attiva il log delle query lente (slowms) e cerca query con $text combinato a $near o $geoWithin.
  3. Crea l'indice compound seguendo la sintassi sopra, verificando che la lingua di default sia quella giusta.
  4. Testa con explain() per confermare che l'indice venga usato e che i numeri siano soddisfacenti.
  5. Non eliminare l'indice geospaziale standalone: ti serve per le query solo-geo o per $geoNear in 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.

Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith → Demo gratis · senza carta

Leggi anche

Dal journal

Ultime guide