Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Eines der typischen Szenarien in allen uns bekannten Anwendungen ist die Suche nach Daten nach bestimmten Kriterien und deren übersichtliche Darstellung. Möglicherweise gibt es auch zusätzliche Optionen zum Sortieren, Gruppieren und Paginieren. Die Aufgabe ist theoretisch trivial, doch bei der Lösung machen viele Entwickler eine Reihe von Fehlern, die später zu Produktivitätseinbußen führen. Versuchen wir, verschiedene Optionen zur Lösung dieses Problems in Betracht zu ziehen und Empfehlungen für die Auswahl der effektivsten Implementierung zu formulieren.

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Paging-Option Nr. 1

Die einfachste Option, die mir in den Sinn kommt, ist eine seitenweise Anzeige der Suchergebnisse in ihrer klassischsten Form.

Probleme bei der Ausgabe von Suchergebnissen und der Leistung
Nehmen wir an, Ihre Anwendung verwendet eine relationale Datenbank. In diesem Fall müssen Sie zwei SQL-Abfragen ausführen, um Informationen in diesem Formular anzuzeigen:

  • Zeilen für die aktuelle Seite abrufen.
  • Berechnen Sie die Gesamtzahl der Zeilen, die den Suchkriterien entsprechen. Diese ist für die Anzeige von Seiten erforderlich.

Schauen wir uns die erste Abfrage am Beispiel einer Test-MS-SQL-Datenbank an AbenteuerWorks für 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, sortiert nach absteigendem Datum der Hinzufügung, also die 50 aktuellsten Bestellungen.

Es läuft schnell auf der Testbasis, aber schauen wir uns den Ausführungsplan und die E/A-Statistiken an:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Sie können E/A-Statistiken für jede Abfrage abrufen, indem Sie den Befehl SET STATISTICS IO ON in der Abfragelaufzeit ausführen.

Wie Sie dem Ausführungsplan entnehmen können, besteht die ressourcenintensivste Option darin, alle Zeilen der Quelltabelle nach dem Hinzufügungsdatum zu sortieren. Und das Problem ist, dass die Sortierung umso „schwieriger“ wird, je mehr Zeilen in der Tabelle erscheinen. In der Praxis sollten solche Situationen vermieden werden. Fügen wir also einen Index zum Datum der Hinzufügung hinzu und prüfen Sie, ob sich der Ressourcenverbrauch geändert hat:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Table 'SalesOrderHeader'. Scan count 1, logical reads 165, physical reads 0, read-ahead reads 5, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Offensichtlich ist es viel besser geworden. Aber sind alle Probleme gelöst? Ändern wir die Abfrage, um nach Bestellungen zu suchen, bei denen die Gesamtkosten der Waren 100 $ übersteigen:

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

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Table 'SalesOrderHeader'. Scan count 1, logical reads 1081, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Wir haben eine lustige Situation: Der Abfrageplan ist nicht viel schlechter als der vorherige, aber die tatsächliche Anzahl logischer Lesevorgänge ist fast doppelt so groß wie bei einem vollständigen Tabellenscan. Es gibt einen Ausweg: Wenn wir einen zusammengesetzten Index aus einem bereits vorhandenen Index erstellen und den Gesamtpreis der Waren als zweites Feld hinzufügen, erhalten wir erneut 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 die beiden Hauptgedanken, die ich hier zum Ausdruck bringen möchte, sind:

  • Das Hinzufügen eines neuen Kriteriums oder einer neuen Sortierreihenfolge zu einer Suchabfrage kann erhebliche Auswirkungen auf die Geschwindigkeit der Suchabfrage haben.
  • Wenn wir jedoch nur einen Teil der Daten und nicht alle Ergebnisse, die den Suchbegriffen entsprechen, subtrahieren müssen, gibt es viele Möglichkeiten, eine solche Abfrage zu optimieren.

