Beliebte Antimuster: Paginierung

Hallo, mein Name ist Dmitry Karlovsky und ich ... lese nicht gern Bücher, denn während Sie die Seite umblättern, brechen Sie aus einer faszinierenden Geschichte aus. Es lohnt sich ein wenig zu zögern, wenn Sie vergessen, worauf der letzte Satz der vorherigen Seite endete, und zurückblättern müssen, um ihn erneut zu lesen. Und wenn es mit physischen Büchern nicht so beängstigend ist, dann ist mit der Ausgabe eines Rest-Servers alles viel trauriger - schließlich gibt es jetzt einige Daten auf der Seite, und nach einer Sekunde ist es völlig anders. Lassen Sie uns darüber nachdenken, wie es passiert ist, wer schuld ist und was am wichtigsten ist - was zu tun ist.


Verschiedene Puginatoren


Das Problem


Wir müssen also alle Nachrichten für die Abfrage "Paginierung" ausgeben, beginnend mit der neuesten (die letzten wurden von oben geändert ) oder in einer schwierigen Reihenfolge. Alles ist in Ordnung, solange wir weniger als hundert dieser Nachrichten haben - wir treffen einfach eine Auswahl aus der Datenbank und geben die Daten zurück:


Anfrage vom Kunden:


GET /message/text=/ 

Datenbankanforderung:


 SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC 

JSON-Antwortschema für Client:


 Array<{ id : number , text : string }> 

Aber die Anzahl der Nachrichten wächst und wir haben die folgenden Probleme:


  1. Datenbankabfragen werden langsamer, da mehr Daten gesammelt werden müssen.
  2. Das Senden von Daten über das Netzwerk nimmt immer mehr Zeit in Anspruch.
  3. Das Rendern dieser Daten auf dem Client wird immer länger.

Ab einem bestimmten Schwellenwert werden Verzögerungen so erheblich, dass die Nutzung unserer Website nicht mehr möglich ist. Wenn er sich natürlich noch nicht hingelegt hatte, müde von einer großen Anzahl paralleler schwerer Anfragen.


Die einfachste Lösung, die vielleicht zuerst in den Sinn kommt und die Sie jetzt in jedem Toaster finden können - Daten nicht alle in großen Mengen, sondern in Seiten aufgeteilt auszugeben. Alles was wir tun müssen, ist nur einen zusätzlichen Parameter vom Client in die Datenbankanforderung zu werfen:


 GET /message/text=/page=5/ 

 SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC SKIP 5 * 10 LIMIT 10 

 SELECT count(*) FROM Message WHERE text LICENE "" 

 { pageItems : Array<{ id : number , text : string }> totalCount : number } 

Ja, wir mussten immer noch alle Nachrichten nachzählen, damit der Client eine Liste von Seiten zeichnen oder die Höhe der virtuellen Schriftrolle berechnen konnte, aber zumindest mussten wir nicht alle diese 100500 Nachrichten aus der Datenbank abrufen.


Und alles wäre in Ordnung, wenn wir eine Art nicht populäres Forum für lange Zeit nicht mehr relevante Themen hätten. Aber sie schreiben und schreiben uns, schreiben und schreiben, und während der Benutzer die fünfte Seite liest, ändert sich die Liste der Nachrichten bis zur Unkenntlichkeit: Neue werden hinzugefügt und alte werden gelöscht. Somit erhalten wir aus Sicht des Benutzers zwei Arten von Problemen:


  1. Auf der nächsten Seite werden möglicherweise wieder Nachrichten angezeigt, die sich bereits auf der vorherigen Seite befanden.
  2. Der Benutzer sieht einige Nachrichten überhaupt nicht, da er es geschafft hat, genau zwischen dem Übergang des Benutzers von 5 auf 6 von Seite 6 auf 5 zu wechseln.

Darüber hinaus haben wir immer noch Leistungsprobleme. Jeder Übergang zur nächsten Seite führt dazu, dass bis zu zwei Suchabfragen in der Datenbank mit einer zunehmenden Anzahl von übersprungenen Elementen von vorherigen Seiten durchgeführt werden müssen.


