Wie kann der Zugriff auf API-Anwendungen schnell und einfach beschleunigt werden?

Die Antwort ist einfach: Verwenden Sie bewährte Tools wie Caching und horizontale Skalierung. Wir müssen sofort sagen, dass dies nicht die einzigen Werkzeuge sind, aber meistens sind es die bewährten klassischen Ansätze, die sich selbst unter modernen Bedingungen als die effektivsten herausstellen. Schauen wir uns ein praktisches Beispiel an.



Über das ursprüngliche Problem



Die PREMIER-Videoplattform hat, wie es sich für eine moderne Ressource gehört, einen Empfehlungsservice für ihre Kunden geschaffen, der auf maschinellem Lernen basiert. Viele Benutzer wenden sich an die Videoplattform - etwa eine Million pro Tag, PREMIER ist sehr beliebt - und die Anrufe kommen sowohl über ein Webformular, von Anwendungen für mobile Geräte als auch von Smart TV.



Die Anfangsdaten, auf deren Grundlage das maschinelle Lernen unseres Dienstes funktioniert, werden im spaltenweisen ClickHouse-DBMS gespeichert. Gemäß dem Zeitplan werden Daten im Hintergrund verarbeitet, um Modelle zu erstellen (die zur Abgabe endgültiger Empfehlungen verwendet werden). Die Berechnungsergebnisse werden im relationalen PostgreSQL-DBMS gespeichert.



Die Lösung des Problems der schnellen Interaktion zwischen Anwendungsserver und Client in kurzer Zeit ist immer relevant. Um die erforderliche Arbeitsgeschwindigkeit sicherzustellen - und die Antwortzeit sollte nicht mehr als 50 ms betragen - mussten wir die Struktur der relationalen Datenbank optimieren, Caching und horizontale Skalierung implementieren. Wir werden jetzt über einige der Techniken sprechen.







Über das Caching



Caching ist eine gängige Optimierungstechnik. Wir erstellen einen zusätzlichen Zwischenspeicher, der schneller als der Hauptspeicher ist, und platzieren dort die am meisten nachgefragten Daten. Der Zugriff auf zwischengespeicherte Daten wird erheblich beschleunigt, was die Geschwindigkeit der Anwendung erheblich erhöht. Bei der Entwicklung einer Hochlastanwendung ist das Caching die häufigste Option zur Optimierung der Anwendungsleistung, ohne die Hardwareressourcen zu erweitern. Das Zwischenspeichern kann zu Einsparungen bei der Ressourcennutzung führen, da dieselbe Ausgabe für dieselbe Eingabe erneut generiert wird.



Die Cache-Kapazität ist begrenzt - je schneller der Speicher, desto teurer ist er - und muss daher effizient genutzt werden. Natürlich können Sie theoretisch auf Caching verzichten, wenn Ihr Hauptspeicher schnell genug ist. Aber es wird wirtschaftlich unrentabel sein, da Sie müssen ein erhebliches Hardware-Upgrade durchführen, das häufig auf eine Erhöhung des Arbeitsspeichers und / oder den Austausch von Festplatten von Festplatte zu SSD hinausläuft. Jene. Erhöhen Sie die Anforderungen an die Infrastruktur erheblich, was sich auf die wirtschaftlichen Parameter der gesamten zu erstellenden Anwendung auswirkt. Ohne bewährtes Caching ist es in den meisten Fällen kaum möglich, ein Massenprodukt zu erstellen.



Das Hinzufügen einer Caching-Ebene löst jedoch nicht automatisch alle Probleme. Es ist auch notwendig, über die Regeln für das Befüllen nachzudenken, die von den Merkmalen der Aufgabe abhängen, die sich je nach Situation und Entwicklung des Dienstes ändern können. Und denken Sie daran, dass dies kein Allheilmittel ist, sondern nur ein Mittel, das die Symptome von Leistungsproblemen in bestimmten Teilen Ihrer Anwendung lindert. Wenn die Anwendung tiefgreifende Architekturprobleme aufweist, eine mittelmäßige Ausführungsumgebung, führt das Zwischenspeichern eher zu Problemen.



Es gibt verschiedene Optionen zum Speichern zwischengespeicherter Ressourcen: lokal - auf der Clientinstanz im Browser, in einem CDN-Dienst eines Drittanbieters auf der Anwendungsseite. Wir werden über In-App-Caching sprechen. Der Speicher des Anwendungsprozesses ist wahrscheinlich die häufigste Option für das Zwischenspeichern von Daten, auf die Sie stoßen werden. Diese Lösung hat jedoch ihre Nachteile, da Speicher ist einem Prozess zugeordnet, der eine bestimmte Aufgabe ausführt. Dies ist umso wichtiger, wenn Sie die Anwendung skalieren möchten, da der Speicher nicht zwischen Prozessen zugewiesen wird, d. H. Es ist in anderen Prozessen nicht verfügbar, z. B. in den Prozessen, die für die asynchrone Verarbeitung verantwortlich sind. Ja, Caching funktioniert, aber wir können wirklich nicht den vollen Nutzen daraus ziehen.



Empfehlungs-Caching







Wenn wir auf das Projekt zurückkommen, verstehen wir die Notwendigkeit einer zentralisierten Caching-Lösung. Für einen gemeinsam genutzten Cache können Sie beispielsweise Memcached verwenden: Wenn eine Anwendung mit derselben Instanz verbunden ist, können Sie sie in vielen Prozessen verwenden. Einerseits ist Memcached eine einfache und bequeme Lösung, andererseits ist es eher begrenzt, wenn es um die präzise Verwaltung von Ungültigkeit, Datentypisierung und komplexeren Abfragen an den zwischengespeicherten Datenspeicher geht. Tatsächlich ist der Redis-Speicher mittlerweile zum Standard für Caching-Aufgaben geworden, ohne die Nachteile von Memcached.



Redis ist ein schneller Speicher für Schlüsselwerte. Es erhöht die Effizienz der Arbeit mit Daten, weil es wird möglich, die Struktur zu definieren. Bietet eine detaillierte Kontrolle über Behinderung und Prävention, sodass Sie aus sechs verschiedenen Richtlinien auswählen können. Redis unterstützt sowohl die faule und eifrige Vorauszahlung als auch die Zeitvoraussetzung. Die Verwendung von Redis-Datenstrukturen kann je nach Geschäftseinheit konkrete Optimierungen ermöglichen. Anstatt Objekte als serialisierte Zeichenfolgen zu speichern, können Entwickler beispielsweise eine Hash-Datenstruktur verwenden, um Felder und Werte nach Schlüssel zu speichern und zu bearbeiten. Durch Hash muss nicht mehr die gesamte Zeichenfolge abgerufen, deserialisiert und bei jedem Update durch einen neuen Wert im Cache ersetzt werden. Dies bedeutet weniger Ressourcenverbrauch und bessere Leistung. Andere Datenstrukturen,Die von Redis vorgeschlagenen "Blätter", "Sätze", "sortierten Sätze", "Hyperlogs", "Bitmaps" und "Geoindexe") können verwendet werden, um noch komplexere Szenarien zu implementieren. Sortierte Sätze zur Analyse von Zeitreihendaten bieten eine geringere Komplexität und ein geringeres Volumen bei der Verarbeitung und Übertragung von Daten. Die HyperLogLog-Datenstruktur kann verwendet werden, um eindeutige Elemente in einem Satz zu zählen, wobei nur eine kleine, dauerhafte Speichermenge verwendet wird, insbesondere 12 KB für jedes HyperLogLog (plus einige Bytes für den Schlüssel selbst). Ein wesentlicher Teil der rund 200 in Redis verfügbaren Befehle ist für Datenverarbeitungsvorgänge und die Einbettung von Logik in die Datenbank selbst mithilfe von Lua-Skripten bestimmt.Integrierte Befehle und Skriptfunktionen bieten Flexibilität für die Verarbeitung von Daten direkt in Redis, ohne dass Daten über das Netzwerk an Ihre Anwendung gesendet werden müssen, wodurch der Aufwand für die Implementierung zusätzlicher Caching-Logik verringert wird. Die Verfügbarkeit von Cache-Daten unmittelbar nach einem Neustart kann die Aufwärmzeit des Cache erheblich verkürzen und die mit der Neuberechnung des Cache-Inhalts aus dem Hauptdatenspeicher verbundene Belastung verringern. In den folgenden Artikeln werden wir über die Funktionen der Redis-Konfiguration und die Aussichten für Clustering sprechen.In den folgenden Artikeln werden wir über die Funktionen der Redis-Konfiguration und die Aussichten für Clustering sprechen.In den folgenden Artikeln werden wir über die Funktionen der Redis-Konfiguration und die Aussichten für Clustering sprechen.



