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

Erwerben Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster