Ausgabe der Suchergebnisse und Leistungsprobleme

Eine der typischen Szenarien in allen gĂ€ngigen Anwendungen ist die Datenabfrage nach bestimmten Kriterien und die Ausgabe in einem leserfreundlichen Format. Hier können zusĂ€tzliche Funktionen wie Sortierung, Gruppierung und Seitenaufteilung hinzukommen. Theoretisch ist die Aufgabe trivial, aber viele Entwickler machen bei der Umsetzung eine Reihe von Fehlern, die die Leistung spĂ€ter beeintrĂ€chtigen. Lassen Sie uns verschiedene LösungsansĂ€tze betrachten und Empfehlungen fĂŒr die Auswahl der effizientesten Implementierung formulieren.

Ausgabe der Suchergebnisse und Leistungsprobleme

Seitenpaging-Option #1

Die naheliegendste und einfachste Option ist die klassische Seite-fĂŒr-Seite-Anzeige der Suchergebnisse.

Ausgabe der Suchergebnisse und Leistungsprobleme
Angenommen, im Anwendung wird eine relationale Datenbank verwendet. In diesem Fall mĂŒssen zwei SQL-Abfragen ausgefĂŒhrt werden, um die Informationen in dieser Form darzustellen:

  • Holen Sie sich die Zeilen fĂŒr die aktuelle Seite.
  • ZĂ€hlen Sie die Gesamtanzahl der Zeilen, die den Suchkriterien entsprechen — dies ist notwendig, um die Seiten anzuzeigen.

Lassen Sie uns die erste Abfrage am Beispiel der Testdatenbank MS SQL betrachten AdventureWorks fĂŒr den Server 2016. 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 einer Liste zurĂŒck, die nach dem Datum der HinzufĂŒgung in absteigender Reihenfolge sortiert ist – das heißt, die letzten 50 Bestellungen.

Sie wird schnell auf der Testdatenbank ausgefĂŒhrt, aber lassen Sie uns den AusfĂŒhrungsplan und die Ein- und Ausgabestatistiken betrachten:

Ausgabe der Suchergebnisse und Leistungsprobleme

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

Um die Ein- und Ausgabestatistiken fĂŒr jede Abfrage zu erhalten, fĂŒhren Sie in der Abfrageumgebung den Befehl SET STATISTICS IO ON aus.

Wie aus dem AusfĂŒhrungsplan ersichtlich ist, ist die Sortierung aller Zeilen der Ausgangstabelle nach dem HinzufĂŒgungsdatum die ressourcenintensivste Operation. Das Problem ist, dass die Sortierung umso 'aufwĂ€ndiger' wird, je mehr Zeilen in der Tabelle vorhanden sind. Solche Situationen sollten in der Praxis vermieden werden, deshalb fĂŒgen wir einen Index auf dem HinzufĂŒgungsdatum hinzu und prĂŒfen, ob sich der Ressourcenverbrauch geĂ€ndert hat:

Ausgabe der Suchergebnisse und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-Anzahl 1, logische LesevorgÀnge 165, physische LesevorgÀnge 0, Vorab-LesevorgÀnge 5, lob logische LesevorgÀnge 0, lob physische LesevorgÀnge 0, lob Vorab-LesevorgÀnge 0.

Offensichtlich hat sich die Situation deutlich verbessert. Aber sind alle Probleme gelöst? Lassen Sie uns die Anfrage Ă€ndern, um Bestellungen zu suchen, bei denen der Gesamtwert der Waren 100 Dollar ĂŒberschreitet:

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

Ausgabe der Suchergebnisse und Leistungsprobleme

Tabelle 'SalesOrderHeader'. Scan-Anzahl 1, logische Leseoperationen 1081, physische Leseoperationen 0, Vorauslesen 0, lob logische Leseoperationen 0, lob physische Leseoperationen 0, lob Vorauslesen 0.

