Suchergebnisse anzeigen und Leistungsprobleme

Eines der typischen Szenarien in allen uns vertrauten Anwendungen ist die Suche nach Daten anhand bestimmter Kriterien und deren Ausgabe in einem lesbaren Format. Hierbei können auch zusĂ€tzliche Funktionen wie Sortierung, Gruppierung und paginierte Ausgabe vorhanden sein. Die Aufgabe scheint trivial zu sein, doch viele Entwickler machen bei der Lösung eine Reihe von Fehlern, die sich negativ auf die Leistung auswirken. Lassen Sie uns verschiedene Lösungsmöglichkeiten fĂŒr dieses Problem betrachten und Empfehlungen fĂŒr die Auswahl der effektivsten Implementierung formulieren.

Suchergebnisse anzeigen und Leistungsprobleme

Paginierungsoption #1

Die einfachste Lösung, die man sich vorstellen kann, ist die paginierte Ausgabe der Suchergebnisse in der klassischsten Form.

Suchergebnisse anzeigen und Leistungsprobleme
Angenommen, die Anwendung nutzt eine relationale Datenbank. In diesem Fall mĂŒssen zur Anzeige der Informationen in dieser Form zwei SQL-Abfragen durchgefĂŒhrt werden:

  • Die Zeilen fĂŒr die aktuelle Seite abrufen.
  • Die Gesamtanzahl der Zeilen zĂ€hlen, die den Suchkriterien entsprechen – dies ist notwendig, um die Seiten anzuzeigen.

Betrachten wir die erste Abfrage am Beispiel einer Test-MS-SQL-Datenbank. AdventureWorks fĂŒr den 2016-Server. Zu diesem Zweck verwenden wir die Tabelle Sales.SalesOrderHeader:

SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Die obige Abfrage gibt die ersten 50 Bestellungen aus der Liste zurĂŒck, die absteigend nach HinzufĂŒgungsdatum sortiert sind, mit anderen Worten – die letzten 50 Bestellungen.

Diese Abfrage wird schnell auf der Testdatenbank ausgefĂŒhrt, aber schauen wir uns den AusfĂŒhrungsplan und die E/A-Statistik an:

Suchergebnisse anzeigen und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-ZĂ€hler 1, logische Reads 698, physische Reads 0, Read-Ahead Reads 0, LOB logische Reads 0, LOB physische Reads 0, LOB Read-Ahead Reads 0.

Die E/A-Statistik fĂŒr jede Abfrage kann erhalten werden, indem im AbfrageausfĂŒhrungsumfeld der Befehl SET STATISTICS IO ON ausgefĂŒhrt wird.

Wie aus dem AusfĂŒhrungsplan hervorgeht, ist die sortieren von allen Zeilen der Ursprungstabelle nach HinzufĂŒgungsdatum am ressourcenintensivsten. Und das Problem besteht darin, dass je mehr Zeilen in der Tabelle vorhanden sind, desto „schwerer“ wird die Sortierung. In der Praxis sollte man solche Situationen vermeiden, daher fĂŒgen wir einen Index auf dem HinzufĂŒgungsdatum hinzu und prĂŒfen, ob die Ressourcennutzung sich geĂ€ndert hat:

Suchergebnisse anzeigen und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-ZĂ€hler 1, logische Reads 165, physische Reads 0, Read-Ahead Reads 5, LOB logische Reads 0, LOB physische Reads 0, LOB Read-Ahead Reads 0.

Offensichtlich ist es viel besser geworden. Aber sind alle Probleme gelöst? Lassen Sie uns die Suchanfrage fĂŒr Bestellungen Ă€ndern, bei denen der Gesamtwert der Produkte 100 Dollar ĂŒbersteigt:

SELECT * FROM Sales.SalesOrderHeader
WHERE SubTotal > 100
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Suchergebnisse anzeigen und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-Anzahl 1, logische LesevorgÀnge 1081, physische LesevorgÀnge 0, VorablesevorgÀnge 0, lob logische LesevorgÀnge 0, lob physische LesevorgÀnge 0, lob VorablesevorgÀnge 0.

Wir haben eine lustige Situation: Der Abfrageplan ist nicht viel schlechter als der vorherige, aber die tatsĂ€chliche Anzahl der logischen LesevorgĂ€nge ist fast doppelt so hoch wie bei einem vollstĂ€ndigen Tabellenscan. Es gibt einen Ausweg — wenn wir den bereits vorhandenen Index zusammensetzen und das zweite Feld den Gesamtpreis der Produkte hinzuzufĂŒgen, erhalten wir wieder 165 logische LesevorgĂ€nge:

CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);

Diese Reihe von Beispielen lĂ€sst sich noch lange fortsetzen, aber zwei Hauptgedanken, die ich hier Ă€ußern möchte, sind folgende:

  • Die HinzufĂŒgung eines neuen Kriteriums oder einer neuen Sortierreihenfolge in der Suchanfrage kann die AusfĂŒhrungsgeschwindigkeit erheblich beeinflussen.
  • Wenn wir jedoch nur einen Teil der Daten abfragen mĂŒssen und nicht alle Ergebnisse, die den Suchkriterien entsprechen, gibt es viele Möglichkeiten, eine solche Abfrage zu optimieren.

Kommen wir jetzt zur zweiten Abfrage, die zu Beginn erwĂ€hnt wurde — die, die die Anzahl der DatensĂ€tze zĂ€hlt, die den Suchkriterien entsprechen. Nehmen wir dasselbe Beispiel — die Suche nach Bestellungen, die mehr als 100 Dollar kosten:

SELECT COUNT(1) FROM Sales.SalesOrderHeader
WHERE SubTotal > 100

Mit dem oben genannten zusammengesetzten Index erhalten wir:

Suchergebnisse anzeigen und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-ZĂ€hler 1, logische Reads 698, physische Reads 0, Read-Ahead Reads 0, LOB logische Reads 0, LOB physische Reads 0, LOB Read-Ahead Reads 0.

Dass die Abfrage ĂŒber den gesamten Index ausgefĂŒhrt wird, ist nicht ĂŒberraschend, da das Feld SubTotal nicht an erster Stelle steht, und daher kann die Abfrage nicht davon profitieren. Das Problem lĂ€sst sich lösen, indem ein weiterer Index auf dem Feld SubTotal hinzugefĂŒgt wird, und am Ende ergibt sich bereits nur noch 48 logische LesevorgĂ€nge.

Es lassen sich noch einige weitere Beispiele fĂŒr Abfragen zur ZĂ€hlung anfĂŒhren, aber die GrundsĂ€tze bleiben die gleichen: Die Abfrage von Daten und die ZĂ€hlung der Gesamtanzahl sind zwei grundlegend verschiedene Abfragen, und jede erfordert ihre eigenen Maßnahmen zur Optimierung. Im Allgemeinen wird es nicht möglich sein, eine Kombination von Indizes zu finden, die fĂŒr beide Abfragen gleichermaßen gut funktioniert.

Daher ist eine der wichtigen Anforderungen, die bei der Entwicklung einer solchen Suchlösung geklĂ€rt werden muss, ob es fĂŒr das GeschĂ€ft wichtig ist, die Gesamtzahl der gefundenen Objekte zu sehen. Oft ist dem nicht so. Und die Navigation zu bestimmten Seitenzahlen ist meiner Meinung nach eine Lösung mit einem sehr engen Anwendungsbereich, da die meisten Szenarien mit Paging wie „zur nĂ€chsten Seite wechseln“ aussehen.

Paging-Option #2

Angenommen, den Benutzern ist die Kenntnis der Gesamtzahl der gefundenen Objekte nicht wichtig. Lassen Sie uns die Suchseite vereinfachen:

Suchergebnisse anzeigen und Leistungsprobleme
TatsĂ€chlich hat sich nur geĂ€ndert, dass es keine Möglichkeit gibt, zu bestimmten Seitenzahlen zu wechseln, und jetzt muss diese Tabelle fĂŒr die Anzeige nicht wissen, wie viele es insgesamt geben könnte. Aber die Frage stellt sich – wie erfĂ€hrt die Tabelle, ob es Daten fĂŒr die nĂ€chste Seite gibt (um den Link „Weiter“ korrekt anzuzeigen)?