Ja, und eine kompetente Implementierung auf der Clientseite ist nicht so einfach. Sie müssen immer darauf vorbereitet sein, dass jede Serverantwort eine neue Gesamtzahl von Nachrichten zurückgeben kann. Dies bedeutet, dass wir den Paginator neu zeichnen und auf eine andere Seite umleiten müssen, wenn die aktuelle plötzlich ist leer. Und natürlich können Sie bei Duplikaten nicht fallen.


Außerdem muss der Client manchmal die Suchergebnisse aktualisieren, aber der Ladevorgang empfängt weiterhin Daten, die möglicherweise bereits aus früheren Anforderungen stammen.


Wie Sie sehen können, hat die Paginierung viele Probleme. Gibt es wirklich keine bessere Lösung?


Lösung


Lassen Sie uns zunächst darauf achten, dass es bei der Arbeit mit der Datenbank zwei Operationen gibt, die sich wesentlich unterscheiden:


  1. Suche. Relativ schweres Auffinden von Zeigern auf Daten für eine Abfrage.
  2. Probenahme. Eine relativ einfache Operation, um tatsächlich Daten zu erhalten.

Es wäre ideal:


  1. Einmal suchen und irgendwo, um sich die Ergebnisse in Form eines Schnappschusses zu einem bestimmten Zeitpunkt zu merken.
  2. Wählen Sie Daten nach Bedarf schnell in kleinen Portionen aus.

Wo können Schnappschüsse gespeichert werden? Es gibt 2 Möglichkeiten:


  1. Auf dem Server. Aber dann verstopfen wir es mit einer Menge Müll mit Suchergebnissen, die im Laufe der Zeit bereinigt werden müssen.
  2. Zum Kunden. Dann müssen Sie jedoch sofort den gesamten Schnappschuss auf den Client übertragen.

Lassen Sie uns die Größe des Schnappschusses schätzen, bei dem es sich nur um eine Liste von Bezeichnern handelt. Es ist zweifelhaft, dass der Benutzer die Geduld hatte, mindestens 100 Seiten ohne Filterung und Sortierung zu rollen. Angenommen, wir haben 20 Elemente pro Seite. Jeder Bezeichner belegt in der JSON-Darstellung nicht mehr als 10 Byte. Multiplizieren Sie und erhalten Sie nicht mehr als 20 KB. Und höchstwahrscheinlich viel weniger. Es wäre vernünftig, die Größe der Ausgabe in beispielsweise 1000 Elementen fest zu begrenzen.


 GET /message/text=/ 

 SELECT id FROM Message WHERE text LICENE "" ORDER BY changed DESC LIMIT 1000 

 Array<number> 

Jetzt kann der Client mindestens einen Paginator, mindestens eine virtuelle Schriftrolle, zeichnen und Daten nur für für ihn interessante Kennungen anfordern.


 GET /message=49,48,47,46,45,42,41,40,39,37/ 

 SELECT FROM Message WHERE id IN [49,48,47,46,45,42,41,40,39,37] 

 Array< { id : number , text : string } | { id : number , error : string } > 

Was wir letztendlich bekommen:


  1. Normalisierte API: Separat suchen, Daten separat auswählen.
  2. Minimieren Sie die Anzahl der Suchanfragen.
  3. Sie können bereits heruntergeladene Daten nicht anfordern oder im Hintergrund aktualisieren.
  4. Relativ einfacher und universeller Code auf der Client-Seite.

Von den Mängeln kann festgestellt werden, außer dass:


  1. Um etwas anzuzeigen, muss der Benutzer mindestens zwei aufeinanderfolgende Anforderungen stellen.
  2. Es ist erforderlich, den Fall zu behandeln, wenn die Kennung vorhanden ist und die Daten darauf nicht mehr verfügbar sind.

Source: https://habr.com/ru/post/de413133/


All Articles