Kommen wir nun zur zweiten Abfrage, die ganz am Anfang erwähnt wurde – diejenige, die die Anzahl der Datensätze zählt, die das Suchkriterium erfüllen. Nehmen wir das gleiche Beispiel – die Suche nach Bestellungen über 100 $:

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

Unter Berücksichtigung des oben angegebenen zusammengesetzten Index erhalten wir:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Die Tatsache, dass die Abfrage den gesamten Index durchläuft, ist nicht überraschend, da sich das Feld „SubTotal“ nicht an der ersten Position befindet und die Abfrage es daher nicht verwenden kann. Das Problem wird gelöst, indem ein weiterer Index zum Feld „SubTotal“ hinzugefügt wird. Dadurch werden nur 48 logische Lesevorgänge durchgeführt.

Sie können noch ein paar weitere Beispiele für Anfragen zur Mengenzählung nennen, aber das Wesentliche bleibt dasselbe: Das Empfangen einer Datenmenge und das Zählen der Gesamtmenge sind zwei grundlegend unterschiedliche Anforderungen, und jedes erfordert seine eigenen Maßnahmen zur Optimierung. Im Allgemeinen werden Sie keine Kombination von Indizes finden, die für beide Abfragen gleich gut funktioniert.

Dementsprechend ist eine der wichtigen Anforderungen, die bei der Entwicklung einer solchen Suchlösung geklärt werden sollte, ob es für ein Unternehmen wirklich wichtig ist, die Gesamtzahl der gefundenen Objekte zu sehen. Es kommt oft vor, dass nein. Und die Navigation nach bestimmten Seitenzahlen ist meiner Meinung nach eine Lösung mit sehr begrenztem Umfang, da die meisten Paging-Szenarien wie „Gehe zur nächsten Seite“ aussehen.

Paging-Option Nr. 2

Nehmen wir an, dass es Benutzern egal ist, wie viele Objekte insgesamt gefunden wurden. Versuchen wir, die Suchseite zu vereinfachen:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung
Tatsächlich hat sich lediglich geändert, dass es keine Möglichkeit gibt, zu bestimmten Seitenzahlen zu navigieren, und dass diese Tabelle jetzt nicht wissen muss, wie viele es sein können, um sie anzuzeigen. Es stellt sich jedoch die Frage: Woher weiß die Tabelle, ob Daten für die nächste Seite vorhanden sind (um den Link „Weiter“ korrekt anzuzeigen)?

Die Antwort ist ganz einfach: Sie können einen Datensatz mehr aus der Datenbank lesen, als für die Anzeige benötigt wird, und das Vorhandensein dieses „zusätzlichen“ Datensatzes zeigt an, ob es einen nächsten Teil gibt. Auf diese Weise müssen Sie nur eine Anfrage ausführen, um eine Datenseite abzurufen, was die Leistung erheblich verbessert und die Unterstützung dieser Funktionalität erleichtert. In meiner Praxis gab es einen Fall, in dem die Weigerung, die Gesamtzahl der Datensätze zu zählen, die Lieferung der Ergebnisse um das Vier- bis Fünffache beschleunigte.

Für diesen Ansatz gibt es mehrere Benutzeroberflächenoptionen: „Zurück“- und „Vorwärts“-Befehle, wie im obigen Beispiel, eine Schaltfläche „Mehr laden“, die einfach einen neuen Teil zu den angezeigten Ergebnissen hinzufügt, „Unendlicher Bildlauf“, was funktioniert nach dem Prinzip „mehr laden“, aber das Signal zum Erhalt der nächsten Portion besteht darin, dass der Benutzer alle angezeigten Ergebnisse bis zum Ende scrollt. Unabhängig von der visuellen Lösung bleibt das Prinzip der Datenerfassung dasselbe.

Nuancen der Paging-Implementierung