Die Antwort ist ganz einfach: Man kann einen Datensatz mehr aus der Datenbank abfragen, als fĂŒr die Anzeige benötigt wird, und das Vorhandensein dieses „zusĂ€tzlichen“ Datensatzes zeigt, ob eine weitere Portion vorhanden ist. Auf diese Weise muss nur eine Abfrage ausgefĂŒhrt werden, um eine Seite von Daten zu erhalten, was die Leistung erheblich verbessert und die UnterstĂŒtzung einer solchen FunktionalitĂ€t erleichtert. Ich habe in der Praxis einen Fall erlebt, bei dem der Verzicht auf die ZĂ€hlung der Gesamtzahl der DatensĂ€tze die Ergebnisse um das 4- bis 5-fache beschleunigt hat.

FĂŒr diesen Ansatz gibt es mehrere Optionen fĂŒr die BenutzeroberflĂ€che: die SchaltflĂ€chen „zurĂŒck“ und „vorwĂ€rts“, wie im obigen Beispiel, eine SchaltflĂ€che „mehr laden“, die einfach eine neue Portion zu den angezeigten Ergebnissen hinzufĂŒgt, und „unendliches Scrollen“, das nach dem Prinzip „mehr laden“ funktioniert, bei dem das Scrollen des Benutzers an das Ende aller angezeigten Ergebnisse das Signal fĂŒr das Abrufen der nĂ€chsten Portion ist. Wie auch immer die visuelle Lösung aussieht, das Prinzip der Datenauswahl bleibt dasselbe.

Details zur Implementierung von Paging

In allen oben genannten Anfragen wird der Ansatz „Offset + Anzahl“ verwendet, bei dem im Antrag angegeben wird, von welcher Zeile im Ergebnis und wie viele Zeilen zurĂŒckgegeben werden sollen. Zuerst betrachten wir, wie man die Übergabe der Parameter in diesem Fall am besten organisiert. In der Praxis habe ich verschiedene Methoden gefunden:

  • Die Seitenzahl des angeforderten Dokuments (pageIndex), die SeitengrĂ¶ĂŸe (pageSize).
  • Die Reihenfolge der ersten zu zurĂŒckgebenden DatensĂ€tze (startIndex), die maximale Anzahl der DatensĂ€tze im Ergebnis (count).
  • Die Reihenfolge der ersten zu zurĂŒckgebenden DatensĂ€tze (startIndex), die Reihenfolge der letzten zu zurĂŒckgebenden DatensĂ€tze (endIndex).

Auf den ersten Blick könnte man denken, dass dies so einfach ist, dass es keinen Unterschied macht. Das ist aber nicht der Fall – die praktischste und universellste Variante ist die zweite (startIndex, count). DafĂŒr gibt es mehrere GrĂŒnde:

  • FĂŒr den Ansatz mit dem Lesen von +1 Datensatz, wie oben beschrieben, ist die erste Variante mit pageIndex und pageSize Ă€ußerst unpraktisch. Zum Beispiel wollen wir 50 DatensĂ€tze auf einer Seite anzeigen. Laut dem oben beschriebenen Algorithmus mĂŒssen wir einen Datensatz mehr lesen als nötig. Wenn dieses "+1" nicht auf dem Server berĂŒcksichtigt wird, bedeutet das, dass wir fĂŒr die erste Seite die DatensĂ€tze von 1 bis 51 anfordern mĂŒssen, fĂŒr die zweite von 51 bis 101 usw. Wenn wir die SeitengrĂ¶ĂŸe auf 51 festlegen und pageIndex erhöhen, gibt die zweite Seite die DatensĂ€tze von 52 bis 102 zurĂŒck usw. Dementsprechend ist im ersten Ansatz die einzige Möglichkeit, den Button fĂŒr die nĂ€chste Seite ordnungsgemĂ€ĂŸ zu implementieren, das Einrechnen eines "ĂŒberflĂŒssigen" Datensatzes auf dem Server, was ein sehr unauffĂ€lliger Aspekt sein wird.
  • Der dritte Ansatz macht ĂŒberhaupt keinen Sinn, da fĂŒr die DurchfĂŒhrung von Anfragen in den meisten Datenbanken dennoch die Anzahl und nicht der Index des letzten Datensatzes ĂŒbergeben werden muss. Es ist zwar eine einfache arithmetische Operation, das subtrahieren von startIndex von endIndex, aber sie ist hier ĂŒberflĂŒssig.

