Ricerca full-text in MongoDB

MongoDB, uno dei principali database NoSQL, è ben noto per le sue prestazioni veloci, lo schema flessibile, la scalabilità e le ottime capacità di indicizzazione. Al centro di questa rapida performance si trovano gli indici MongoDB, che supportano l'esecuzione efficiente delle query evitando scansioni full-collection e quindi limitando il numero di documenti ricerche MongoDB. 

A partire dalla versione 2.4, MongoDB ha iniziato con una funzione sperimentale di supporto Ricerca full-text utilizzando Indici di testo. Questa funzionalità è ora diventata parte integrante del prodotto (e non è più una funzionalità sperimentale). In questo articolo esploreremo le funzionalità di ricerca full-text di MongoDB direttamente dai fondamenti.

Se sei nuovo in MongoDB, ti consiglio di leggere i seguenti articoli su Envato Tuts + che ti aiuteranno a capire i concetti di base di MongoDB:

  • Iniziare con MongoDB - Parte 1
  • Mappatura di database relazionali e SQL a MongoDB 

Le basi

Prima di entrare nei dettagli, diamo un'occhiata ad alcuni retroscena. La ricerca full-text si riferisce alla tecnica di ricerca a database full-text contro i criteri di ricerca specificati dall'utente. È qualcosa di simile al modo in cui cerchiamo qualsiasi contenuto su Google (o in realtà qualsiasi altra applicazione di ricerca) inserendo determinate parole chiave / frasi e recuperando i risultati pertinenti ordinati in base alla loro posizione.