Nach Auswahl eines Caching-Tools bestand das Hauptproblem in der Synchronisation der im Cache gespeicherten Daten und der in der Anwendung gespeicherten Daten. Abhängig von der Geschäftslogik Ihrer Anwendung gibt es verschiedene Möglichkeiten, Daten zu synchronisieren. In unserem Fall lag die Schwierigkeit in der Erstellung eines Algorithmus zur Dateninvalidierung. Daten, die in den Cache gestellt werden, werden dort für eine begrenzte Zeit gespeichert, solange sie in der aktuellen Situation oder zumindest mit der Wahrscheinlichkeit einer solchen verwendet werden müssen. Wenn sich die Situation entwickelt, müssen sie Platz für andere Daten schaffen, die unter den geänderten Bedingungen mehr benötigt werden. Die Hauptaufgabe in diesem Fall ist die Auswahl der Kriterien, anhand derer Daten aus dem Cache entfernt werden. In den meisten Fällen ist dies der Zeitpunkt für die Relevanz der Daten. Es lohnt sich jedoch, sich an andere Parameter zu erinnern: an das Volumen, die Rangfolge (gleich, mit Toleranzen,Lebensdauer), Kategorie (Haupt- oder Zusatzdaten) usw.



Die grundlegende und übliche Methode, um Daten auf dem neuesten Stand zu halten, ist die Veralterung der Zeit. Diese Methode wird angesichts der regelmäßigen zentralen Aktualisierung der Daten des Empfehlungsantrags auch von uns verwendet. Es ist jedoch nicht alles so einfach, wie es auf den ersten Blick scheint: In diesem Fall ist es äußerst wichtig, das Ranking zu überwachen, damit nur wirklich beliebte Daten in den Cache gelangen. Dies wird möglich dank der Sammlung von Abfragestatistiken und der Implementierung von "Datenvorwärmung", d.h. Vorladen von Daten in den Cache zu Beginn der Anwendung. Die Verwaltung der Cache-Größe ist auch ein wichtiger Aspekt des Caching. Unsere Anwendung generiert ungefähr Millionen von Empfehlungen, daher ist es unrealistisch, all diese Daten im Cache zu speichern. Die Verwaltung der Cache-Größe erfolgt durch Entfernen von Daten aus dem Cache, um Platz für neue Daten zu schaffen.Es gibt verschiedene Standardmethoden: TTL, FIFO, LIFO, zuletzt aufgerufen. Bisher verwenden wir TTL, weil Die Redis-Instanz geht nicht über die zugewiesenen Speicher- und Festplattenressourcen hinaus.



Denken Sie daran, dass der Cache anders ist. Am häufigsten gibt es zwei Kategorien: Durchschreiben und Zurückstellen. Im ersten Fall erfolgt das Schreiben synchron sowohl zum Cache als auch zum Hauptspeicher. Im zweiten Fall wird das Schreiben zunächst nur im Cache ausgeführt, und das Schreiben in den Hauptspeicher wird verschoben, bis die geänderten Daten durch einen anderen Cache-Block ersetzt werden. Das Zurückschreiben des Caches ist schwieriger zu implementieren, da die Daten im Cache überwacht werden müssen, damit sie anschließend in den Hauptspeicher geschrieben werden können, wenn sie aus dem Cache entfernt werden. Wir verwenden die erste Option in Verbindung mit dem Cache-Aufwärmverfahren. Beachten Sie, dass das "Aufwärmen" an sich eine wichtige und schwierige Aufgabe ist. Unsere Lösung wird in den folgenden Artikeln erläutert.



Skalieren Sie in einer Empfehlungs-App



Um PREMIER Zugriff auf die Empfehlungsanwendung zu gewähren, verwenden wir das HTTP-Protokoll, das häufig die Hauptoption bei der Interaktion von Anwendungen ist. Es eignet sich zum Organisieren der Interaktion zwischen Anwendungen, insbesondere wenn Kubernetes und Ingress Controller als Infrastrukturumgebung verwendet werden. Die Verwendung von Kubernetes erleichtert die Skalierung. Das Tool ist in der Lage, die Anforderung automatisch zwischen den Pods im Cluster zu verteilen, um eine gleichmäßige Arbeit zu gewährleisten, sodass Entwickler leichter skalieren können. Verantwortlich dafür ist das Ingress Controller-Modul, das die Regeln für die externe Verbindung zu Anwendungen in Kubernetes definiert. Standardmäßig kann auf Anwendungen in Kubernetes nicht über das externe Netzwerk zugegriffen werden. Um externen Zugriff auf Anwendungen zu gewähren, müssen Sie eine Ingress-Ressource deklarieren, die den automatischen Ausgleich unterstützt.Wir verwenden Nginx Ingress Controller, der SSL / TLS, URI-Umschreiberegeln sowie VirtualServer und VirtualServerRoute unterstützt, um Anforderungen je nach URI und Host-Header an verschiedene Anwendungen weiterzuleiten.



