Fulltekstsøk i MongoDB

MongoDB, en av de ledende NoSQL-databasene, er kjent for sin raske ytelse, fleksible skjema, skalerbarhet og gode indekseringsegenskaper. Kjernen til denne raske ytelsen ligger MongoDB indekser, som støtter effektiv eksekvering av spørringer ved å unngå fullsamlingssøk og dermed begrense antall dokumenter MongoDB-søk. 

Fra og med versjon 2.4 begynte MongoDB med en eksperimentell funksjon som støtter Fullt tekstsøk ved hjelp av Tekstindekser. Denne funksjonen har nå blitt en integrert del av produktet (og er ikke lenger en eksperimentell funksjon). I denne artikkelen skal vi utforske fulltekstsøkfunksjonalitetene til MongoDB, rett fra grunnleggende.

Hvis du er ny på MongoDB, anbefaler jeg at du leser følgende artikler om Envato Tuts + som vil hjelpe deg å forstå de grunnleggende konseptene i MongoDB:

  • Komme i gang med MongoDB - Del 1
  • Mapping Relational Databaser og SQL til MongoDB 

Det grunnleggende

Før vi kommer inn i noen detaljer, la oss se på litt bakgrunn. Fulltekstsøk refererer til teknikken for å søke en fulltekstdatabase mot søkekriteriene spesifisert av brukeren. Det er noe som ligner på hvordan vi søker på innhold på Google (eller faktisk andre søkeprogrammer) ved å skrive inn bestemte strengord / setninger og få tilbake de relevante resultatene sortert etter deres rangering.

Her er noen flere scenarier der vi ville se et fulltekstsøk som skjer:

  • Vurder å søke i favorittemnet ditt på Wiki. Når du skriver inn en søketekst på Wiki, gir søkemotoren resultatene av alle artiklene relatert til søkeordene / setningene du søkte etter (selv om disse søkeordene ble brukt dypt inne i artikkelen). Disse søkeresultatene er sortert etter relevans basert på deres målte poengsum.  
  • Som et annet eksempel kan du vurdere et nettsamfunn hvor brukeren kan gjøre et søk for å finne alle innleggene som inneholder søkeordet katteri dem; eller å være mer komplisert, alle innleggene som har kommentarer som inneholder ordet katter.  

Før vi går videre, er det visse generelle vilkår knyttet til fulltekstsøk som du burde vite. Disse vilkårene gjelder for fullføringssøkimplementering (og ikke MongoDB-spesifikk).

Stopp ord

Stoppord er de irrelevante ordene som skal filtreres ut fra en tekst. For eksempel: a, an, den, er, på, som osv.

stemming

Stemming er prosessen med å redusere ordene til deres stamme. For eksempel: ord som stående, står, stod, etc. har en felles base stå.

scoring

En relativ rangering for å måle hvilken av søkeresultatene som er mest relevant.  

Alternativer til fulltekstsøk i MongoDB

Før MongoDB kom opp med begrepet tekstindekser, ville vi enten modellere våre data for å støtte søkeordssøk eller bruke vanlige uttrykk for å implementere slike søkefunksjoner. Bruk av noen av disse tilnærmingene hadde imidlertid sine egne begrensninger:

  • For det første støtter ingen av disse tilnærmingene funksjoner som stemming, stoppord, rangering osv.  
  • Bruk av søkeordssøk vil kreve opprettelse av multi-nøkkelindekser, som ikke er tilstrekkelige i forhold til fulltekst.
  • Bruk av vanlige uttrykk er ikke effektiv fra ytelsessynspunktet, siden disse uttrykkene ikke effektivt bruker indekser.
  • I tillegg til det kan ingen av disse teknikkene brukes til å utføre noen setningssøk (som å søke etter "filmer utgitt i 2015") eller vektede søk.  

Bortsett fra disse tilnærmingene, for mer avanserte og komplekse søk-sentriske applikasjoner, finnes det alternative løsninger som Elastic Search eller SOLR. Men ved hjelp av noen av disse løsningene øker applikasjonens arkitektoniske kompleksitet, siden MongoDB nå må snakke med en ekstern ekstern database. 

Merk at MongoDBs fulltekstsøk ikke er foreslått som en komplett erstatning av søkemotordatabaser som Elastic, SOLR, etc. Det kan imidlertid brukes effektivt til de fleste applikasjoner som er bygget med MongoDB i dag.

Vi presenterer MongoDB Text Search

Ved å bruke MongoDB fulltekstsøk kan du definere en tekstindeks på et hvilket som helst felt i dokumentet hvis verdi er en streng eller en rekke strenger. Når vi oppretter en tekstindeks på et felt, tilkaller MongoDB og stilker det indekserte feltets tekstinnhold, og setter opp indeksene tilsvarende.  

For å forstå ting ytterligere, la oss nå dykke inn i noen praktiske ting. Jeg vil at du skal følge veiledningen med meg ved å prøve ut eksemplene i mongo skallet. Vi vil først lage noen prøvedata som vi skal bruke gjennom hele artikkelen, og så fortsetter vi videre for å diskutere nøkkelbegreper.

For formålet med denne artikkelen, vurder en samling meldinger som lagrer dokumenter av følgende struktur: 

"subject": "Joe eier en hund", "innhold": "Hunder er mannens beste venn", "liker": 60, "år": 2015, "språk": "engelsk")

La oss sette inn noen eksemplar dokumenter ved hjelp av sett inn kommando for å lage testdataene våre:

db.messages.insert ("subject": "Joe eier en hund", "innhold": "Hunder er mannens beste venn", "liker": 60, "år": 2015, "språk": "engelsk") ) "db.messages.insert" ("subject": "Hunder spiser katter og hund spiser duer også", "innhold": "Katter er ikke onde", "liker": 30, "år": 2015, "språk": "engelsk") db.messages.insert ("subject": "Katter spiser rotter", "innhold": "Rotter kjenner ikke mat", "liker": 55, "år": 2014, "språk": "engelsk") db.messages.insert ("subject": "Rats eat Joe", "innhold": "Joe spiste en rotte", "liker": 75, "år": 2014, "språk" Engelsk")

Opprette en tekstindeks

En tekstindeks opprettes ganske likt hvordan vi lager en vanlig indeks, bortsett fra at den spesifiserer tekst søkeord i stedet for å angi en stigende / synkende rekkefølge.

Indeksere et enkelt felt

Lag en tekstindeks på Emne feltet i dokumentet vårt ved hjelp av følgende spørring:

db.messages.createIndex ( "emne": "tekst")

For å teste denne nyopprettede tekstindeksen på Emne feltet, søker vi dokumenter ved hjelp av $ tekst operatør. Vi vil se etter alle dokumentene som har søkeordet hunder i deres Emne felt. 

Siden vi kjører et tekstsøk, er vi også interessert i å få litt statistikk om hvor relevant de resulterende dokumentene er. For dette formålet vil vi bruke $ Meta: "textScore" uttrykk, som gir informasjon om behandling av $ tekst operatør. Vi vil også sortere dokumentene av deres textScore bruker sortere kommando. En høyere textScore Indikerer en mer relevant kamp. 

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

Ovennevnte søk returnerer følgende dokumenter som inneholder søkeordet hunder i deres Emne felt. 

"_id": ObjectId ("55f4a5d9b592880356441e94"), "subject": "Hunder spiser katter og hunder spiser duer også", "innhold": "Katter er ikke onde", "liker": 30, "år" "Language": "Engelsk", "Score": 1 "_id": ObjectId ("55f4a5d9b592880356441e93"), "Subject": "Joe eier en hund", "innhold": "Hunder er mannens beste venn" liker ": 60," år ": 2015," språk ":" engelsk "," poengsum ": 0.6666666666666666

Som du kan se har det første dokumentet en score på 1 (siden søkeordet hund vises to ganger i emnet) i motsetning til det andre dokumentet med en poengsum på 0,66. Spørringen har også sortert de returnerte dokumentene i synkende rekkefølge av partituret deres.

Et spørsmål som kan oppstå i tankene dine er at hvis vi søker etter søkeordet hunder, hvorfor er søkemotoren tar søkeordet hund (uten 's') i betraktning? Husk vår diskusjon om stemming, hvor noen søkeord er redusert til deres base? Dette er grunnen til at søkeordet hunder er redusert til hund.

Indeksere flere felt (sammensatt indeksering)

Oftere enn ikke, vil du bruke tekstsøk på flere felt i et dokument. I vårt eksempel vil vi aktivere sammensatt tekstindeksering på Emne og innhold Enger. Gå videre og utfør følgende kommando i mongo shell:  

db.messages.createIndex ( "emne": "tekst", "innhold": "tekst")

Gjorde dette arbeidet? Nei!! Opprette en ny tekstindeks vil gi deg en feilmelding som sier at det allerede finnes en fulltekstsøkingsindeks. Hvorfor er det slik? Svaret er at tekstindeksene kommer med en begrensning av kun en tekstindeks per samling. Derfor, hvis du ønsker å opprette en annen tekstindeks, må du slippe den eksisterende og gjenskape den nye. 

db.messages.dropIndex ("subject_text") db.messages.createIndex ("emne": "tekst", "innhold": "tekst") 

Etter å ha utført de ovennevnte indeksopprettingsspørsmålene, prøv å søke etter alle dokumenter med søkeord katt.

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

Ovennevnte spørring vil sende ut følgende dokumenter:

"_id": ObjectId ("55f4af22b592880356441ea4"), "subject": "Hunder spiser katter og hunder spiser også duer", "innhold": "Katter er ikke onde", "liker": 30, "år" "språk": "engelsk", "poeng": 1.3333333333333335 "_id": ObjectId ("55f4af22b592880356441ea5"), "subject": "Katter spiser rotter", "innhold": "Rotter kjenner ikke mat" ": 55," år ": 2014," språk ":" engelsk "," poeng ": 0.6666666666666666 

Du kan se at poengsummen til det første dokumentet, som inneholder søkeordet katt i begge Emne og innhold felter, er høyere. 

Indeksering hele dokumentet (Wildcard Indexing)

I det siste eksemplet legger vi en kombinert indeks på Emne og innhold Enger. Men det kan være scenarier der du vil at tekstinnhold i dokumentene skal søkes. 

For eksempel, bør du vurdere å lagre e-post i MongoDB-dokumenter. I tilfelle av e-post, må alle feltene, inkludert Avsender, Mottaker, Emne og Kropp, søkes. I slike scenarier kan du indeksere alle strengfeltene i dokumentet ditt ved hjelp av $ ** wildcard spesifier.

Forespørselen ville gå noe slikt (pass på at du sletter den eksisterende indeksen før du oppretter en ny):

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

Denne spørringen vil automatisk sette opp tekstindekser på noen strengfelter i våre dokumenter. For å teste dette ut, legg inn et nytt dokument med et nytt felt plassering i det:

db.messages.insert ("subject": "Fugler kan lage mat", "innhold": "Fugler spiser ikke rotter", "liker": 12, "år": 2013, plassering: "Chicago" :"Engelsk")

Nå hvis du prøver tekstsøk med søkeord chicago (spørring under), vil det returnere dokumentet som vi nettopp har satt inn.

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

Noen ting jeg vil fokusere på her:

  • Vær oppmerksom på at vi ikke eksplisitt definerte en indeks på plassering feltet etter at vi har satt inn et nytt dokument. Dette skyldes at vi allerede har definert en tekstindeks på hele dokumentet ved hjelp av $ ** operatør.
  • Wildcard-indekser kan være treg til tider, spesielt i scenarier der dataene dine er svært store. Av denne grunn, planlegg dokumentindeksene dine (aka wildcard-indekser) klokt, da det kan føre til et resultatstreff.

Avansert søking

Setningssøk

Du kan søke etter setninger som "smarte fugler som elsker matlaging"bruker tekstindekser. Som standard gjør søkeordet en ELLER søk på alle de angitte søkeordene, dvs. det vil se etter dokumenter som inneholder enten søkeordene smart, fugl, kjærlighet eller kokk.

db.messages.find ($ text: $ search: "smarte fugler som lager mat", score: $ meta: "tekstpoeng"). sort (score: $ meta: ")

Denne spørringen vil sende ut følgende dokumenter:

"_id": ObjectId ("55f5289cb592880356441ead"), "emne": "Fugler kan lage mat", "innhold": "Fugler spiser ikke rotter", "liker": 12, "år": 2013, "plassering" "Cats eat rats", "content": "Rats koker ikke mat", "Chicago", "Language": "Engelsk", "Score": 2 "_id": ObjectId ("55f5289bb59282880356441eab") "," liker ": 55," år ": 2014," språk ":" engelsk "," poeng ": 0.6666666666666666 

I tilfelle du ønsker å utføre en eksakt setningssøk (logisk OG), kan du gjøre det ved å angi dobbelte sitater i søketeksten. 

db.messages.find ($ text: $ search: "\" lag mat \ "", poengsum: $ meta: "textScore"). sort (score: $ meta: "textScore ")

Denne spørringen vil resultere i følgende dokument, som inneholder uttrykket "lag mat" sammen:

"_id": ObjectId ("55f5289bb59282880356441eab"), "subject": "Katter spiser rotter", "innhold": "Rotter kjenner ikke mat", "liker": 55, "år": 2014, "språk": "engelsk", "poengsum": 0.666666666666666666

Negasjonssøk

Prefikser et søkeord med - (minustegn) utelukker alle dokumentene som inneholder det negerte begrepet. For eksempel, prøv å søke etter ethvert dokument som inneholder søkeordet rotte men inneholder ikke fugler bruker følgende spørring:

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

Ser bak scenene

En viktig funksjonalitet jeg ikke avslørte til nå, er hvordan du ser bak kulissene og ser hvordan søkeordene dine blir stemmed, stopper formulering, negerer, etc. $ forklare til redning. Du kan kjøre forklare spørringen ved å passere ekte som parameter, som vil gi deg detaljert statistikk på spørringen.  

db.messages.find ($ text: $ search: "hunder som ikke spiser katter spiste rotter \" hunder spiser \ "-vennner, score: $ meta:" textScore ") score: $ meta: "textScore".) forklare (sann) 

Hvis du ser på queryPlanner objekt returnert av forklar kommandoen, vil du kunne se hvordan MongoDB analyserte den gjeldende søkestrengen. Vær oppmerksom på at det forsømte å stoppe ord som hvem, og stammede hunder til hund

Du kan også se vilkårene som vi forsømte fra vårt søk og setningene vi brukte i parsedTextQuery seksjon.  

"parsedTextQuery": "vilkår": ["hund", "katt", "ikke", "spise", "spiste", "rotte", "hund", "spise"], "negatedTerms" "]," setninger ": [" hunder spiser "]," negatedPhrases ": [] 

Forklar spørringen vil være svært nyttig når vi utfører mer komplekse søk og ønsker å analysere dem.

Vektet tekstsøk

Når vi har indekser på mer enn ett felt i dokumentet, vil de fleste ganger det ene feltet bli viktigere (det vil si mer vekt) enn det andre. For eksempel, når du søker på tvers av en blogg, bør tittelen på bloggen ha høyest vekt, etterfulgt av blogginnholdet.

Standardvekten for hvert indeksert felt er 1. For å tilordne relative vekter for de indekserte feltene, kan du inkludere vekter alternativet mens du bruker createIndex kommando.

La oss forstå dette med et eksempel. Hvis du prøver å søke etter kokk søkeord med våre nåværende indekser, vil det resultere i to dokumenter, som begge har samme poengsum.   

db.messages.find ($ text: $ search: "cook", score: $ meta: "textScore"). sort (score: $ meta: "textScore")
"_id": ObjectId ("55f5289cb592880356441ead"), "emne": "Fugler kan lage mat", "innhold": "Fugler spiser ikke rotter", "liker": 12, "år": 2013, "plassering" "Cats eat rats", "content": "rotter koker ikke mat", "cats eat rats", "content": "english", "score": 0.666666666666666666 "_id": ObjectId ("55f5289bb59282880356441eab") "," liker ": 55," år ": 2014," språk ":" engelsk "," poeng ": 0.6666666666666666 

La oss nå endre indeksene våre for å inkludere vekter; med Emne feltet har en vekt på 3 mot innhold felt med en vekt på 1.

db.messages.createIndex ("$ **": "text", "vekter": emne: 3, innhold: 1)

Prøv å søke etter søkeord kokk nå, og du vil se at dokumentet som inneholder dette søkeordet i Emne feltet har en større poengsum (av 2) enn den andre (som har 0,66).

"_id": ObjectId ("55f5289cb592880356441ead"), "emne": "Fugler kan lage mat", "innhold": "Fugler spiser ikke rotter", "liker": 12, "år": 2013, "plassering" "Cats eat rats", "content": "Rats koker ikke mat", "Chicago", "Language": "Engelsk", "Score": 2 "_id": ObjectId ("55f5289bb59282880356441eab") "," liker ": 55," år ": 2014," språk ":" engelsk "," poeng ": 0.6666666666666666 

Partisjonering av tekstindekser

Etter hvert som dataene som er lagret i søknaden din vokser, fortsetter størrelsen på tekstindeksene å vokse også. Med denne økningen i størrelsen på tekstindekser, må MongoDB søke etter alle indekserte oppføringer når et tekstsøk er gjort. 

Som en metode for å holde tekstsøkingen effektiv med voksende indekser, kan du begrense antall skannede indeksoppføringer ved å bruke likestillingsforhold med en vanlig $ tekst Søke. Et svært vanlig eksempel på dette ville være å søke alle innleggene som ble gjort i løpet av et bestemt år / måned, eller søke på alle innleggene med en bestemt kategori / tag.

Hvis du observerer dokumentene som vi jobber med, har vi en år feltet i dem som vi ikke har brukt ennå. Et vanlig scenario ville være å søke meldinger etter år, sammen med det fulltekstsøk som vi har lært om. 

For dette kan vi opprette en sammensatt indeks som spesifiserer en stigende / synkende indeksnøkkel på år etterfulgt av en tekstindeks på Emne felt. Ved å gjøre dette, gjør vi to viktige ting:

  • Vi deler logisk opp hele samlingsdataene i sett adskilt av året.
  • Dette ville begrense tekstsøket for å skanne bare de dokumentene som faller under et bestemt år (eller ring det sett).

Slett indeksene du allerede har, og opprett en ny sammensatt indeks på (år, Emne):

db.messages.createIndex ("år": 1, "emne": "tekst")

Nå utfør følgende spørring for å søke alle meldingene som ble opprettet i 2015 og inneholde katter søkeord:

db.messages.find (år: 2015, $ tekst: $ søk: "katter", poengsum: $ meta: "textScore"). sort (score: $ meta: "textScore" )

Forespørselen ville returnere bare ett sammenpasset dokument som forventet. Hvis du forklare denne spørringen og se på executionStats, du vil finne det totalDocsExamined for denne spørringen var 1, noe som bekrefter at vår nye indeks ble utnyttet riktig, og MongoDB måtte bare skanne et enkelt dokument mens du sikkert ignorerte alle andre dokumenter som ikke var under 2015.

Tekstindekser: Fordeler

Hva mer kan tekstindekser gjøre?

Vi har kommet langt i denne artikkelen og lærer om tekstindekser. Det er mange andre begreper som du kan eksperimentere med tekstindekser. Men på grunn av omfanget av denne artikkelen, vil vi ikke kunne diskutere dem i detalj i dag. Likevel, la oss få en kort titt på hva disse funksjonene er:

  • Tekstindekser gir flerspråklig støtte, slik at du kan søke på forskjellige språk ved hjelp av $ språk operatør. MongoDB støtter for tiden rundt 15 språk, inkludert fransk, tysk, russisk, etc.
  • Tekstindekser kan brukes til å samle rørledningsspørsmål. Kampstadiet i et samlet søk kan spesifisere bruken av en fulltekstsøk.
  • Du kan bruke dine vanlige operatører til projeksjoner, filtre, grenser, sorter, etc., mens du arbeider med tekstindekser.

MongoDB Text Indexing vs Eksterne Søk Databaser

Med tanke på det faktum at MongoDB fulltekstsøk ikke er en komplett erstatning for tradisjonelle søkemotordatabaser som brukes med MongoDB, anbefales det å bruke den innfødte MongoDB-funksjonaliteten av følgende grunner:

  • Som en nylig snakk på MongoDB virker det nåværende omfanget av tekstsøk perfekt for et flertall applikasjoner (rundt 80%) som er bygget med MongoDB i dag.
  • Å bygge søkemulighetene i søknaden din i samme applikasjonsdatabase reduserer programmets arkitektoniske kompleksitet.
  • MongoDB tekstsøk fungerer i sanntid, uten oppdateringer eller oppdateringer. Når du setter inn eller oppdaterer et dokument, oppdateres tekstindeksoppføringene.
  • Tekstsøk blir integrert i db kernel-funksjonalitetene til MongoDB, det er helt konsistent og fungerer bra selv med sharding og replikering.
  • Den integreres perfekt med eksisterende Mongo-funksjoner, for eksempel filtre, aggregering, oppdateringer, etc..    

Tekstindekser: Ulemper

Fulltekstsøk er en relativt ny funksjon i MongoDB, det er visse funksjoner som den for tiden mangler. Jeg ville dele dem i tre kategorier. La oss se.

Funksjoner som mangler fra tekstsøk

  • Tekstindekser har for øyeblikket ikke muligheten til å støtte pluggbare grensesnitt som pluggable stemmers, stoppe ord, etc.
  • De støtter for øyeblikket ikke funksjoner som å søke på grunnlag av synonymer, lignende ord osv.
  • De lagrer ikke termoposisjoner, det vil si antall ord som de to søkeordene er separert fra.
  • Du kan ikke spesifisere sorteringsrekkefølgen for et sorteringsuttrykk fra en tekstindeks.

Begrensninger i eksisterende funksjonaliteter

  • En sammensatt tekstindeks kan ikke inneholde noen annen type indeks, som multi-key indekser eller geo-romlige indekser. I tillegg, hvis din sammensatte tekstindeks inneholder noen indeksnøkler før tekstindeksnøkkelen, må alle spørsmålene angi likestillingsoperatørene for de foregående tastene.
  • Det er noen spørringsspesifikke begrensninger. For eksempel kan et søk spesifisere bare en enkelt $ tekst uttrykk, du kan ikke bruke $ tekst med $ nor, du kan ikke bruke hint() kommandoen med $ tekst, ved hjelp av $ tekst med $ eller trenger alle klausulene i din $ eller uttrykk for å bli indeksert, osv.

Ytelse Ulemper

  • Tekstindekser oppretter en overhead mens du legger inn nye dokumenter. Dette slår igjen innførings gjennomstrømningen.
  • Noen søk som setningssøk kan være relativt sakte.

Wrapping Up 

Fulltekstsøk har alltid vært en av de mest etterspurte funksjonene i MongoDB. I denne artikkelen startet vi med en introduksjon til hva fulltekstsøk er, før vi går videre til grunnleggende om å lage tekstindekser. 

Vi undersøkte deretter sammensatt indeksering, wildcard-indeksering, setningssøk og negasjonssøk. Videre utforsket vi noen viktige begreper som å analysere tekstindekser, vektet søk og logisk partisjonering av indeksene dine. Vi kan forvente noen store oppdateringer til denne funksjonaliteten i de kommende utgivelsene til MongoDB. 

Jeg anbefaler at du gir tekst-søk et forsøk og deler tankene dine. Hvis du allerede har implementert den i søknaden din, vennligst del din erfaring her. Endelig er du velkommen til å legge inn dine spørsmål, tanker og forslag til denne artikkelen i kommentarfeltet.