Nun sollten die Nachteile der Implementierung von Paging ĂŒber "Offset + Anzahl" beschrieben werden:

  • Das Abrufen jeder nĂ€chsten Seite wird aufwendiger und langsamer sein als die vorherige, da die Datenbank dennoch alle DatensĂ€tze "von Anfang an" entsprechend den Such- und Sortierkriterien durchgehen muss, bevor sie an dem benötigten Abschnitt stoppt.
  • Nicht alle Datenbankmanagementsysteme unterstĂŒtzen diesen Ansatz.

Es gibt Alternativen, aber auch diese sind nicht ideal. Der erste solcher AnsĂ€tze wird als „Keyset Paging“ oder „Seek-Methode“ bezeichnet und besteht darin, dass man nach dem Abrufen eines Teils die Werte der Felder im letzten Datensatz auf der Seite speichern kann, und diese dann verwenden, um den nĂ€chsten Teil abzurufen. Zum Beispiel haben wir eine solche Anfrage durchgefĂŒhrt:

SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

In dem letzten Eintrag erhielten wir den Bestellungstermin '2014-06-29'. Um die nĂ€chste Seite zu erhalten, könnte man versuchen, Folgendes auszufĂŒhren:

SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate < '2014-06-29'
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Das Problem ist, dass OrderDate kein eindeutiges Feld ist und die oben angegebene Bedingung mit hoher Wahrscheinlichkeit viele benötigte Zeilen ĂŒberspringen wird. Um Eindeutigkeit in diese Abfrage zu bringen, muss ein eindeutiges Feld zur Bedingung hinzugefĂŒgt werden (nehmen wir an, dass 75074 der letzte Wert des PrimĂ€rschlĂŒssels aus der ersten Charge ist):

SELECT * FROM Sales.SalesOrderHeader
WHERE (OrderDate = '2014-06-29' AND SalesOrderID < 75074)
   OR (OrderDate < '2014-06-29')
ORDER BY OrderDate DESC, SalesOrderID DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Diese Variante wird korrekt funktionieren, aber allgemein wird es schwierig sein, sie zu optimieren, da die Bedingung den Operator OR enthĂ€lt. Wenn der Wert des PrimĂ€rschlĂŒssels mit steigendem OrderDate wĂ€chst, kann die Bedingung vereinfacht werden, indem nur der Filter nach SalesOrderID beibehalten wird. Wenn jedoch zwischen den Werten des PrimĂ€rschlĂŒssels und dem Feld, nach dem das Ergebnis sortiert ist, keine strikte Korrelation besteht, wird es in den meisten DBMS nicht möglich sein, dieses OR zu vermeiden. Eine mir bekannte Ausnahme ist PostgreSQL, das den Vergleich von Tupeln vollumfĂ€nglich unterstĂŒtzt. Die oben genannte Bedingung kann als 'WHERE (OrderDate, SalesOrderID) < ('2014-06-29', 75074)' formuliert werden. Bei Vorhandensein eines zusammengesetzten SchlĂŒssels mit diesen beiden Feldern sollte eine solche Abfrage recht einfach sein.