Alle oben angegebenen Abfragebeispiele verwenden den „Offset + Count“-Ansatz, bei dem die Abfrage selbst angibt, in welcher Reihenfolge die Ergebniszeilen und wie viele Zeilen zurückgegeben werden müssen. Schauen wir uns zunächst an, wie die Parameterübergabe in diesem Fall am besten organisiert wird. In der Praxis bin ich auf mehrere Methoden gestoßen:

  • Die Seriennummer der angeforderten Seite (pageIndex), Seitengröße (pageSize).
  • Die Seriennummer des ersten zurückzugebenden Datensatzes (startIndex), die maximale Anzahl der Datensätze im Ergebnis (count).
  • Die Sequenznummer des ersten zurückgegebenen Datensatzes (startIndex), die Sequenznummer des letzten zurückgegebenen Datensatzes (endIndex).

Auf den ersten Blick mag es scheinen, dass dies so elementar ist, dass es keinen Unterschied gibt. Dies ist jedoch nicht der Fall – die bequemste und universellste Option ist die zweite (startIndex, count). Dafür gibt es mehrere Gründe:

  • Für den oben angegebenen Ansatz zum Korrekturlesen von +1-Einträgen ist die erste Option mit pageIndex und pageSize äußerst unpraktisch. Wir möchten beispielsweise 50 Beiträge pro Seite anzeigen. Gemäß dem obigen Algorithmus müssen Sie einen Datensatz mehr als nötig lesen. Wenn dieses „+1“ nicht auf dem Server implementiert ist, müssen wir für die erste Seite Datensätze von 1 bis 51 anfordern, für die zweite Seite von 51 bis 101 usw. Wenn Sie eine Seitengröße von 51 angeben und pageIndex erhöhen, wird die zweite Seite von 52 auf 102 usw. zurückgesetzt. Dementsprechend besteht bei der ersten Option die einzige Möglichkeit, eine Schaltfläche zum Wechseln zur nächsten Seite ordnungsgemäß zu implementieren, darin, die „zusätzliche“ Zeile vom Server Korrektur lesen zu lassen, was eine sehr implizite Nuance darstellt.
  • Die dritte Option macht überhaupt keinen Sinn, da Sie zum Ausführen von Abfragen in den meisten Datenbanken immer noch die Anzahl und nicht den Index des letzten Datensatzes übergeben müssen. Das Subtrahieren von startIndex von endIndex mag eine einfache arithmetische Operation sein, ist aber hier überflüssig.

Nun sollten wir die Nachteile der Implementierung von Paging durch „Offset + Menge“ beschreiben:

  • Das Abrufen jeder weiteren Seite ist teurer und langsamer als das vorherige, da die Datenbank weiterhin alle Datensätze „von Anfang an“ gemäß den Such- und Sortierkriterien durchgehen und dann beim gewünschten Fragment anhalten muss.
  • Nicht alle DBMS können diesen Ansatz unterstützen.

Es gibt Alternativen, aber auch sie sind unvollkommen. Der erste dieser Ansätze heißt „Keyset-Paging“ oder „Suchmethode“ und lautet wie folgt: Nach dem Empfang eines Teils können Sie sich die Feldwerte im letzten Datensatz auf der Seite merken und sie dann zum Abrufen verwenden die nächste Portion. Wir haben beispielsweise die folgende Abfrage ausgeführt:

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

Und im letzten Datensatz haben wir den Bestelldatumswert „2014“ erhalten. Um dann zur nächsten Seite zu gelangen, können Sie Folgendes versuchen:

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