In der Grundkonfiguration des Ingress Controllers können Sie nur die Grundfunktionen von Nginx verwenden. Dies ist ein Routing, das auf Host und Pfad basiert. Zusätzliche Funktionen wie URI-Umschreiberegeln, zusätzliche Antwortheader und Verbindungszeitlimits sind nicht verfügbar. Mit Anmerkungen, die auf die Ingress-Ressource angewendet werden, können Sie die Funktionen von Nginx selbst verwenden (normalerweise über die Konfiguration der Anwendung selbst verfügbar) und das Verhalten von Nginx für jede Ingress-Ressource ändern.



Wir planen, den Nginx Ingress Controller nicht nur in dem Projekt zu verwenden, das wir gerade prüfen, sondern auch in einer Reihe anderer Anwendungen, über die wir später sprechen werden. Wir werden in den folgenden Artikeln darüber sprechen.







Risiken und Folgen der Verwendung von Caching



Jedes Team, das an der Optimierung einer datenintensiven Anwendung arbeitet, hat viele Fragen zur Optimierung des Caching. Gleichzeitig kann das Problem mit dem Caching nicht "ein für allemal" gelöst werden. Im Laufe der Zeit treten verschiedene Probleme auf. Wenn Ihre Benutzerbasis wächst, können beispielsweise widersprüchliche Statistiken zur Popularität von Daten nach Kategorien auftreten, und dieses Problem muss behoben werden.



Caching ist zwar ein leistungsstarkes Tool zur Beschleunigung einer Anwendung, aber nicht die einzige Lösung. Abhängig von Ihrer Situation müssen Sie möglicherweise die Anwendungslogik optimieren, den Stack und die Infrastrukturumgebung ändern. Sie sollten auch vorsichtig sein, wenn Sie nicht funktionierende Anforderungen validieren. Es ist möglich, dass nach Absprache mit dem Product Owner die Anwendungsanforderungen überbewertet werden. Es muss beachtet werden, dass jede der Lösungen ihre eigenen Eigenschaften hat.



Das Risiko der Bereitstellung veralteter Daten, die Erhöhung der Gesamtkomplexität der Lösung und die Wahrscheinlichkeit der Einführung latenter Fehler müssen behoben werden, bevor eine Caching-Methode auf ein Projekt angewendet wird. In diesem Fall wird das Zwischenspeichern die Problemlösung nur erschweren, sondern lediglich Leistungs- und Skalierbarkeitsprobleme verbergen: Sind Datenbankabfragen langsam? - Cache führt zu schneller Speicherung! Sind API-Aufrufe langsam? - Cache-Ergebnisse auf dem Client! Dies liegt daran, dass die Komplexität des Codes, der das Caching verwaltet, mit zunehmender Komplexität der Geschäftslogik erheblich zunimmt.



In den ersten Versionen der Anwendung hat das Caching unmittelbar nach der Implementierung einen spürbaren Effekt. Schließlich ist auch die Geschäftslogik einfach: Sie speichern den Wert und erhalten ihn zurück. Die Invalidierung ist einfach, da Abhängigkeiten zwischen Geschäftseinheiten trivial oder nicht vorhanden sind. Im Laufe der Zeit müssen Sie jedoch immer mehr Geschäftseinheiten zwischenspeichern, um die Leistung zu verbessern.



Caching ist kein Wundermittel für Leistungsprobleme. In vielen Fällen ist die Optimierung Ihres Codes und des Kernspeichers auf lange Sicht gut. Darüber hinaus sollte die Einführung von Caching eine Reaktion auf das Problem und keine vorzeitige Optimierung sein.



Zusammenfassend lässt sich sagen, dass die Optimierung und Skalierbarkeit der Anwendungsleistung ein fortlaufender Prozess ist, der darauf abzielt, ein vorhersehbares Verhalten innerhalb bestimmter nicht funktionaler Anforderungen zu erreichen. Caching ist erforderlich, um die Hardwarekosten und die Entwicklungszeit für die Verbesserung der Leistung und Skalierbarkeit zu reduzieren.



Links:






All Articles