Einen alternativen Ansatz findet man beispielsweise in ElasticSearch Scroll API oder Cosmos DB — wenn die Abfrage neben den Daten einen speziellen Identifikator zurĂŒckgibt, mit dem die nĂ€chste Charge von Daten abgerufen werden kann. Wenn dieser Identifikator unbegrenzte Lebensdauer hat (wie in Cosmos DB), ist es eine hervorragende Möglichkeit, Paging mit sequenziellen ÜbergĂ€ngen zwischen den Seiten zu implementieren (Variante #2, die oben erwĂ€hnt wurde). Mögliche Nachteile: wird nicht von allen DBMS unterstĂŒtzt; der erhaltene Identifikator der nĂ€chsten Charge kann eine begrenzte Lebensdauer haben, was im Allgemeinen nicht fĂŒr die Interaktion mit Benutzern geeignet ist (wie beispielsweise bei der ElasticSearch Scroll API).

Komplexe Filterung

Wir erschweren die Aufgabe weiter. Angenommen, es gibt die Anforderung, eine sogenannte facettierte Suche zu implementieren, die allen aus Online-Shops bekannt ist. Die oben genannten Beispiele basieren auf der Bestellungstabelle, sind in diesem Fall jedoch nicht sehr aussagekrÀftig, also wechseln wir zur Produkttabelle aus der AdventureWorks-Datenbank:

Suchergebnisse anzeigen und Leistungsprobleme
Was ist die Idee der facettierten Suche? Es ist so, dass fĂŒr jedes Filterelement die Anzahl der DatensĂ€tze angezeigt wird, die diesem Kriterium entsprechen. unter BerĂŒcksichtigung der Filter, die in allen anderen Kategorien ausgewĂ€hlt wurden..

Wenn wir in diesem Beispiel die Kategorie FahrrÀder und die Farbe Schwarz auswÀhlen, zeigt die Tabelle nur schwarze FahrrÀder an, aber dabei:

  • FĂŒr jedes Kriterium der Gruppe «Kategorien» wird die Anzahl der Produkte aus dieser Kategorie in Schwarz angezeigt.
  • FĂŒr jedes Kriterium der Gruppe «Farben» wird die Anzahl der FahrrĂ€der in dieser Farbe angezeigt.

Hier ist ein Beispiel fĂŒr die Ausgabe des Ergebnisses unter solchen Bedingungen:

Suchergebnisse anzeigen und Leistungsprobleme
Wenn wir zusĂ€tzlich die Kategorie «Bekleidung» auswĂ€hlen, zeigt die Tabelle auch schwarze Kleidung an, die verfĂŒgbar ist. Die Anzahl der Produkte in Schwarz in der Rubrik «Farben» wird ebenfalls gemĂ€ĂŸ den neuen Bedingungen neu berechnet, aber in der Rubrik «Kategorien» bleibt alles unverĂ€ndert
 Ich hoffe, diese Beispiele sind ausreichend, um den gewohnten Algorithmus der funktionsfĂ€higen Suche zu verstehen.

Stellen wir uns nun vor, wie dies in einer relationalen Datenbank umgesetzt werden kann. Jede Gruppe von Kriterien, wie Kategorie und Farbe, erfordert eine separate Abfrage:

SELECT pc.ProductCategoryID, pc.Name, COUNT(1) FROM Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
  INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
WHERE p.Color = 'Black'
GROUP BY pc.ProductCategoryID, pc.Name
ORDER BY COUNT(1) DESC

Suchergebnisse anzeigen und Leistungsprobleme

SELECT Color, COUNT(1) FROM Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE ps.ProductCategoryID = 1 --FahrrÀder
GROUP BY Color
ORDER BY COUNT(1) DESC

Suchergebnisse anzeigen und Leistungsprobleme
Was ist also das Problem mit dieser Lösung? Ganz einfach – sie ist schlecht skalierbar. Jeder Abschnitt des Filters erfordert eine separate Abfrage zur ZĂ€hlung der Mengen, und diese Abfragen sind nicht sehr leicht. In Online-Shops kann es in einigen Kategorien mehrere Dutzend Filterabschnitte geben, was zu einem ernsthaften Leistungsproblem werden kann.

Normalerweise schlagen mir nach diesen Aussagen einige Lösungen vor, nÀmlich:

  • Alle ZĂ€hlungen in eine Anfrage zusammenfassen. Technisch ist das möglich mit dem SchlĂŒsselwort UNION, allerdings hilft das der Leistung nicht wirklich — die Datenbank muss dennoch jeden der Teile "von Grund auf" ausfĂŒhren.
  • Mengen cachen. Das wird mir praktisch jedes Mal vorgeschlagen, wenn ich das Problem beschreibe. Der Knackpunkt ist, dass dies im Allgemeinen unmöglich ist. Angenommen, wir haben 10 „Facetten“, von denen jede 5 Werte hat. Dies ist eine sehr „modeste“ Situation im Vergleich zu dem, was man in denselben Online-Shops sehen kann. Die Auswahl eines Facettenelements beeinflusst die Mengen in 9 anderen, mit anderen Worten, fĂŒr jede Kombination von Kriterien können die Mengen unterschiedlich sein. In unserem Beispiel gibt es insgesamt 50 Kriterien, die der Benutzer auswĂ€hlen kann, was bedeutet, dass es 250 mögliche Kombinationen gibt. Um ein solches Datenarray zu fĂŒllen, reicht weder der Speicher noch die Zeit aus. Man könnte einwenden, dass nicht alle Kombinationen realistisch sind und die Benutzer selten mehr als 5-10 Kriterien auswĂ€hlen. Ja, es ist möglich, ein lazy loading und Caching der Menge nur fĂŒr das zu machen, was jemals ausgewĂ€hlt wurde, aber je mehr Auswahlmöglichkeiten es gibt, desto weniger effektiv wird dieses Cache und desto deutlicher werden die Probleme mit der Reaktionszeit (insbesondere wenn sich der Datensatz regelmĂ€ĂŸig Ă€ndert).

GlĂŒcklicherweise gibt es fĂŒr eine solche Aufgabe bereits seit langem ausreichend effiziente Lösungen, die vorhersehbar bei großen Datenmengen funktionieren. FĂŒr jede dieser Optionen ergibt es Sinn, die Neuberechnung der Facetten und das Abrufen der Ergebnisseite in zwei parallele Anfragen an den Server zu unterteilen und die BenutzeroberflĂ€che so zu gestalten, dass das Laden der Facettendaten die Anzeige der Suchergebnisse "nicht stört".

  • Rufen Sie die vollstĂ€ndige Neuberechnung der "Facetten" so selten wie möglich auf. Zum Beispiel sollten Sie nicht bei jeder Änderung der Suchkriterien alle berechnen, sondern stattdessen die Gesamtanzahl der Ergebnisse ermitteln, die den aktuellen Bedingungen entsprechen, und dem Benutzer anbieten, diese anzuzeigen – „1425 EintrĂ€ge gefunden, anzeigen?“ Der Benutzer kann entweder weiterhin die Suchkriterien Ă€ndern oder auf die SchaltflĂ€che „anzeigen“ klicken. Nur im zweiten Fall werden alle Anfragen zur Ergebnisbeschaffung und zur Neuberechnung der Anzahl der „Facetten“ ausgefĂŒhrt. Dabei ist es offensichtlich, dass man mit der Anfrage zur Ermittlung der Gesamtanzahl der Ergebnisse und deren Optimierung umgehen muss. Diese Vorgehensweise findet man in vielen kleinen Online-Shops. Offensichtlich ist dies kein Allheilmittel fĂŒr das Problem, kann aber in einfachen FĂ€llen ein guter Kompromiss sein.
  • Verwenden Sie Suchmaschinen wie Solr, ElasticSearch, Sphinx und andere zur Ergebnissuche und zur ZĂ€hlung der Facetten. All diese sind darauf ausgelegt, „Facetten“ zu generieren, und tun dies dank des invertierten Indexes recht effektiv. Wie Suchmaschinen funktionieren, warum sie in solchen FĂ€llen effektiver sind als allgemeine Datenbanken, welche Praktiken und Fallstricke es gibt – das ist ein Thema fĂŒr einen eigenen Artikel. Hier möchte ich darauf hinweisen, dass eine Suchmaschine nicht das Hauptdatenspeicher ersetzen kann; sie wird als ErgĂ€nzung verwendet: Alle Änderungen in der Hauptdatenbank, die fĂŒr die Suche von Bedeutung sind, werden mit dem Suchindex synchronisiert; der Suchmechanismus interagiert normalerweise nur mit der Suchmaschine und greift nicht auf die Hauptdatenbank zu. Einer der wichtigsten Punkte hierbei ist, wie man diese Synchronisation zuverlĂ€ssig organisiert. Alles hĂ€ngt von den Anforderungen an die „Reaktionszeit“ ab. Wenn die Zeit zwischen einer Änderung in der Hauptdatenbank und deren „Erscheinung“ in der Suche nicht kritisch ist, kann man einen Dienst erstellen, der alle paar Minuten nach kĂŒrzlich geĂ€nderten DatensĂ€tzen sucht und diese indexiert. Wenn die minimal mögliche Reaktionszeit erforderlich ist, kann man etwas wie transactional outbox zum Senden von Updates an den Suchdienst implementieren.

Das DBMS Tarantool ist ein attraktives, zukunftstrÀchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.

  1. Die Umsetzung von Paging auf der Serverseite ist eine ernsthafte KomplexitĂ€t und sollte nur fĂŒr schnell wachsende oder einfach große DatensĂ€tze sinnvoll eingesetzt werden. Es gibt kein absolut prĂ€zises Rezept, um »groĂŸÂ« oder »schnell wachsend« zu bewerten, aber ich wĂŒrde mich an einen solchen Ansatz halten:
    • Wenn das Abrufen einer vollstĂ€ndigen Datensammlung unter BerĂŒcksichtigung der Serverzeit und der NetzwerkĂŒbertragung innerhalb der Leistungsanforderungen liegt, macht ein Paging auf der Serverseite keinen Sinn.
    • Es kann die Situation eintreten, dass in naher Zukunft keine Leistungsprobleme zu erwarten sind, da die Datenmenge gering ist, die Datensammlung jedoch stĂ€ndig wĂ€chst. Wenn ein bestimmter Datensatz in der Zukunft möglicherweise die vorherige Anforderung nicht mehr erfĂŒllt, ist es besser, das Paging gleich von Anfang an vorzusehen.
  2. Wenn vonseiten des GeschÀfts keine strenge Forderung besteht, die Gesamtanzahl der Ergebnisse oder die Seitenzahlen anzuzeigen, und in Ihrem System kein Suchmaschinen-Engine vorhanden ist, ist es besser, diese Punkte nicht zu implementieren und die Variante #2 in Betracht zu ziehen.
  3. Wenn es eine klare Anforderung fĂŒr facettiertes Suchen gibt, haben Sie zwei Möglichkeiten, die Leistung nicht zu beeintrĂ€chtigen:
    • Nicht alle Mengen bei jeder Änderung der Suchkriterien neu zu berechnen.
    • Suchmaschinen wie Solr, ElasticSearch, Sphinx und andere zu verwenden. Man sollte jedoch verstehen, dass sie die Hauptdatenbank nicht ersetzen können und als ErgĂ€nzung zu dem Hauptspeicher fĂŒr die Lösung von Suchanfragen verwendet werden sollten.
  4. Auch im Falle des facettierten Suchens macht es Sinn, das Abrufen der Suchergebnisse und das ZĂ€hlen der Mengen in zwei parallele Anfragen zu unterteilen. Das ZĂ€hlen der Mengen kann lĂ€nger dauern als das Abrufen der Ergebnisse, wĂ€hrend die Ergebnisse fĂŒr den Benutzer wichtiger sind.
  5. Wenn Sie eine SQL-Datenbank fĂŒr die Suche verwenden, sollte jede CodeĂ€nderung in diesem Bereich gut auf Leistung bei den entsprechenden Datenmengen getestet werden (ĂŒber die Menge in einer »lebendigen« Datenbank). Es ist auch ratsam, die AusfĂŒhrungszeiten von Abfragen auf allen Datenbankinstanzen zu ĂŒberwachen, insbesondere auf der »lebenden«. Selbst wenn in der Entwicklungsphase mit den AbfrageplĂ€nen alles gut war, kann sich die Situation mit wachsender Datenmenge merklich Ă€ndern.

Quelle: habr.com

60GB SSD 8Gb DDR4