Das Problem besteht darin, dass OrderDate ein nicht eindeutiges Feld ist und die oben angegebene Bedingung wahrscheinlich viele erforderliche Zeilen übersieht. Um dieser Abfrage Eindeutigkeit zu verleihen, müssen Sie der Bedingung ein eindeutiges Feld hinzufügen (angenommen, 75074 ist der letzte Wert des Primärschlüssels aus dem ersten Teil):

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 Option funktioniert ordnungsgemäß, ist jedoch im Allgemeinen schwierig zu optimieren, da die Bedingung einen ODER-Operator enthält. Wenn der Wert des Primärschlüssels mit zunehmendem OrderDate zunimmt, kann die Bedingung vereinfacht werden, indem nur ein Filter nach SalesOrderID belassen wird. Wenn jedoch keine strikte Korrelation zwischen den Werten des Primärschlüssels und dem Feld besteht, nach dem das Ergebnis sortiert wird, kann dieses ODER in den meisten DBMS nicht vermieden werden. Eine mir bekannte Ausnahme ist PostgreSQL, das Tupelvergleiche vollständig unterstützt, und die obige Bedingung kann als „WHERE (OrderDate, SalesOrderID) < ('2014-06-29', 75074)“ geschrieben werden. Bei einem zusammengesetzten Schlüssel mit diesen beiden Feldern sollte eine solche Abfrage recht einfach sein.

Ein zweiter alternativer Ansatz findet sich beispielsweise in ElasticSearch-Scroll-API oder Kosmos DB — wenn eine Anfrage zusätzlich zu den Daten einen speziellen Bezeichner zurückgibt, mit dem Sie den nächsten Teil der Daten abrufen können. Wenn dieser Bezeichner eine unbegrenzte Lebensdauer hat (wie in Comsos DB), dann ist dies eine großartige Möglichkeit, Paging mit sequentiellem Übergang zwischen Seiten zu implementieren (Option Nr. 2 oben erwähnt). Mögliche Nachteile: Es wird nicht in allen DBMS unterstützt; Die resultierende Next-Chunk-ID hat möglicherweise eine begrenzte Lebensdauer, was im Allgemeinen nicht für die Implementierung von Benutzerinteraktionen geeignet ist (z. B. die ElasticSearch-Scroll-API).

Komplexe Filterung

Machen wir die Aufgabe noch komplizierter. Angenommen, es besteht die Anforderung, die sogenannte Facettensuche zu implementieren, die jedem aus Online-Shops bestens bekannt ist. Die obigen Beispiele, die auf der Tabelle „Orders“ basieren, sind in diesem Fall nicht sehr anschaulich. Wechseln wir also zur Tabelle „Product“ aus der AdventureWorks-Datenbank:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung
Was ist die Idee hinter der Facettensuche? Fakt ist, dass für jedes Filterelement die Anzahl der Datensätze angezeigt wird, die dieses Kriterium erfüllen unter Berücksichtigung der in allen anderen Kategorien ausgewählten Filter.

Wenn wir in diesem Beispiel beispielsweise die Kategorie „Fahrräder“ und die Farbe Schwarz auswählen, werden in der Tabelle nur schwarze Fahrräder angezeigt, aber:

  • Für jedes Kriterium in 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 dieser Farbe angezeigt.

Hier ist ein Beispiel für die Ergebnisausgabe für solche Bedingungen:

Probleme bei der Ausgabe von Suchergebnissen und der Leistung
Wenn Sie zusätzlich die Kategorie „Bekleidung“ ankreuzen, werden in der Tabelle auch schwarze Kleidungsstücke angezeigt, die vorrätig sind. Auch die Anzahl der schwarzen Produkte im Bereich „Farbe“ wird entsprechend den neuen Bedingungen neu berechnet, nur im Bereich „Kategorien“ ändert sich nichts... Ich hoffe, diese Beispiele reichen aus, um den üblichen Facettensuchalgorithmus zu verstehen.

Stellen wir uns nun vor, wie dies auf relationaler Basis implementiert werden kann. Für jede Kriteriengruppe, z. B. Kategorie und Farbe, ist eine separate Abfrage erforderlich:

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

Probleme bei der Ausgabe von Suchergebnissen und der Leistung

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

Probleme bei der Ausgabe von Suchergebnissen und der Leistung
Was ist an dieser Lösung falsch? Es ist sehr einfach – es lässt sich nicht gut skalieren. Jeder Filterabschnitt erfordert eine separate Abfrage zur Mengenberechnung, und diese Abfragen sind nicht die einfachsten. In Online-Shops verfügen einige Kategorien möglicherweise über mehrere Dutzend Filterabschnitte, was ein ernstes Leistungsproblem darstellen kann.