Ecco alcuni scenari in cui verrebbe visualizzata una ricerca full-text:

  • Considera la ricerca del tuo argomento preferito su Wiki. Quando inserisci un testo di ricerca su Wiki, il motore di ricerca mostra i risultati di tutti gli articoli relativi alle parole chiave / frase che hai cercato (anche se quelle parole chiave sono state utilizzate in profondità nell'articolo). Questi risultati di ricerca sono ordinati per rilevanza in base al punteggio corrispondente.  
  • Come altro esempio, si consideri un sito di social networking in cui l'utente può effettuare una ricerca per trovare tutti i post che contengono la parola chiave gattiin loro; o per essere più complesso, tutti i post che contengono commenti contenenti la parola gatti.  

Prima di andare avanti, ci sono alcuni termini generali relativi alla ricerca full-text che dovresti sapere. Questi termini sono applicabili a qualsiasi implementazione di ricerca full-text (e non specifica per MongoDB).

Ferma parole

Le parole di stop sono le parole irrilevanti che dovrebbero essere filtrate da un testo. Ad esempio: a, an, the, is, at, which, ecc.

Stemming

Staminare è il processo di riduzione delle parole alla loro radice. Ad esempio: parole come in piedi, in piedi, in piedi, ecc. Hanno una base comune.

punteggio

Una classifica relativa per misurare quale dei risultati della ricerca è più rilevante.  

Alternative alla ricerca full-text in MongoDB

Prima che MongoDB arrivasse con il concetto di indici di testo, modellavamo i nostri dati per supportare le ricerche di parole chiave o usiamo espressioni regolari per implementare tali funzionalità di ricerca. Tuttavia, l'utilizzo di uno di questi approcci ha i suoi limiti:

  • Innanzitutto, nessuno di questi approcci supporta funzionalità come lo stemming, le parole di stop, il posizionamento, ecc.  
  • L'utilizzo delle ricerche per parole chiave richiederebbe la creazione di indici multi-chiave, che non sono sufficienti rispetto al testo completo.
  • L'utilizzo di espressioni regolari non è efficiente dal punto di vista delle prestazioni, poiché queste espressioni non utilizzano in modo efficace gli indici.
  • Inoltre, nessuna di queste tecniche può essere utilizzata per eseguire ricerche di frasi (come la ricerca di "film pubblicati nel 2015") o ricerche ponderate.  

Oltre a questi approcci, per applicazioni di ricerca più complesse e avanzate, esistono soluzioni alternative come Elastic Search o SOLR. Ma l'utilizzo di una di queste soluzioni aumenta la complessità architettonica dell'applicazione, dal momento che MongoDB deve ora parlare con un database esterno aggiuntivo. 

Si noti che la ricerca full-text di MongoDB non viene proposta come una sostituzione completa dei database dei motori di ricerca come Elastic, SOLR, ecc. Tuttavia, può essere efficacemente utilizzata per la maggior parte delle applicazioni che sono state costruite con MongoDB oggi.

Presentazione della ricerca di testo MongoDB

Utilizzando la ricerca full text di MongoDB, è possibile definire un indice di testo su qualsiasi campo nel documento il cui valore è una stringa o un array di stringhe. Quando creiamo un indice di testo su un campo, MongoDB tokenizza e blocca il contenuto del testo del campo indicizzato e imposta gli indici di conseguenza.  

Per comprendere meglio le cose, passiamo ora ad alcune cose pratiche. Voglio che seguiate il tutorial con me provando gli esempi nella shell mongo. Per prima cosa creeremo alcuni dati campione che utilizzeremo in tutto l'articolo, quindi passeremo a discutere concetti chiave.

Ai fini di questo articolo, prendere in considerazione una raccolta messaggi che memorizza i documenti della seguente struttura: 

"oggetto": "Joe possiede un cane", "contenuto": "I cani sono il migliore amico dell'uomo", "Mi piace": 60, "anno": 2015, "lingua": "inglese"

Cerchiamo di inserire alcuni documenti di esempio usando il inserire comando per creare i nostri dati di test:

db.messages.insert ("oggetto": "Joe possiede un cane", "contenuto": "I cani sono il migliore amico dell'uomo", "Mi piace": 60, "anno": 2015, "lingua": "inglese" ) db.messages.insert ("subject": "I cani mangiano gatti e il cane mangia anche i piccioni", "contenuto": "I gatti non sono cattivi", "Mi piace": 30, "anno": 2015, "lingua": "inglese") db.messages.insert ("oggetto": "I gatti mangiano i ratti", "contenuto": "I ratti non cucinano cibo", "Mi piace": 55, "anno": 2014, "lingua": "inglese") db.messages.insert ("subject": "Rats eat Joe", "content": "Joe ha mangiato un topo", "likes": 75, "year": 2014, "language": " Inglese")

Creazione di un indice di testo

Un indice di testo è creato in modo abbastanza simile al modo in cui creiamo un indice regolare, tranne per il fatto che specifica il testo parola chiave invece di specificare un ordine crescente / decrescente.

Indicizzazione di un singolo campo

Crea un indice di testo sul soggetto campo del nostro documento utilizzando la seguente query:

db.messages.createIndex ( "soggetto": "text")

Per testare questo indice di testo appena creato sul soggetto campo, cercheremo i documenti usando il $ testo operatore. Cercheremo tutti i documenti che contengono la parola chiave cani nel loro soggetto campo. 

Poiché stiamo eseguendo una ricerca di testo, siamo anche interessati a ottenere alcune statistiche su quanto siano pertinenti i documenti risultanti. Per questo scopo, useremo il $ Meta: "textScore" espressione, che fornisce informazioni sull'elaborazione del file $ testo operatore. Ordineremo anche i documenti con i loro textScore usando il ordinare comando. Un più alto textScore indica una corrispondenza più rilevante. 

db.messages.find ($ text: $ search: "dogs", score: $ meta: "toextScore"). sort (score: $ meta: "textScore")

La query precedente restituisce i seguenti documenti contenenti la parola chiave cani nel loro soggetto campo. 

"_id": ObjectId ("55f4a5d9b592880356441e94"), "subject": "I cani mangiano gatti e il cane mangia anche i piccioni", "contenuto": "I gatti non sono cattivi", "Mi piace": 30, "anno": 2015, "lingua": "inglese", "punteggio": 1 "_id": ObjectId ("55f4a5d9b592880356441e93"), "oggetto": "Joe possiede un cane", "contenuto": "I cani sono i migliori amici dell'uomo", " likes ": 60," year ": 2015," language ":" inglese "," score ": 0.6666666666666666

Come puoi vedere, il primo documento ha un punteggio di 1 (dalla parola chiave cane appare due volte nella sua materia) rispetto al secondo documento con un punteggio di 0,66. La query ha anche ordinato i documenti restituiti in ordine decrescente del loro punteggio.

Una domanda che potrebbe sorgere nella tua mente è che se stiamo cercando la parola chiave cani, perché il motore di ricerca sta prendendo la parola chiave cane (senza "s") in considerazione? Ricorda la nostra discussione sulla derivazione, dove le parole chiave di ricerca sono ridotte alla loro base? Questo è il motivo per cui la parola chiave cani è ridotto a cane.

Indicizzazione di più campi (indicizzazione composta)

Più spesso, userete la ricerca testuale su più campi di un documento. Nel nostro esempio, abiliteremo l'indicizzazione del testo composto su soggetto e soddisfare campi. Vai avanti ed esegui il seguente comando in mongo shell:  

db.messages.createIndex ( "soggetto": "testo", "content": "text")

Questo lavoro? No!! La creazione di un secondo indice di testo ti darà un messaggio di errore che dice che esiste già un indice di ricerca full-text. Perché è così? La risposta è che gli indici di testo hanno una limitazione di un solo indice di testo per collezione. Quindi se si desidera creare un altro indice di testo, sarà necessario eliminare quello esistente e ricreare quello nuovo. 

db.messages.dropIndex ("subject_text") db.messages.createIndex ("oggetto": "testo", "contenuto": "testo") 

Dopo aver eseguito le query di creazione dell'indice sopra, prova a cercare tutti i documenti con la parola chiave gatto.

db.messages.find ($ text: $ search: "cat", score: $ meta: "textScore"). sort (score: $ meta: "textScore")

La query precedente produrrebbe i seguenti documenti:

"_id": ObjectId ("55f4af22b592880356441ea4"), "subject": "I cani mangiano gatti e il cane mangia anche i piccioni", "contenuto": "I gatti non sono cattivi", "Mi piace": 30, "anno": 2015, "lingua": "inglese", "punteggio": 1.3333333333333335 "_id": ObjectId ("55f4af22b592880356441ea5"), "oggetto": "I gatti mangiano ratti", "contenuto": "I ratti non cucinano cibo", "Mi piace" ": 55," anno ": 2014," lingua ":" inglese "," punteggio ": 0.6666666666666666 

Puoi vedere che il punteggio del primo documento, che contiene la parola chiave gatto in entrambe soggetto e soddisfare campi, è più alto. 

Indicizzazione dell'intero documento (indicizzazione con caratteri jolly)

Nell'ultimo esempio, abbiamo inserito un indice combinato su soggetto e soddisfare campi. Ma ci possono essere degli scenari in cui desideri che qualsiasi contenuto di testo nei tuoi documenti sia ricercabile. 

Ad esempio, considera la possibilità di memorizzare le email nei documenti MongoDB. Nel caso di e-mail, tutti i campi, incluso Mittente, Destinatario, Oggetto e Corpo, devono essere ricercabili. In tali scenari è possibile indicizzare tutti i campi stringa del documento utilizzando il $ ** identificatore di caratteri jolly.

La query dovrebbe essere simile a questa (assicurati di eliminare l'indice esistente prima di crearne uno nuovo):

db.messages.createIndex ( "$ **": "text")

Questa query imposta automaticamente gli indici di testo su tutti i campi stringa nei nostri documenti. Per testare questo, inserisci un nuovo documento con un nuovo campo Posizione dentro:

db.messages.insert ("subject": "Birds can cook", "content": "Birds do not eat rats", "likes": 12, "year": 2013, location: "Chicago", "language" :"Inglese")

Ora se provi a cercare testo con parole chiave Chicago (query sotto), restituirà il documento che abbiamo appena inserito.

db.messages.find ($ text: $ search: "chicago", score: $ meta: "textScore"). sort (score: $ meta: "textScore")

Alcune cose su cui vorrei soffermarmi qui:

  • Si osservi che non abbiamo definito esplicitamente un indice sul Posizione campo dopo aver inserito un nuovo documento. Questo perché abbiamo già definito un indice di testo sull'intero documento usando il $ ** operatore.
  • Gli indici jolly possono essere lenti a volte, soprattutto in scenari in cui i dati sono molto grandi. Per questo motivo, pianifica saggiamente gli indici dei documenti (noti anche come indici jolly), in quanto possono causare un calo di prestazioni.

Ricerca avanzata

Ricerca per frase

Puoi cercare frasi come "uccelli intelligenti che amano cucinare"usando gli indici di testo. Per impostazione predefinita, la ricerca frase fa un O cerca su tutte le parole chiave specificate, cioè cercherà i documenti che contengono le parole chiave inteligente, uccello, amore o cucinare.

db.messages.find ($ text: $ search: "uccelli intelligenti che cucinano", punteggio: $ meta: "testo Punteggio"). ordina (punteggio: $ meta: "testo punteggio ")

Questa query produrrebbe i seguenti documenti:

"_id": ObjectId ("55f5289cb592880356441ead"), "subject": "Birds can cook", "content": "Birds do not eat rats", "likes": 12, "year": 2013, "location": "Chicago", "lingua": "inglese", "punteggio": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "oggetto": "I gatti mangiano ratti", "contenuto": "I ratti non cucinano cibo "," likes ": 55," anno ": 2014," lingua ":" inglese "," punteggio ": 0.6666666666666666 

Nel caso in cui si desideri eseguire una ricerca di frase esatta (logica E), puoi farlo specificando le virgolette doppie nel testo di ricerca. 

db.messages.find ($ text: $ search: "\" cook food \ "", score: $ meta: "textScore"). sort (score: $ meta: "textScore ")

Questa query risulterebbe nel seguente documento, che contiene la frase "cucinare cibo" insieme:

"_id": ObjectId ("55f5289bb592880356441eab"), "oggetto": "I gatti mangiano ratti", "contenuto": "I ratti non cucinano cibo", "Mi piace": 55, "anno": 2014, "lingua": "inglese", "punteggio": 0.6666666666666666

Ricerca di negazione

Prefixing di una parola chiave di ricerca con - (segno meno) esclude tutti i documenti che contengono il termine negato. Ad esempio, prova a cercare qualsiasi documento che contenga la parola chiave ratto ma non contiene uccelli utilizzando la seguente query:

db.messages.find ($ text: $ search: "rat -birds", score: $ meta: "textScore"). sort (score: $ meta: "textScore" )

Guardando dietro le quinte

Un'importante funzionalità che non ho rivelato finora è il modo in cui guardi dietro le quinte e vedi come vengono bloccate le tue parole chiave di ricerca, interrompi la dicitura applicata, negata, ecc.. $ spiegare Al salvataggio. È possibile eseguire la query query passando vero come parametro, che fornirà statistiche dettagliate sull'esecuzione della query.  

db.messages.find ($ text: $ search: "cani che i gatti non mangiano mangiano ratti \" cani mangiano \ "-friends", punteggio: $ meta: "textScore"). sort ( punteggio: $ meta: "textScore".) spiegare (vero) 

Se guardi il queryPlanner oggetto restituito dal comando spiega, sarete in grado di vedere come MongoDB ha analizzato la stringa di ricerca fornita. Osservare che ha trascurato parole di arresto come chi, e derivava cani a cane

Puoi anche vedere i termini che abbiamo trascurato dalla nostra ricerca e le frasi che abbiamo usato nel parsedTextQuery sezione.  

"parsedTextQuery": "terms": ["dog", "cat", "dont", "eat", "ate", "rat", "dog", "eat"], "negatedTerms": ["friend "]," frasi ": [" cani mangiano "]," negatedPhrases ": [] 

La query di spiegazione sarà molto utile poiché eseguiamo query di ricerca più complesse e desideriamo analizzarle.

Ricerca di testo ponderata

Quando abbiamo indici su più di un campo nel nostro documento, la maggior parte delle volte un campo sarà più importante (cioè più peso) rispetto all'altro. Ad esempio, quando stai cercando attraverso un blog, il titolo del blog dovrebbe essere di massima importanza, seguito dal contenuto del blog.

Il peso predefinito per ogni campo indicizzato è 1. Per assegnare pesi relativi ai campi indicizzati, è possibile includere il pesi opzione durante l'utilizzo del createIndex comando.

Comprendiamo questo con un esempio. Se provi a cercare il cucinare parola chiave con i nostri indici correnti, genererà due documenti, entrambi con lo stesso punteggio.   

db.messages.find ($ text: $ search: "cook", score: $ meta: "textScore"). sort (score: $ meta: "textScore")
"_id": ObjectId ("55f5289cb592880356441ead"), "subject": "Birds can cook", "content": "Birds do not eat rats", "likes": 12, "year": 2013, "location": "Chicago", "lingua": "inglese", "punteggio": 0.6666666666666666 "_id": ObjectId ("55f5289bb592880356441eab"), "oggetto": "I gatti mangiano ratti", "contenuto": "I ratti non cucinano cibo "," likes ": 55," anno ": 2014," lingua ":" inglese "," punteggio ": 0.6666666666666666 

Ora modifichiamo i nostri indici per includere i pesi; con il soggetto campo avendo un peso di 3 contro il soddisfare campo con un peso di 1.

db.messages.createIndex ("$ **": "testo", "pesi": oggetto: 3, contenuto: 1)

Prova a cercare la parola chiave cucinare ora, e vedrai che il documento che contiene questa parola chiave nel soggetto il campo ha un punteggio maggiore (di 2) rispetto all'altro (che ha 0.66).

"_id": ObjectId ("55f5289cb592880356441ead"), "subject": "Birds can cook", "content": "Birds do not eat rats", "likes": 12, "year": 2013, "location": "Chicago", "lingua": "inglese", "punteggio": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "oggetto": "I gatti mangiano ratti", "contenuto": "I ratti non cucinano cibo "," likes ": 55," anno ": 2014," lingua ":" inglese "," punteggio ": 0.6666666666666666 

Partizionare gli indici di testo

Man mano che i dati memorizzati nell'applicazione crescono, anche le dimensioni degli indici di testo continuano a crescere. Con questo aumento delle dimensioni degli indici di testo, MongoDB deve cercare tutte le voci indicizzate ogni volta che viene effettuata una ricerca di testo. 

Come tecnica per mantenere efficiente la ricerca di testo con indici in crescita, è possibile limitare il numero di voci dell'indice digitalizzate utilizzando le condizioni di parità con un regolare $ testo ricerca. Un esempio molto comune di questo sarebbe la ricerca di tutti i post fatti durante un determinato anno / mese, o la ricerca di tutti i messaggi con una determinata categoria / tag.

Se osservi i documenti su cui stiamo lavorando, abbiamo un anno campo in loro che non abbiamo ancora usato. Uno scenario comune sarebbe quello di cercare i messaggi per anno, insieme alla ricerca full-text che abbiamo imparato. 

Per questo, possiamo creare un indice composto che specifica una chiave di indice crescente / decrescente anno seguito da un indice di testo sul soggetto campo. In questo modo, stiamo facendo due cose importanti:

  • Stiamo logicamente partizionando l'intera raccolta dati in serie separate per anno.
  • Ciò limiterebbe la ricerca del testo per scansionare solo quei documenti che rientrano in un anno specifico (o chiamarlo impostato).

Rilascia gli indici che hai già e crea un nuovo indice composto su (anno, soggetto):

db.messages.createIndex ("anno": 1, "oggetto": "testo")

Ora esegui la seguente query per cercare tutti i messaggi che sono stati creati nel 2015 e contengono il gatti parola chiave:

db.messages.find (year: 2015, $ text: $ search: "cats", score: $ meta: "textScore"). sort (score: $ meta: "textScore" )

La query restituirebbe solo un documento corrispondente come previsto. Se tu spiegare questa domanda e guarda il executionStats, lo troverai totalDocsExamined per questa query è stato 1, il che conferma che il nostro nuovo indice è stato utilizzato correttamente e MongoDB ha dovuto eseguire la scansione di un singolo documento ignorando in sicurezza tutti gli altri documenti che non sono rientrati nel 2015.

Indici di testo: vantaggi

Che altro possono fare gli indici di testo?

Abbiamo fatto molta strada in questo articolo imparando gli indici di testo. Ci sono molti altri concetti che puoi sperimentare con gli indici di testo. Ma a causa della portata di questo articolo, non saremo in grado di discuterli in dettaglio oggi. Tuttavia, diamo una breve occhiata a quali sono queste funzionalità:

  • Gli indici di testo forniscono supporto multilingue, che consente di effettuare ricerche in diverse lingue utilizzando il comando $ lingua operatore. MongoDB attualmente supporta circa 15 lingue, tra cui francese, tedesco, russo, ecc.
  • Gli indici di testo possono essere utilizzati nelle query di pipeline di aggregazione. La fase di corrispondenza in una ricerca aggregata può specificare l'uso di una query di ricerca full-text.
  • Puoi utilizzare i tuoi operatori regolari per proiezioni, filtri, limiti, ordinamenti, ecc. Mentre lavori con gli indici di testo.

Indicizzazione del testo MongoDB rispetto ai database di ricerca esterni

Tenendo presente che la ricerca full text di MongoDB non è una sostituzione completa dei database dei motori di ricerca tradizionali utilizzati con MongoDB, si consiglia la funzionalità nativa di MongoDB per i seguenti motivi:

  • Come in un recente discorso su MongoDB, l'attuale ambito della ricerca testuale funziona perfettamente per la maggior parte delle applicazioni (circa l'80%) costruite usando MongoDB oggi.
  • La creazione delle funzionalità di ricerca della tua applicazione all'interno dello stesso database dell'applicazione riduce la complessità architettonica dell'applicazione.
  • La ricerca di testo MongoDB funziona in tempo reale, senza ritardi o aggiornamenti batch. Nel momento in cui inserisci o aggiorni un documento, le voci dell'indice di testo vengono aggiornate.
  • La ricerca testuale è integrata nelle funzionalità del kernel db di MongoDB, è totalmente coerente e funziona bene anche con sharding e replicazione.
  • Si integra perfettamente con le funzionalità di Mongo esistenti come filtri, aggregazione, aggiornamenti, ecc.    

Indici di testo: svantaggi

La ricerca full-text è una caratteristica relativamente nuova in MongoDB, ci sono alcune funzionalità che al momento manca. Li dividerei in tre categorie. Diamo un'occhiata.

Funzionalità mancanti dalla ricerca di testo

  • Gli indici di testo al momento non hanno la capacità di supportare interfacce collegabili come stemmer inseribili, parole di arresto, ecc.
  • Al momento non supportano funzionalità come la ricerca basata su sinonimi, parole simili, ecc.
  • Non memorizzano posizioni a termine, cioè il numero di parole con cui le due parole chiave sono separate.
  • Non è possibile specificare l'ordinamento per un'espressione di ordinamento da un indice di testo.

Restrizioni nelle funzionalità esistenti

  • Un indice di testo composto non può includere nessun altro tipo di indice, come indici multi-chiave o indici geo-spaziali. Inoltre, se l'indice di testo composto include chiavi di indice prima della chiave dell'indice di testo, tutte le query devono specificare gli operatori di uguaglianza per le chiavi precedenti.
  • Esistono alcune limitazioni specifiche per le query. Ad esempio, una query può specificare solo una singola $ testo espressione, non puoi usare $ testo con $ né, non puoi usare il suggerimento() comando con $ testo, utilizzando $ testo con $ o ha bisogno di tutte le clausole nel tuo $ o espressione da indicizzare, ecc.

Aspetti negativi delle prestazioni

  • Gli indici di testo creano un sovraccarico durante l'inserimento di nuovi documenti. Questo a sua volta colpisce il throughput di inserimento.
  • Alcune query come le ricerche di frasi possono essere relativamente lente.

Avvolgendo 

La ricerca full-text è sempre stata una delle funzionalità più richieste di MongoDB. In questo articolo, abbiamo iniziato con un'introduzione alla ricerca full-text, prima di passare alle basi della creazione di indici di testo. 

Abbiamo quindi esplorato l'indicizzazione composta, l'indicizzazione con caratteri jolly, le ricerche di frasi e le ricerche di negazione. Inoltre, abbiamo esplorato alcuni concetti importanti come l'analisi degli indici di testo, la ricerca ponderata e la partizione logica degli indici. Possiamo aspettarci alcuni importanti aggiornamenti a questa funzionalità nelle prossime versioni di MongoDB. 

Ti consiglio di provare il testo e di condividere i tuoi pensieri. Se l'hai già implementato nella tua applicazione, condividi gentilmente la tua esperienza qui. Infine, sentiti libero di postare domande, pensieri e suggerimenti su questo articolo nella sezione commenti.