So beschleunigen Sie MongoDB-Regex-Abfragen um den Faktor 10

Mit NoSQL-Datenbanken ist es einfach, Dokumente zu erstellen, die eine Reihe von Elementen enthalten. Stellen Sie sich zum Beispiel eine Filmdatenbank vor, in der jedes Dokument einen Filmtitel und die Besetzung enthält.

{
    Titel: "Matrix",
    Besetzung: ['Keanu Reeves', 'Carrie-Anne Moss']
}

Um einen Film mit Carrie-Anne Moss abzufragen, führen Sie einfach db.movies.find ({cast: 'Carrie-Anne Moss'}) aus, um das entsprechende Dokument zurückzugewinnen.

Verwenden eines Regex für nicht genaue Suchanfragen

Leider würden Benutzer auf diese Weise keine Daten in ein Suchfeld eingeben.
Sie könnten etwas wie "Carrie Moss" oder "Moss Carrie-Anne" eingeben, und eine genaue find () - Abfrage würde hier fehlschlagen.

Reguläre Ausdrücke (Regex) bieten die Möglichkeit, Zeichenfolgen mit einem Muster abzugleichen, und MongoDB verfügt über eine integrierte Regex-Engine.

Unter Verwendung von Regexen könnte die Cast-Suche mit einer Abfrage wie der implementiert werden

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

$ elemMatch gibt die Datensätze zurück, bei denen ein Array-Element beiden Kriterien entspricht - im Gegensatz dazu, wenn ein einfaches $ und (was der Standard für eine Liste von Kriterien ist) verwendet wird, ohne dass $ elemMatch Filme mit 'Carrie-Anne Moss' zurückgibt, aber auch diese wo 'Sandra Moss' und 'Carrie-Anne Fisher' zusammen die Hauptrolle spielen. Dies wäre eine Obermenge der Informationen, die wir abrufen möchten.
Beachten Sie auch das "i", bei dem die Groß- und Kleinschreibung nicht berücksichtigt wird. Wir müssen das hinzufügen, da wir uns nicht darauf verlassen können, dass Ihre Benutzer die Umschalttaste so verwenden, wie sie sollten.

In Ihren ersten Tests wird dies gut funktionieren, aber sobald Ihre Datenbank und Ihre Benutzerbasis wachsen, werden Sie feststellen, dass diese Regex-Abfragen vorhanden sind

  1. verbrauchen viel CPU-Zeit
  2. sind extrem langsam

Warum können wir nicht einfach einen Index hinzufügen?

Indizes sind das erste, was bei der Optimierung der Abfrageleistung mit jeder Datenbank berücksichtigt werden muss. In der MongoDB-Dokumentation ist ziemlich klar, dass wir in diesem Fall kein Glück haben, da bei der Regex die Groß- und Kleinschreibung nicht berücksichtigt wird. Und selbst wenn wir ein Array mit kleineren Akteuren erstellen würden, könnten wir immer noch nicht von optimierten Abfragen profitieren, da wir den ^ -Anker nicht verwenden können, um den Anfang des Texts zu markieren. Warum? Weil "Carrie-Anne Moss" und "Moss Carrie Anne". Wir wissen einfach nicht, wie die gesuchte Saite beginnt.

Also keine regulären Indizes für uns. Neuere Versionen von MongoDB unterstützen jedoch auch Textindizes.
Mit Textindizes können Sie Suchabfragen in beliebigen Zeichenfolgen ausführen. Dies sollte genau das sein, was für unsere Besetzungsabfrage benötigt wird.

Textindizes werden uns retten

So einfach ist das nicht. Textindizes in MongoDB weisen einige Einschränkungen auf:

  • Wenn Sie mehrere Felder in einem Dokument indizieren möchten, werden alle in einer Textsuchabfrage abgefragt. Mittel: Es gibt keine Möglichkeit, Felder auszuwählen, mit denen verglichen werden soll. Wenn Sie also vielleicht später eine Liste von Regisseuren pro Film hinzufügen und einen Textindex darauf setzen, wird bei einer Suche nach Regisseuren gesucht und die Besetzung vorgenommen.
  • Sie sind standardmäßig sehr breit. Eine Suche nach "Sean Connery" wird uns alle Filme liefern, die Schauspieler namens "Sean", alle Arten von "Conneries" und unseren geliebten "Sean Connery" enthalten.

Auf der anderen Seite sind Textsuchanfragen sehr schnell und effizient.
Können wir sie möglicherweise verwenden, um Dokumente für eine genaue Suche vorab zu qualifizieren?

Beginnen wir also damit, diesen Index unserer Sammlung hinzuzufügen:

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

Danach könnten wir unsere erste Suchanfrage versuchen:

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

Wie bereits erwähnt, gibt dies ein Ergebnis, aber auch falsche Positive für oder einen Anwendungsfall zurück.

Kombinieren der Textsuche mit Regex Matching

Sie wissen, dass in einer bedingten Anweisung wie if (somefunc () && someOtherFunc ()) {} someOtherFunc () nicht ausgewertet wird, wenn someFunc () false zurückgibt. Dies wird oft als Kurzschluss bezeichnet. Gleiches gilt für MongoDB-Abfragen. Wenn wir also zwei Bedingungen verwenden und logisch verbinden, wird die zweite nicht ausgeführt, wenn die erste keine Daten zurückgibt.

Darüber hinaus sind Datenbanken intelligent genug, um die zweite Abfrage auf die Ergebnismenge der ersten zu reduzieren. Wenn Sie also eine Abfrage wie {a: 1, b: 2} ausführen, werden zuerst alle Datensätze mit a: 1 gefunden und dann das Ergebnis auf reduziert alle Datensätze stimmen auch mit b: 2 überein.

Mit diesem Wissen können wir eine Abfrage erstellen, die zuerst eine Textsuche verwendet, um eine Obermenge unserer endgültigen Ergebnismenge zu finden, und dann die teurere reguläre Abfrage ausführen, um das Ergebnis einzugrenzen:

db.movies.find ({
$ und: [{
    $ text: {
        $ search: "Moss Carrie-Anne"
    }}, {
    Besetzung: {
        $ elemMatch: {$ regulärer Ausdruck: / Moss /, $ regulärer Ausdruck: / Carrie-Anne /}}
    }]}
);

Lass mich wiederholen:

  • Wenn wir eine einfache Suche nach einem Textindex durchführen, erhalten wir alle Dokumente mit indiziertem Text, der die gesuchten Wörter enthält. Dies ist zu weit gefasst, aber bereits eine Obermenge des gewünschten Ergebnisses.
  • Eine Regex-Abfrage, die mit einem logischen Wert hinzugefügt wurde und nur die aus der Textsuchabfrage abgeleitete Obermenge durchläuft.
  • Wenn die Textsuche keine Ergebnisse liefert, wird die Regex-Abfrage überhaupt nicht ausgeführt

Insbesondere bei großen Datenmengen wird dadurch die CPU-Auslastung drastisch reduziert und Ihre Abfragen beschleunigt. In meinen Tests wurden Abfragen 10-mal schneller ausgeführt und lieferten natürlich die gleichen Ergebnisse wie bei regulären Abfragen.

Übrigens: Dies ist nicht nur für MongoDB- oder gar Text- oder Regex-Abfragen relevant. In der Tat kann die Auswahl der Reihenfolge Ihrer Bedingungen die Leistung mit jeder Datenbank drastisch steigern.

HTH :)