Normalerweise werden mir nach diesen Aussagen einige Lösungen angeboten, nämlich:

  • Fassen Sie alle Mengenzählungen in einer Abfrage zusammen. Technisch ist dies mit dem Schlüsselwort UNION möglich, aber die Leistung wird dadurch nicht wesentlich verbessert – die Datenbank muss weiterhin jedes der Fragmente von Grund auf ausführen.
  • Cache-Mengen. Das wird mir fast jedes Mal vorgeschlagen, wenn ich ein Problem beschreibe. Der Vorbehalt besteht darin, dass dies im Allgemeinen unmöglich ist. Nehmen wir an, wir haben 10 „Facetten“, von denen jede 5 Werte hat. Dies ist eine sehr „bescheidene“ Situation im Vergleich zu dem, was in denselben Online-Shops zu sehen ist. Die Wahl eines Facettenelements wirkt sich auf die Mengen in neun anderen aus, d. h. für jede Kombination von Kriterien können die Mengen unterschiedlich sein. In unserem Beispiel gibt es insgesamt 9 Kriterien, die der Benutzer auswählen kann, daher gibt es 50 mögliche Kombinationen. Es ist nicht genügend Speicher oder Zeit vorhanden, um ein solches Datenarray zu füllen. Hier kann man einwenden und sagen, dass nicht alle Kombinationen real sind und der Benutzer selten mehr als 250-5 Kriterien auswählt. Ja, es ist möglich, Lazy Loading durchzuführen und nur eine Menge zwischenzuspeichern, die jemals ausgewählt wurde, aber je mehr Auswahlen es gibt, desto weniger effizient wird ein solcher Cache sein und desto deutlicher werden die Probleme mit der Antwortzeit sein (insbesondere, wenn die Der Datensatz ändert sich regelmäßig).

Glücklicherweise gibt es für ein solches Problem seit langem recht effektive Lösungen, die bei großen Datenmengen vorhersehbar funktionieren. Für jede dieser Optionen ist es sinnvoll, die Neuberechnung der Facetten und den Empfang der Ergebnisseite in zwei parallele Aufrufe an den Server aufzuteilen und die Benutzeroberfläche so zu organisieren, dass das Laden von Daten nach Facetten die Anzeige von „nicht beeinträchtigt“. Suchergebnisse.

  • Rufen Sie so selten wie möglich eine vollständige Neuberechnung der „Facetten“ auf. Berechnen Sie beispielsweise nicht jedes Mal alles neu, wenn sich die Suchkriterien ändern, sondern ermitteln Sie stattdessen die Gesamtzahl der Ergebnisse, die den aktuellen Bedingungen entsprechen, und fordern Sie den Benutzer auf, sie anzuzeigen – „1425 Datensätze gefunden, anzeigen?“ Der Benutzer kann entweder die Suchbegriffe weiter ändern oder auf die Schaltfläche „Anzeigen“ klicken. Erst im zweiten Fall werden alle Anfragen zur Ergebnisermittlung und Neuberechnung von Mengen auf allen „Facetten“ ausgeführt. In diesem Fall müssen Sie sich, wie Sie leicht erkennen können, mit einer Anfrage befassen, um die Gesamtzahl der Ergebnisse und deren Optimierung zu erhalten. Diese Methode ist in vielen kleinen Online-Shops zu finden. Natürlich ist dies kein Allheilmittel für dieses Problem, kann aber in einfachen Fällen ein guter Kompromiss sein.
  • Verwenden Sie Suchmaschinen, um Ergebnisse zu finden und Facetten zu zählen, wie z. B. Solr, ElasticSearch, Sphinx und andere. Sie alle sind darauf ausgelegt, „Facetten“ zu bilden und tun dies aufgrund des invertierten Index recht effizient. Wie Suchmaschinen funktionieren, warum sie in solchen Fällen effektiver sind als Allzweckdatenbanken, welche Praktiken und Fallstricke es gibt – das ist ein Thema für einen separaten Artikel. An dieser Stelle möchte ich Sie darauf aufmerksam machen, dass die Suchmaschine kein Ersatz für den Hauptdatenspeicher sein kann, sondern als Ergänzung dient: Alle für die Suche relevanten Änderungen in der Hauptdatenbank werden in den Suchindex synchronisiert; Die Suchmaschine 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. Es hängt alles von den Anforderungen an die „Reaktionszeit“ ab. Wenn die Zeit zwischen einer Änderung in der Hauptdatenbank und ihrer „Manifestation“ in der Suche nicht kritisch ist, können Sie einen Dienst erstellen, der alle paar Minuten nach kürzlich geänderten Datensätzen sucht und diese indiziert. Wenn Sie eine möglichst kurze Reaktionszeit wünschen, können Sie so etwas implementieren Transaktionsausgang um Aktualisierungen an den Suchdienst zu senden.