Wir haben eine interessante Situation: Der Abfrageplan ist nicht viel schlechter als der vorherige, aber die tatsĂ€chliche Anzahl an logischen LesevorgĂ€ngen ist fast doppelt so hoch wie bei einem vollstĂ€ndigen Tabellenscan. Es gibt einen Ausweg — wenn wir den bestehenden Index zu einem zusammengesetzten machen und das Gesamtpreisfeld als zweites Feld hinzufĂŒgen, erhalten wir wieder 165 logische LesevorgĂ€nge:

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

Diese Beispielreihe könnte noch lange fortgesetzt werden, aber zwei Hauptgedanken, die ich hier zum Ausdruck bringen möchte, sind folgende:

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

Lassen Sie uns zum zweiten Abfrageanliegen ĂŒbergehen, das zu Beginn erwĂ€hnt wurde – nĂ€mlich zu dem, das die Anzahl der DatensĂ€tze zĂ€hlt, die dem Suchkriterium entsprechen. Nehmen wir dasselbe Beispiel – die Suche nach Bestellungen, die mehr als 100 Dollar kosten:

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

Vorhanden ist ein zusammengesetzter Index, wie oben angegeben:

Ausgabe der Suchergebnisse und Leistungsprobleme

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

Es ist nicht ĂŒberraschend, dass die Abfrage den gesamten Index durchlĂ€uft, da das Feld SubTotal nicht an erster Stelle steht. Daher kann die Abfrage nicht darauf zugreifen. Das Problem wird gelöst, indem ein weiterer Index fĂŒr das Feld SubTotal hinzugefĂŒgt wird, und letztendlich ergeben sich nur 48 logische Reads.

Es können weitere Beispiele fĂŒr Abfragen zur ZĂ€hlung angegeben werden, aber das Prinzip bleibt dasselbe: Das Abrufen von DatensĂ€tzen und das ZĂ€hlen der Gesamtanzahl sind zwei grundlegend unterschiedliche Abfragen, und jede erfordert eigene Optimierungsmaßnahmen. 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 zu klĂ€ren ist, ob es fĂŒr das Unternehmen wichtig ist, die Gesamtanzahl der gefundenen Objekte zu sehen. HĂ€ufig ist dies nicht der Fall. Und meiner Meinung nach ist die Navigation zu bestimmten Seitenzahlen eine Lösung mit sehr begrenztem Anwendungsbereich, da die meisten Szenarien mit Paging so aussehen wie „gehe zur nĂ€chsten Seite“.

Paging-Option #2

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

Ausgabe der Suchergebnisse und Leistungsprobleme
TatsĂ€chlich hat sich nur geĂ€ndert, dass es keine Möglichkeit mehr gibt, zu bestimmten Seitenzahlen zu navigieren, und nun benötigt diese Tabelle keine Kenntnisse darĂŒber, wie viele Seiten insgesamt existieren könnten. Aber es stellt sich die Frage – wie erfĂ€hrt die Tabelle, ob Daten fĂŒr die nĂ€chste Seite vorhanden sind (um den Link „Weiter“ korrekt anzuzeigen)?

Die Antwort ist recht einfach: Man kann aus der Datenbank einen Datensatz mehr auslesen, als zur Anzeige benötigt wird. Das Vorhandensein dieses „zusĂ€tzlichen“ Datensatzes zeigt an, ob es eine weitere Charge gibt. Dadurch benötigt man fĂŒr das Abrufen einer Datenseite nur einen einzigen API-Aufruf, was die Leistung erheblich verbessert und die Wartung dieser FunktionalitĂ€t erleichtert. In der Praxis hatte ich einen Fall, in dem der Verzicht auf die ZĂ€hlung der GesamtdatensĂ€tze die Ergebnisausgabe um das 4- bis 5-Fache beschleunigte.

FĂŒr diesen Ansatz gibt es mehrere Optionen fĂŒr die BenutzeroberflĂ€che: die SchaltflĂ€chen „ZurĂŒck“ und „Weiter“, wie im obigen Beispiel, die SchaltflĂ€che „Mehr laden“, die einfach eine neue Charge zu den angezeigten Ergebnissen hinzufĂŒgt, sowie das „endlose Scrollen“, das nach dem Prinzip von „mehr laden“ funktioniert, wobei das Signal fĂŒr das Abrufen der nĂ€chsten Charge das Durchscrollen aller angezeigten Ergebnisse bis zum Ende durch den Benutzer ist. UnabhĂ€ngig von der visuellen Lösung bleibt das Prinzip der Datenauswahl dasselbe.

Nuancen der Implementierung von Paging

In allen oben genannten Beispielen der Abfragen wird der Ansatz "Verschiebung + Menge" verwendet, bei dem im Abfrage angeben wird, von welcher Ergebniszeile und wie viele Zeilen zurĂŒckgegeben werden sollen. Lassen Sie uns zunĂ€chst betrachten, wie die Übergabe der Parameter in diesem Fall am besten organisiert werden kann. In der Praxis habe ich mehrere Möglichkeiten gefunden:

  • Die Seitenzahl (pageIndex) sowie die SeitengrĂ¶ĂŸe (pageSize).
  • Die Reihenfolge der ersten zurĂŒckzugebenden DatensĂ€tze (startIndex) und die maximale Anzahl der DatensĂ€tze im Ergebnis (count).
  • Die Reihenfolge der ersten zurĂŒckzugebenden DatensĂ€tze (startIndex) sowie die Reihenfolge des letzten zurĂŒckzugebenden Datensatzes (endIndex).

Auf den ersten Blick mag es einfach erscheinen, sodass es keinen Unterschied gibt. Das ist jedoch nicht der Fall – die bequemste und universellste Variante ist die zweite (startIndex, count). DafĂŒr gibt es mehrere GrĂŒnde:

  • FĂŒr den Ansatz mit einer Lesung von +1 Datensatz, wie oben dargestellt, ist die erste Variante mit pageIndex und pageSize Ă€ußerst unpraktisch. Zum Beispiel möchten wir 50 DatensĂ€tze pro Seite anzeigen. Laut dem oben genannten Algorithmus mĂŒssen mehr DatensĂ€tze gelesen werden, als tatsĂ€chlich benötigt werden. Wenn dieses „+1“ nicht serverseitig eingeplant ist, mĂŒssen wir fĂŒr die erste Seite die DatensĂ€tze von 1 bis 51 anfordern, fĂŒr die zweite von 51 bis 101 usw. Wenn wir die Seitenanzahl auf 51 festlegen und den pageIndex erhöhen, wird die zweite Seite die DatensĂ€tze von 52 bis 102 zurĂŒckgeben usw. Somit besteht der einzige Weg, die SchaltflĂ€che zum Wechseln zur nĂ€chsten Seite vernĂŒnftig zu implementieren, darin, auf dem Server eine Lesung der â€žĂŒberschĂŒssigen“ Zeile einzuplanen, was ein sehr unauffĂ€lliger Aspekt ist.
  • Die dritte Variante hat ĂŒberhaupt keinen Sinn, da fĂŒr die AusfĂŒhrung von Abfragen in den meisten Datenbanken dennoch die Anzahl und nicht der Index des letzten Datensatzes ĂŒbergeben werden muss. Auch wenn die Subtraktion von startIndex aus endIndex und eine elementare arithmetische Operation ist, ist sie hier ĂŒberflĂŒssig.

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

  • Der Zugriff auf jede nĂ€chste Seite wird teurer und langsamer sein als der vorherige, da die Datenbank alle EintrĂ€ge gemĂ€ĂŸ den Such- und Sortierkriterien von „anfangs“ durchgehen muss, um dann am gewĂŒnschten Abschnitt zu stoppen.
  • Nicht alle DBMS können diesen Ansatz unterstĂŒtzen.

Es gibt Alternativen, aber auch diese sind nicht perfekt. Der erste dieser AnsĂ€tze wird als „Keyset Paging“ oder „Seek-Methode“ bezeichnet und funktioniert wie folgt: Nach dem Abrufen einer Menge kann man die Werte der Felder im letzten Eintrag auf der Seite speichern und diese dann fĂŒr den Zugriff auf die nĂ€chste Menge verwenden. Zum Beispiel haben wir diese Abfrage durchgefĂŒhrt:

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

Und im letzten Eintrag erhielten wir den Wert des Bestelldatums ‚2014-06-29‘. Dann könnte man fĂŒr den Zugriff auf die nĂ€chste Seite 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 ĂŒberspringt. Um Klarheit in diese Abfrage zu bringen, muss ein eindeutiges Feld 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 im Allgemeinen 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 zunimmt, kann die Bedingung vereinfacht werden, indem nur der Filter nach SalesOrderID beibehalten wird. Wenn jedoch keine strenge Korrelation zwischen den Werten des PrimĂ€rschlĂŒssels und dem Feld, nach dem das Ergebnis sortiert ist, besteht, lĂ€sst sich dieses OR in den meisten DBMS nicht vermeiden. Eine bekannte Ausnahme ist PostgreSQL, wo der Vergleich von Tupeln vollstĂ€ndig unterstĂŒtzt wird und die oben angegebene Bedingung als „WHERE (OrderDate, SalesOrderID) < (‘2014-06-29’, 75074)“ aufgezeichnet werden kann. Wenn ein zusammengesetzter SchlĂŒssel mit diesen beiden Feldern vorhanden ist, sollte eine solche Abfrage relativ leicht sein.

Ein zweiter alternativer Ansatz findet sich beispielsweise in ElasticSearch Scroll-API oder Cosmos DB — wenn die Anfrage neben den Daten eine spezielle Kennung zurĂŒckgibt, mit der man die nĂ€chste Datenmenge abrufen kann. Wenn diese Kennung unbegrenzte Lebensdauer hat (wie in Cosmos DB), dann ist das eine ausgezeichnete Möglichkeit, Paging mit sequenziellen SeitenĂŒbergĂ€ngen zu realisieren (wie in Variante #2 oben erwĂ€hnt). Mögliche Nachteile: wird nicht von allen DBMS unterstĂŒtzt; die erhaltene Kennung der nĂ€chsten Datenmenge kann eine begrenzte Lebensdauer haben, was im Allgemeinen fĂŒr die Benutzerinteraktion nicht geeignet ist (wie zum Beispiel die ElasticSearch Scroll-API).

Komplexe Filterung

Lassen Sie uns die Aufgabe weiter verkomplizieren. 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 Bestelltabelle sind in diesem Fall nicht sehr aussagekrÀftig, also wechseln wir zur Produkttabelle aus der AdventureWorks-Datenbank:

Ausgabe der Suchergebnisse und Leistungsprobleme
Was ist die Idee der facettierten Suche? Dass fĂŒr jedes Filterelement die Anzahl der EintrĂ€ge angezeigt wird, die dem Kriterium entsprechen. unter BerĂŒcksichtigung der Filter, die in allen anderen Kategorien ausgewĂ€hlt wurden.

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

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

Hier ist ein Beispiel fĂŒr die Ausgabe eines solchen Ergebnisses:

Ausgabe der Suchergebnisse und Leistungsprobleme
Wenn außerdem die Kategorie „Bekleidung“ markiert ist, zeigt die Tabelle auch die schwarze Kleidung, die verfĂŒgbar ist. Die Anzahl der Produkte in der Sektion „Farben“ wird ebenfalls gemĂ€ĂŸ den neuen Bedingungen neu berechnet, wobei sich in der Sektion „Kategorien“ nichts Ă€ndern wird
 Ich hoffe, diese Beispiele sind ausreichend, um den gewohnten Algorithmus der facettierten Suche zu verstehen.

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

WÄHLEN Sie pc.ProductCategoryID, pc.Name, COUNT(1) VON Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
  INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
WO p.Farbe = 'Schwarz'
GRUPPIEREN NACH pc.ProductCategoryID, pc.Name
ORDERN NACH COUNT(1) DESC

Ausgabe der Suchergebnisse und Leistungsprobleme

WÄHLEN Sie Farbe, COUNT(1) VON Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WO ps.ProductCategoryID = 1 --FahrrÀder
GRUPPIEREN NACH Farbe
ORDERN NACH COUNT(1) DESC

Ausgabe der Suchergebnisse und Leistungsprobleme
Was ist das Problem mit dieser Lösung? Ganz einfach – sie lĂ€sst sich schlecht skalieren. Jedes Filtersegment benötigt eine separate Anfrage zur ZĂ€hlung der Anzahl, und diese Anfragen sind nicht gerade leicht. In Online-Shops können in bestimmten Kategorien mehrere Dutzend Filtersegmente vorhanden sein, was eine ernsthafte Herausforderung fĂŒr die Performance darstellen kann.

In der Regel werden mir nach diesen Aussagen einige Lösungen angeboten, nÀmlich:

  • Alle ZĂ€hlungen in einer Anfrage zu kombinieren. Technisch gesehen ist das mit dem SchlĂŒsselwort UNION möglich, aber die Performance wird dadurch nicht signifikant verbessert – die Datenbank muss dennoch jeden der Fragmente von Grund auf ausfĂŒhren.
  • Die Menge zu cachen, wird mir praktisch jedes Mal vorgeschlagen, wenn ich ein Problem beschreibe. Der Haken dabei ist, dass dies im Allgemeinen unmöglich ist. Nehmen wir an, wir haben 10 "Facetten", von denen jede 5 Werte hat. Das ist eine sehr "modeste" Situation im Vergleich zu dem, was man in den gleichen 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. Insgesamt gibt es in unserem Beispiel 50 Kriterien, die der Benutzer auswĂ€hlen kann; entsprechend wird es 250 mögliche Kombinationen geben. Um ein solches Datenarray zu fĂŒllen, reicht weder der Speicher noch die Zeit aus. Hier könnte man dagegen einwenden, dass nicht alle Kombinationen realistisch sind und Benutzer selten mehr als 5-10 Kriterien auswĂ€hlen. Ja, man könnte eine lazy Loading- und Caching-Lösung nur fĂŒr das implementieren, was jemals ausgewĂ€hlt wurde, aber je mehr Auswahlmöglichkeiten es gibt, desto weniger effektiv wird dieses Cache und desto sichtbarere werden die Probleme mit der Reaktionszeit sein (insbesondere wenn sich der Datensatz regelmĂ€ĂŸig Ă€ndert).

GlĂŒcklicherweise gibt es schon seit lĂ€ngerer Zeit effektive Lösungen fĂŒr diese Art von Aufgabe, die zuverlĂ€ssig mit großen Datenmengen arbeiten. FĂŒr jede dieser Optionen ist es sinnvoll, die Berechnung der Facetten und das Abrufen der Ergebnisse in zwei parallele Serveranfragen aufzuteilen. Zudem sollte die BenutzeroberflĂ€che so gestaltet werden, dass das Laden der Daten zu den Facetten die Anzeige der Suchergebnisse nicht stört.

  • Versuchen Sie, die vollstĂ€ndige Neuberechnung der "Fasetten" so selten wie möglich auszufĂŒhren. Berechnen Sie zum Beispiel nicht alles bei jeder Änderung der Suchkriterien, sondern ermitteln Sie stattdessen die Gesamtzahl der Ergebnisse, die den aktuellen Bedingungen entsprechen, und schlagen Sie dem Benutzer vor, diese anzuzeigen: "1425 EintrĂ€ge gefunden, anzeigen?" Der Benutzer kann entweder die Suchkriterien weiter anpassen oder auf die SchaltflĂ€che „anzeigen“ klicken. Nur im zweiten Fall werden alle Abfragen zur Ergebniserfassung und Neuberechnung der Mengen in allen "Fasetten" ausgefĂŒhrt. Dabei mĂŒssen Sie, wie leicht zu erkennen ist, mit einer Anfrage zur Ermittlung der Gesamtzahl der Ergebnisse und deren Optimierung umgehen. Diese Methode findet man hĂ€ufig in vielen kleinen Online-Shops. Offensichtlich ist dies kein Allheilmittel fĂŒr dieses Problem, kann aber in einfachen FĂ€llen ein akzeptabler Kompromiss sein.
  • Verwenden Sie eine Suchmaschine, um Ergebnisse zu suchen und Facetten zu zĂ€hlen, wie Solr, ElasticSearch, Sphinx und andere. Alle sind fĂŒr den Aufbau von „Facetten“ konzipiert und tun dies recht effizient durch Hilfe eines invertierten Indexes. Wie Suchmaschinen funktionieren, warum sie in solchen FĂ€llen effektiver sind als allgemeine Datenbanken, welche Praktiken und Fallstricke es gibt – das ist Thema fĂŒr einen eigenen Artikel. Hier möchte ich darauf hinweisen, dass eine Suchmaschine kein Ersatz fĂŒr das primĂ€re Datenspeicher ist, sondern als ErgĂ€nzung genutzt wird: Alle Änderungen in der Hauptdatenbank, die fĂŒr die Suche relevant sind, werden im 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 der Änderung in der Hauptdatenbank und deren „Manifestation“ in der Suche nicht kritisch ist, kann ein Dienst eingerichtet werden, der alle paar Minuten nach zuletzt geĂ€nderten DatensĂ€tzen sucht und sie indiziert. Wenn die minimal mögliche Reaktionszeit erforderlich ist, kann etwas Ähnliches wie transaktionale Ausgangsbotschaft zum Senden von Updates an den Suchdienst.

Fazit

  1. Die Implementierung von Paging auf der Serverseite ist eine ernsthafte Komplikation und sollte nur fĂŒr schnell wachsende oder einfach große DatensĂ€tze angewendet werden. Es gibt kein absolut prĂ€zises Rezept dafĂŒr, was "groß" oder "schnell wachsend" ist, aber ich wĂŒrde diesen Ansatz verfolgen:
    • Wenn das Abrufen der vollstĂ€ndigen Datensammlung unter BerĂŒcksichtigung der Serverzeiten und der Netzwerkauslastung innerhalb der Leistungsanforderungen liegt, macht die Umsetzung von Paging auf der Serverseite wenig Sinn.
    • Es kann vorkommen, dass vorerst keine Leistungsprobleme zu erwarten sind, da es nur wenige Daten gibt, die Datensammlung jedoch stĂ€ndig wĂ€chst. Wenn ein Datensatz in der Zukunft möglicherweise nicht mehr den vorherigen Punkt erfĂŒllt, ist es besser, Paging direkt einzuplanen.
  2. Wenn es von der GeschÀftseite keine strengen Anforderungen an die Gesamtanzahl der Ergebnisse oder an die Anzeige der Seitenzahlen gibt und Ihr System keinen Suchmechanismus besitzt, ist es besser, diese Punkte nicht umzusetzen und Variante #2 in Betracht zu ziehen.
  3. Wenn es eine klare Anforderung an die facettierte Suche gibt, haben Sie zwei Optionen, um die Leistung nicht zu beeintrÀchtigen:
    • ZĂ€hlen Sie nicht alle Mengen bei jeder Änderung der Suchkriterien neu.
    • Verwenden Sie Suchmaschinen wie Solr, ElasticSearch, Sphinx und andere. Es ist jedoch wichtig zu verstehen, dass sie keine Ersatzlösung fĂŒr die Hauptdatenbank darstellen und als ErgĂ€nzung zum Hauptspeicher fĂŒr die Lösung von Suchaufgaben genutzt werden sollten.
  4. Auch im Fall der facettierten Suche ist es sinnvoll, das Abrufen der Suchergebnisseite 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 SuchvorgĂ€nge verwenden, sollten alle CodeĂ€nderungen, die sich auf diesen Bereich beziehen, grĂŒndlich auf deren Leistung mit einem entsprechenden Datenvolumen (grĂ¶ĂŸer als das Volumen in der „Live“-Datenbank) getestet werden. Es ist außerdem ratsam, die AbfrageausfĂŒhrungszeiten auf allen Datenbankinstanzen zu ĂŒberwachen, insbesondere auf der „Live“-Instanz. Selbst wenn in der Entwicklungsphase mit den AbfrageplĂ€nen alles gut war, kann sich die Situation mit wachsendem Datenvolumen erheblich Ă€ndern.

Quelle: habr.com

ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen đŸ”„ ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster