Sådan fremskyndes MongoDB Regex-forespørgsler med en faktor på op til 10

Med NoSQL-databaser er det nemt at oprette dokumenter, der indeholder en række elementer. Forestil dig for eksempel en filmdatabase, hvor hvert dokument indeholder en filmtitel og rollebesætningen.

{
    titel: 'Matrix',
    rollebesætning: ['Keanu Reeves', 'Carrie-Anne Moss']
}

For at spørge en film med Carrie-Anne Moss, ville vi simpelthen køre db.movies.find ({cast: 'Carrie-Anne Moss'}) for at få det matchende dokument tilbage.

Brug af en Regex til ikke-nøjagtige søgeforespørgsler

Desværre er det ikke sådan, at brugere vil indtaste data i et søgefelt.
De kunne indtaste noget som 'Carrie Moss' eller 'moss carrie-anne', og en nøjagtig søgning () -forespørgsel ville komme til kort her.

Regelmæssige udtryk (regex) giver en måde at matche strenge mod et mønster, og MongoDB leveres med en indbygget regex-motor.

Brug af regexes den cast søgning kunne implementeres med en forespørgsel som

db.movies.find ({
    rollebesætning: {$ elemMatch: {$ regex: / Moss / i, $ regex: / Carrie-Anne / i}}
});

$ elemMatch returnerer disse poster, hvor et array-element matcher begge kriterier - i modsætning hertil bruger en almindelig $ og (som er standard for en liste over kriterier) uden $ elemMatch ville returnere film med 'Carrie-Anne Moss', men også dem hvor 'Sandra Moss' og 'Carrie-Anne Fisher' spiller hovedrollen sammen. Dette ville være et supersæt af de oplysninger, vi ønsker at hente.
Bemærk også det 'jeg', der gør regex-sagen ufølsom. Vi må tilføje det, fordi vi ikke kan stole på, at dine brugere bruger deres shift-nøgle, som de burde.

I dine første test fungerer dette godt, men så snart din database og brugerbase vokser, vil du finde ud af, at disse regex-forespørgsler

  1. forbruge meget CPU-tid
  2. er ekstremt langsomt

Hvorfor kan vi ikke bare tilføje et indeks?

Indekser er den første ting, der skal overvejes, når du optimerer forespørgselsydelsen med en hvilken som helst database. MongoDB-dokumentationen er temmelig klar over, at vi er ude af held i denne sag, fordi regex er sansefølsom. Og selv hvis vi opretter en matrix med skuespillere med mindre karakter, kunne vi stadig ikke drage fordel af optimerede forespørgsler, fordi vi ikke kan bruge ^ ankeret til at markere begyndelsen på teksten. Hvorfor? Fordi ‘Carrie-Anne Moss’ og ‘Moss Carrie Anne’. Vi ved simpelthen ikke, hvordan den streng, vi leder efter, begynder.

Så ingen regelmæssige indekser for os. Men nyere versioner af MongoDB understøtter også tekstindekser.
Tekstindekser giver dig mulighed for at udføre søgeforespørgsler i vilkårlige strenge. Dette skal være nøjagtigt, hvad der er behov for vores cast-forespørgsel.

Tekstindekser vil sikre os

Nå, det er ikke så let. Tekstindekser i MongoDB kommer med et par advarsler:

  • Hvis du vil indeksere flere felter i et dokument, bliver de alle spurgt i en tekstsøgning. Midler: Der er ingen måde at vælge felter at matche mod. Så hvis du måske senere tilføjer en liste over instruktører pr. Film og lægger et tekstindeks på den, vil en søgning søge instruktører og rollebesættere.
  • De er som standard meget brede. En søgning efter 'Sean Connery' giver os alle film, der indeholder en eller anden skuespiller kaldet 'Sean', alle slags 'Connerie' og sammen med vores elskede 'Sean Connery'.

På den anden side er tekstsøgeforespørgsler ret hurtige og effektive.
Kan vi måske bruge dem til at præ-kvalificere dokumenter til en nøjagtig søgning?

Så lad os starte med at tilføje dette indeks til vores samling:

db.movies.createIndex ({cast: "tekst"});

Derefter kunne vi prøve vores første søgeforespørgsel:

db.movies.find ({$ tekst: {$ search: "Moss Carrie-Anne"}});

Som nævnt vil dette returnere et resultat, men også falske positiver for eller use-case.

Kombination af tekstsøgning med Regex Matching

Du ved, at i en betinget erklæring likeif (somefunc () && someOtherFunc ()) {}, vil nogleOtherFunc () ikke blive evalueret, hvis nogleFunc () returnerer falske. Dette kaldes ofte 'kortslutning'. Det samme gælder for MongoDB-forespørgsler. Dette betyder, at hvis vi bruger og logisk forbinder to betingelser, udføres den anden ikke, hvis den første ikke returnerer nogen data.

Derudover er databaser smarte nok til at reducere den anden forespørgsel til resultatsættet for den første, så hvis vi tager en forespørgsel som {a: 1, b: 2}, ville vi først finde alle poster med en: 1 og derefter reducere resultatet til alle poster, der matcher b: 2 også.

Anvendelse af denne viden kan vi oprette en forespørgsel, der først bruger en tekstsøgning til bredt at finde et supersæt af vores endelige resultatsæt og derefter udføre den dyrere regex-forespørgsel for at indsnævre resultatet:

db.movies.find ({
$ Og: [{
    $ tekst: {
        $ søgning: "Moss Carrie-Anne"
    }}, {
    rollebesætning: {
        $ elemMatch: {$ regex: / Moss /, $ regex: / Carrie-Anne /}}
    }]}
);

Lad mig gentage:

  • Når vi udfører en simpel søgning mod et tekstindeks, får vi alle dokumenter med indekseret tekst, der indeholder de ord, vi leder efter. Dette er for bredt, men allerede et supersæt af det resultat, vi ønsker.
  • En regex-forespørgsel tilføjet med en logisk og går kun gennem det supersæt, der stammer fra tekstsøgeforespørgslen.
  • Hvis tekstsøgningen ikke giver nogen resultater, udføres regex-forespørgslen overhovedet ikke

Specielt for store datasæt vil dette drastisk reducere CPU-belastningen og også fremskynde dine spørgsmål. I mine test udføres forespørgsler 10 gange hurtigere, hvilket naturligvis returnerer de samme resultater som med regex-forespørgsler alene.

I øvrigt - dette er ikke kun relevant for MongoDB eller endda tekst vs. regex-forespørgsler. Faktisk kan du vælge rækkefølgen af ​​dine forhold med omhu det drastisk øge ydelsen med enhver database.

HTH :)