Befund

  1. Die Implementierung von serverseitigem Paging stellt eine erhebliche Komplikation dar und ist nur bei schnell wachsenden oder einfach großen Datenmengen sinnvoll. Es gibt kein absolut genaues Rezept, wie man „groß“ oder „schnell wachsend“ bewertet, aber ich würde diesem Ansatz folgen:
    • Wenn der Empfang einer vollständigen Datensammlung unter Berücksichtigung der Serverzeit und der Netzwerkübertragung normalerweise den Leistungsanforderungen entspricht, macht es keinen Sinn, Paging auf der Serverseite zu implementieren.
    • Es kann vorkommen, dass in naher Zukunft keine Leistungsprobleme zu erwarten sind, da nur wenige Daten vorhanden sind, die Datenerfassung jedoch ständig wächst. Wenn ein Datensatz in der Zukunft den vorherigen Punkt möglicherweise nicht mehr erfüllt, ist es besser, sofort mit dem Paging zu beginnen.
  2. Wenn es seitens des Unternehmens keine strenge Anforderung gibt, die Gesamtzahl der Ergebnisse oder die Seitenzahlen anzuzeigen, und Ihr System nicht über eine Suchmaschine verfügt, sollten Sie diese Punkte besser nicht umsetzen und Option #2 in Betracht ziehen.
  3. Wenn eine klare Anforderung für die Facettensuche besteht, haben Sie zwei Möglichkeiten, ohne Einbußen bei der Leistung hinnehmen zu müssen:
    • Berechnen Sie nicht jedes Mal alle Mengen neu, wenn sich die Suchkriterien ändern.
    • Nutzen Sie Suchmaschinen wie Solr, ElasticSearch, Sphinx und andere. Es sollte jedoch klar sein, dass es kein Ersatz für die Hauptdatenbank sein kann und als Ergänzung zum Hauptspeicher zur Lösung von Suchproblemen verwendet werden sollte.
  4. Auch bei der Facettensuche ist es sinnvoll, den Abruf der Suchergebnisseite und die Zählung auf zwei parallele Anfragen aufzuteilen. Das Zählen von Mengen kann länger dauern als das Erhalten von Ergebnissen, obwohl die Ergebnisse für den Benutzer wichtiger sind.
  5. Wenn Sie eine SQL-Datenbank für die Suche verwenden, sollten alle Codeänderungen in Bezug auf diesen Teil sorgfältig auf Leistung bei der entsprechenden Datenmenge (die das Volumen in der Live-Datenbank überschreitet) getestet werden. Es empfiehlt sich auch, die Überwachung der Abfrageausführungszeit auf allen Instanzen der Datenbank und insbesondere auf der „Live“-Instanz zu verwenden. Auch wenn in der Entwicklungsphase mit den Abfrageplänen alles in Ordnung war, kann sich die Situation mit zunehmendem Datenvolumen merklich ändern.

Source: habr.com

Kommentar hinzufügen