Die Ekstase und die Qual der Caches

In den Jahren der Erstellung von Services bei Amazon haben wir verschiedene Versionen des folgenden Szenarios erlebt: Wir erstellen einen neuen Dienst, und dieser Dienst muss einige Netzwerkanrufe tätigen, um seine Anforderungen zu erfüllen. Möglicherweise beziehen sich diese Aufrufe auf eine relationale Datenbank oder einen AWS-Service wie Amazon DynamoDB oder einen anderen internen Service. In einfachen Tests oder bei niedrigen Anforderungsraten funktioniert der Service hervorragend, aber wir bemerken ein Problem am Horizont. Das Problem kann sein, dass Anrufe an diesen anderen Service langsam sind oder die Datenbank mit zunehmendem Anrufvolumen teuer skaliert werden kann. Wir stellen auch fest, dass viele Anfragen dieselbe Downstream-Ressource oder dieselben Abfrageergebnisse verwenden, sodass wir der Meinung sind, dass das Zwischenspeichern dieser Daten die Antwort auf unsere Probleme sein könnte. Wir fügen einen Cache hinzu und unser Service scheint stark verbessert zu sein. Wir stellen fest, dass die Anforderungslatenzzeit gesunken ist, die Kosten gesenkt werden und kleine Downstream-Verfügbarkeitsverluste geglättet werden. Nach einer Weile kann sich niemand mehr an das Leben vor dem Cache erinnern. Abhängigkeiten reduzieren ihre Flottengröße entsprechend und die Datenbank wird verkleinert. Gerade wenn alles gut zu laufen scheint, könnte der Service für eine Katastrophe bereit sein. Es kann zu Änderungen im Datenverkehr, einem Ausfall der Cache-Flotte oder anderen unerwarteten Umständen kommen, die zu einem kalten oder anderweitig nicht verfügbaren Cache führen können. Dies kann wiederum zu einem Anstieg des Datenverkehrs zu nachgelagerten Diensten führen, der zu Ausfällen sowohl in unseren Abhängigkeiten als auch in unserem Dienst führen kann.

Wir haben gerade einen Dienst beschrieben, der von seinem Cache abhängig geworden ist. Der Cache wurde versehentlich von einer hilfreichen Ergänzung des Dienstes zu einem notwendigen und kritischen Teil seiner Funktionsfähigkeit erhöht. Im Mittelpunkt dieses Problems steht das modale Verhalten, das vom Cache eingeführt wird. Das Verhalten hängt davon ab, ob ein bestimmtes Objekt zwischengespeichert wird. Eine unerwartete Verschiebung der Verteilung dieses modalen Verhaltens kann möglicherweise zu einer Katastrophe führen.

Wir haben sowohl die Vorteile als auch die Herausforderungen des Caching beim Erstellen und Betreiben von Diensten bei Amazon erlebt. Der Rest dieses Artikels beschreibt unsere Erfahrungen, Best Practices und Überlegungen zur Verwendung von Caches.

Wann wir Caching verwenden

Mehrere Faktoren veranlassen uns, einen Cache zu unserem System hinzuzufügen. Häufig beginnt dies mit einer Beobachtung der Latenz oder Effizienz einer Abhängigkeit bei einer bestimmten Anforderungsrate. Dies kann beispielsweise der Fall sein, wenn wir feststellen, dass eine Abhängigkeit möglicherweise gedrosselt wird oder auf andere Weise nicht in der Lage ist, mit der erwarteten Last Schritt zu halten. Es hat sich als hilfreich erwiesen, das Zwischenspeichern in Betracht zu ziehen, wenn ungleichmäßige Anforderungsmuster auftreten, die zu Hotkey-/Hotpartitionsdrosselung führen. Daten aus dieser Abhängigkeit sind ein guter Kandidat für die Zwischenspeicherung, wenn ein solcher Cache eine gute Cachetrefferquote für alle Anforderungen aufweisen würde. Das heißt, Ergebnisse von Aufrufen der Abhängigkeit können über mehrere Anforderungen oder Vorgänge hinweg verwendet werden. Wenn für jede Anforderung in der Regel eine eindeutige Abfrage an den abhängigen Dienst mit Ergebnissen pro Anforderung erforderlich ist, weist ein Cache eine vernachlässigbare Trefferquote auf, und der Cache funktioniert nicht. Eine zweite Überlegung ist, wie tolerant der Service eines Teams und seine Kunden für eine letztendliche Datenkonsistenz sind. Die zwischengespeicherten Daten werden im Laufe der Zeit notwendigerweise inkonsistent mit der Quelle. Daher kann das Zwischenspeichern nur erfolgreich sein, wenn sowohl der Service als auch seine Clients dies entsprechend kompensieren. Die Änderungsrate der Quelldaten sowie die Cache-Richtlinie zum Aktualisieren von Daten bestimmen, wie inkonsistent die Daten tendenziell sind. Diese beiden sind miteinander verwandt. Beispielsweise können relativ statische oder sich langsam ändernde Daten für längere Zeiträume zwischengespeichert werden.

Lokale Caches

Service-Caches können entweder im Arbeitsspeicher oder außerhalb des Service implementiert werden. On-Box-Caches, die üblicherweise im Prozessspeicher implementiert werden, sind relativ schnell und einfach zu implementieren und können bei minimalem Arbeitsaufwand erhebliche Verbesserungen bewirken. On-Box-Caches sind oft der erste Ansatz, der implementiert und bewertet wird, sobald der Bedarf an Caching erkannt wurde. Im Gegensatz zu externen Caches haben sie keinen zusätzlichen Overhead für den Betrieb, sodass die Integration in einen vorhandenen Service mit einem relativ geringen Risiko verbunden ist. Wir implementieren häufig einen On-Box-Cache als speicherinterne Hash-Tabelle, die über die Anwendungslogik verwaltet (z. B. indem Ergebnisse nach Abschluss der Serviceaufrufe explizit in den Cache gestellt werden) oder in den Service-Client eingebettet wird (z. B. durch Verwendung eines zwischengespeicherten HTTP-Clients).

Trotz der Vorteile und der verführerischen Einfachheit von In-Memory-Caches haben sie einige Nachteile. Zum einen sind die zwischengespeicherten Daten von Server zu Server in ihrer gesamten Flotte inkonsistent, was ein Problem mit der Cache-Kohärenz zur Folge hat. Wenn ein Client den Service wiederholt aufruft, werden möglicherweise neuere Daten für den ersten Aufruf und ältere Daten für den zweiten Aufruf abgerufen, je nachdem, welcher Server die Anforderung bearbeitet.

Ein weiteres Manko ist, dass die Downstream-Last jetzt proportional zur Flottengröße des Services ist, sodass es mit zunehmender Anzahl von Servern immer noch möglich sein kann, abhängige Services zu überfordern. Wir haben festgestellt, dass eine effektive Möglichkeit zur Überwachung die Ausgabe von Messdaten zu Cache-Treffern/Fehlern und der Anzahl der Anforderungen an nachgelagerte Services ist.

In-Memory-Caches sind auch anfällig für „Kaltstart“-Probleme. Diese Probleme treten auf, wenn ein neuer Server mit einem vollständig leeren Cache gestartet wird. Dies kann zu einer Häufung von Anforderungen an den abhängigen Service führen, wenn dieser den Cache füllt. Dies kann ein erhebliches Problem bei Bereitstellungen oder in anderen Situationen sein, in denen der Cache flottenweit geleert wird. Probleme mit der Cache-Kohärenz und mit dem leeren Cache können häufig mithilfe der Anforderungszusammenführung behoben werden, die später in diesem Artikel ausführlich beschrieben wird.

Externe Caches

Externe Caches können viele der Probleme beheben, die wir gerade besprochen haben. Ein externer Cache speichert zwischengespeicherte Daten in einer separaten Flotte, beispielsweise mit Memcached oder Redis. Ein externer Cache speichert zwischengespeicherte Daten in einer getrennten Flotte, beispielsweise mit Memcached oder Redis. (Beachten Sie, dass diese Probleme nicht vollständig behoben sind, da es bei der Aktualisierung des Caches zu Fehlern kommen kann.) Die Gesamtlast für Downstream-Dienste ist im Vergleich zu In-Memory-Caches geringer und nicht proportional zur Flottengröße. Kaltstartprobleme bei Ereignissen wie Bereitstellungen sind nicht vorhanden, da der externe Cache während der Bereitstellung gefüllt bleibt. Schließlich bieten externe Caches mehr verfügbaren Speicherplatz als speicherinterne Caches, wodurch das Auftreten von Cache-Bereinigung aufgrund von Speicherplatzbeschränkungen verringert wird.

Externe Caches haben jedoch ihre eigenen Mängel zu berücksichtigen. Das erste ist eine erhöhte Komplexität des Gesamtsystems und eine erhöhte Betriebslast, da eine zusätzliche Flotte zur Überwachung, Verwaltung und Skalierung zur Verfügung steht. Die Verfügbarkeitseigenschaften der Cache-Flotte unterscheiden sich von dem abhängigen Dienst, für den sie als Cache fungiert. Die Cache-Flotte ist häufig weniger verfügbar, wenn sie beispielsweise keine Unterstützung für Upgrades ohne Ausfallzeiten bietet und Wartungsfenster benötigt.

Um zu verhindern, dass die Dienstverfügbarkeit aufgrund des externen Caches beeinträchtigt wird, müssen wir den Dienstcode hinzufügen, um die Nichtverfügbarkeit der Cache-Flotte, den Ausfall des Cache-Knotens oder Cache-Put/Get-Fehler zu beheben. Eine Möglichkeit besteht darin, auf den abhängigen Service zurückzugreifen. Wir haben jedoch erfahren, dass wir bei dieser Vorgehensweise vorsichtig sein müssen. Während eines längeren Cache-Ausfalls führt dies zu einer atypischen Zunahme des Datenverkehrs zum Downstream-Dienst, was zu einer Drosselung oder einem Ausfall dieses abhängigen Dienstes führt und letztendlich die Verfügbarkeit verringert. Wir ziehen es vor, entweder den externen Cache in Verbindung mit einem speicherinternen Cache zu verwenden, auf den wir zurückgreifen können, wenn der externe Cache nicht mehr verfügbar ist, oder Load-Shedding zu verwenden und die maximale Rate der an den Downstream-Service gesendeten Anforderungen zu begrenzen. Wir testen das Verhalten des Dienstes bei deaktiviertem Caching, um sicherzustellen, dass die von uns eingerichteten Sicherheitsvorkehrungen, um zu verhindern, dass Abhängigkeiten abgebrannt werden, tatsächlich wie erwartet funktionieren.

Eine zweite Überlegung betrifft die Skalierung und Elastizität der Cache-Flotte. Sobald die Cache-Flotte ihre Anforderungsrate oder Speichergrenzen erreicht, müssen Knoten hinzugefügt werden. Wir bestimmen, welche Metriken Frühindikatoren für diese Grenzwerte sind, damit wir Monitore und Alarme entsprechend einrichten können. Beispielsweise stellte unser Team bei einem Service, an dem ich kürzlich gearbeitet habe, fest, dass die CPU-Auslastung sehr hoch war, als die Redis-Anforderungsrate ihr Limit erreichte. Wir verwendeten Lasttests mit realistischen Verkehrsmustern, um den Grenzwert zu bestimmen und die richtige Alarmschwelle zu finden.

Während wir die Cache-Flotte erweitern, achten wir darauf, dass keine Ausfälle oder massiven Datenverluste im Cache auftreten. Unterschiedliche Caching-Technologien haben unterschiedliche Aspekte.. Beispielsweise unterstützen einige Cache-Server das Hinzufügen von Knoten zu einem Cluster ohne Ausfallzeit nicht und nicht alle Cache-Client-Bibliotheken bieten konsistentes Hashing. Dies ist erforderlich, um Knoten zur Cache-Flotte hinzuzufügen und zwischengespeicherte Daten neu zu verteilen. Aufgrund der unterschiedlichen Client-Implementierungen von konsistentem Hashing und der Entdeckung von Knoten in der Cache-Flotte testen wir das Hinzufügen und Entfernen von Cache-Servern gründlich, bevor wir mit der Produktion beginnen.

Bei einem externen Cache achten wir besonders auf die Robustheit, wenn das Speicherformat geändert wird. Im Cache gespeicherte Daten werden wie in einem permanenten Speicher behandelt. Wir stellen sicher, dass aktualisierte Software immer Daten lesen kann, die eine frühere Version der Software geschrieben hat, und dass ältere Versionen problemlos neue Formate / Felder anzeigen können (z. B. während Bereitstellungen, wenn die Flotte eine Mischung aus altem und neuem Code enthält). Um ungewollte Überraschungen zu vermeiden, muss verhindert werden, dass unvorhergesehene Ausnahmen auftreten, wenn unerwartete Formate auftreten. Dies reicht jedoch nicht aus, um alle formatbezogenen Probleme zu vermeiden. Das Erkennen einer Nichtübereinstimmung des Versionsformats und das Verwerfen der zwischengespeicherten Daten kann zu Massenaktualisierungen der Caches führen, was wiederum zu einer Drosselung des abhängigen Dienstes oder zu Stromausfällen führen kann. Probleme mit dem Serialisierungsformat werden im Artikel Gewährleistung von Rollback-Sicherheit während der Bereitstellung ausführlicher behandelt.

Eine letzte Überlegung für externe Caches ist, dass sie von einzelnen Knoten in der Service-Flotte aktualisiert werden. Caches verfügen in der Regel nicht über Funktionen wie bedingte Puts und Transaktionen. Daher achten wir darauf, dass der Cache-Aktualisierungscode korrekt ist und den Cache niemals in einem ungültigen oder inkonsistenten Zustand belassen kann.

Inline- vs. Side-Caches

Eine weitere Entscheidung, die wir treffen müssen, wenn wir verschiedene Caching-Ansätze bewerten, ist die Wahl zwischen Inline- und Side-Caches. Inline-Caches oder Read-through/Write-through-Caches integrieren die Cache-Verwaltung in die Hauptdatenzugriffs-API, sodass die Cache-Verwaltung ein Implementierungsdetail dieser API ist. Beispiele hierfür sind anwendungsspezifische Implementierungen wie Amazon DynamoDB Accelerator (DAX) und standardbasierte Implementierungen wie HTTP-Caching (entweder mit einem lokalen Caching-Client oder einem externen Cache-Server wie Nginx oder Varnish). Side-Caches sind dagegen generische Objektspeicher, wie sie von Amazon ElastiCache (Memcached und Redis) bereitgestellt werden, oder Bibliotheken wie Ehcache und Google Guava für In-Memory-Caches. Bei Side-Caches bearbeitet der Anwendungscode den Cache direkt vor und nach Aufrufen der Datenquelle, prüft vor dem Ausführen der Downstream-Aufrufe auf zwischengespeicherte Objekte und fügt Objekte nach Abschluss dieser Aufrufe in den Cache ein.

Der Hauptvorteil eines Inline-Caches ist ein einheitliches API-Modell für Clients. Das Chaching kann hinzugefügt, entfernt oder optimiert werden, ohne dass die Client-Logik geändert wird. Durch einen Inline-Cache wird auch die Cache-Verwaltungslogik aus dem Anwendungscode entfernt, wodurch eine Quelle potenzieller Fehler beseitigt wird. HTTP-Caches sind besonders attraktiv, da zahlreiche Standardoptionen zur Verfügung stehen, z. B. In-Memory-Bibliotheken, eigenständige HTTP-Proxys wie die zuvor genannten und verwaltete Dienste wie Content Delivery Networks (CDNs).

Die Transparenz von Inline-Caches kann jedoch auch ein Nachteil der Verfügbarkeit sein. Externe Caches sind jetzt Teil der Verfügbarkeitsgleichung für diese Abhängigkeit. Der Client hat keine Möglichkeit, einen vorübergehend nicht verfügbaren Cache zu kompensieren. Wenn Sie beispielsweise eine Lackflotte haben, die Anforderungen von einem externen REST-Service zwischenspeichert, ist es aus Sicht Ihres Service so, als ob die Abhängigkeit selbst gesunken wäre, wenn diese Zwischenspeicherflotte ausfällt. Der andere Nachteil eines Inline-Caches ist, dass er in das Protokoll oder den Dienst eingebaut werden muss, für den er zwischengespeichert wird. Wenn kein Inline-Cache für das Protokoll verfügbar ist, ist dieses Inline-Caching nur dann eine Option, wenn Sie selbst einen integrierten Client- oder Proxy-Dienst erstellen möchten.

Cacheablauf

Einige der schwierigsten Details zur Cache-Implementierung sind die Auswahl der richtigen Cache-Größe, Ablaufrichtlinie und Bereinigungsrichtlinie. Die Bereinigungsrichtlinie bestimmt, wie lange ein Element im Cache aufbewahrt werden soll. Die gängigste Richtlinie verwendet einen absoluten zeitbasierten Ablauf (d. h. sie ordnet jedem Objekt beim Laden eine TTL (Time to Live) zu). Die TTL wird basierend auf den Clientanforderungen ausgewählt, z. B. wie tolerant der Client gegenüber veralteten Daten ist und wie statisch die Daten sind, da sich langsam ändernde Daten aggressiver zwischengespeichert werden können. Die ideale Cachegröße basiert auf einem Modell des erwarteten Anforderungsvolumens und der Verteilung der zwischengespeicherten Objekte auf diese Anforderungen. Daraus schätzen wir eine Cachegröße, die bei diesen Verkehrsmustern eine hohe Cachetrefferquote gewährleistet. Die Bereinigungsrichtlinie steuert, wie Elemente aus dem Cache entfernt werden, wenn die Kapazität erreicht ist. Die am häufigsten angewendete Bereinigungsrichtlinie ist LRU (Least Recently Used).

Bisher ist dies nur eine Gedankenübung. Die realen Datenverkehrsmuster können von unseren Modellen abweichen. Daher verfolgen wir die tatsächliche Leistung unseres Caches. Unser bevorzugter Weg, dies zu tun, besteht darin, Service-Metriken zu Cache-Treffern und -Fehlern, der Gesamt-Cache-Größe und der Anzahl von Anfragen an nachgelagerte Services auszugeben.

Wir haben gelernt, dass wir bei der Auswahl der Werte für die Cache-Größe und die Ablaufrichtlinie bewusst vorgehen müssen. Wir möchten vermeiden, dass ein Entwickler während der ersten Implementierung willkürlich eine bestimmte Cache-Größe und TTL-Werte auswählt und diese dann zu einem späteren Zeitpunkt nicht mehr überprüft. Wir haben Beispiele aus der Praxis gesehen, die gezeigt haben, dass es an Folgemaßnahmen mangelt, die zu vorübergehenden Ausfällen und einer Verschärfung der andauernden Ausfälle führen

Ein weiteres Muster zur Verbesserung der Ausfallsicherheit bei nicht verfügbaren Downstream-Diensten ist die Verwendung von zwei TTLs: einer weichen TTL und einer harten TTL. Der Client versucht, zwischengespeicherte Elemente basierend auf der Soft-TTL zu aktualisieren. Wenn der Downstream-Dienst jedoch nicht verfügbar ist oder auf andere Weise nicht auf die Anforderung reagiert, werden die vorhandenen Cache-Daten weiterhin verwendet, bis die Hard-TTL erreicht ist. Ein Beispiel für dieses Muster wird im IAM-Client (AWS Identity and Access Management) verwendet.

Wir verwenden auch den weichen und harten TTL-Ansatz mit Gegendruck, um die Auswirkungen von Downstream-Service-Brownouts zu reduzieren. Der Downstream-Service kann mit einem Rückstauereignis reagieren, wenn er abgebräunt ist. Dadurch wird signalisiert, dass der aufrufende Dienst zwischengespeicherte Daten bis zur festen TTL verwenden und nur Daten anfordern soll, die sich nicht in seinem Cache befinden. Wir setzen dies fort, bis der nachgeschaltete Service den Gegendruck entfernt. Dieses Muster ermöglicht es dem Downstream-Service, sich von einem Spannungsabfall zu erholen, während die Verfügbarkeit der Upstream-Services erhalten bleibt.

Andere Überlegungen

Eine wichtige Überlegung ist, wie sich der Cache verhält, wenn Fehler vom Downstream-Dienst empfangen werden. Eine Möglichkeit, damit umzugehen, besteht darin, Clients mit dem zuletzt zwischengespeicherten guten Wert zu antworten, z. B. mithilfe des zuvor beschriebenen Soft-TTL/Hard-TTL-Musters. Eine andere Option, die wir verwenden, besteht darin, die Fehlerantwort (d. h. wir verwenden einen "negativen Cache") mit einer anderen TTL als positive Cache-Einträge zwischenzuspeichern und den Fehler an den Client weiterzuleiten. Die Vorgehensweise, die wir in einer bestimmten Situation wählen, hängt von den Details des Dienstes ab und davon, wann es für Kunden besser ist, veraltete Daten im Vergleich zu Fehlern zu sehen. Unabhängig davon, welchen Ansatz wir wählen, ist es wichtig, dass wir in Fehlerfällen sicherstellen, dass sich etwas im Cache befindet. Wenn dies nicht der Fall ist und der Downstream-Dienst vorübergehend nicht verfügbar ist oder bestimmte Anforderungen auf andere Weise nicht erfüllen kann (z. B. wenn eine Downstream-Ressource gelöscht wird), bombardiert der Upstream-Dienst sie weiterhin mit Datenverkehr und kann möglicherweise einen Ausfall verursachen oder einen verstärken bestehende. Wir haben Beispiele aus der Praxis gesehen, in denen ein Versagen, negative Antworten zwischenzuspeichern, zu erhöhten Ausfallraten und Fehlern führte.

Sicherheit ist ein weiterer wichtiger Aspekt des Caching. Wenn wir einem Service einen Cache hinzufügen, bewerten und mindern wir alle zusätzlichen Sicherheitsrisiken, die dadurch entstehen. Beispielsweise fehlt externen Caching-Flotten häufig die Verschlüsselung für serialisierte Daten und die Sicherheit auf Transportebene. Dies ist besonders wichtig, wenn vertrauliche Benutzerinformationen im Cache gespeichert werden. Das Problem kann durch die Verwendung von Amazon ElastiCache for Redis behoben werden, das die Verschlüsselung während der Übertragung und im Ruhezustand unterstützt. Caches sind auch anfällig für Vergiftungsangriffe, bei denen eine Sicherheitsanfälligkeit im Downstream-Protokoll es einem Angreifer ermöglicht, einen Cache mit einem von ihm kontrollierten Wert zu füllen. Dies verstärkt die Auswirkung eines Angriffs, da alle Anforderungen, die gesendet werden, während dieser Wert im Cache verbleibt, den böswilligen Wert sehen. Für ein letztes Beispiel sind Caches auch anfällig für Side-Channel-Timing-Attacken. Zwischengespeicherte Werte werden schneller als nicht zwischengespeicherte Werte zurückgegeben, sodass ein Angreifer mithilfe der Antwortzeit Informationen zu Anforderungen abrufen kann, die andere Clients oder Tenets stellen.

Eine letzte Überlegung ist die Situation der „donnernden Herde“, in der viele Kunden Anfragen stellen, die ungefähr zur gleichen Zeit dieselbe nicht zwischengespeicherte Downstream-Ressource benötigen. Dies kann auch auftreten, wenn ein Server hochfährt und sich mit einem leeren lokalen Cache der Flotte anschließt. Dies führt dazu, dass eine große Anzahl von Anforderungen von jedem Server an die Downstream-Abhängigkeit gesendet wird, was zu einem Throttling/Brownout führen kann. Um dieses Problem zu beheben, verwenden wir die Anforderungszusammenführung, bei der die Server oder der externe Cache sicherstellen, dass nur eine ausstehende Anforderung für nicht zwischengespeicherte Ressourcen verfügbar ist. Einige Caching-Bibliotheken bieten Unterstützung für das Zusammenführen von Anforderungen, und einige externe Inline-Caches (wie Nginx oder Varnish) unterstützen dies ebenfalls. Darüber hinaus kann die Anforderungszusammenführung über vorhandene Caches implementiert werden. 

Best Practices und Überlegungen zu Amazon

In diesem Artikel wurden einige bewährte Methoden von Amazon sowie die mit dem Zwischenspeichern verbundenen Kompromisse und Risiken angesprochen. Im Folgenden finden Sie eine Zusammenfassung der Best Practices und Überlegungen von Amazon, die unsere Teams bei der Einführung eines Caches verwenden:

• Stellen Sie sicher, dass ein legitimer Bedarf für einen Cache besteht, der hinsichtlich Kosten, Latenz und/oder Verfügbarkeitsverbesserungen gerechtfertigt ist. Stellen Sie sicher, dass die Daten zwischengespeichert werden können. Dies bedeutet, dass sie für mehrere Clientanforderungen verwendet werden können. Seien Sie skeptisch gegenüber dem Wert, den ein Cache bringt, und prüfen Sie sorgfältig, ob die Vorteile die zusätzlichen Risiken überwiegen, die der Cache mit sich bringt.
• Planen Sie, den Cache mit derselben Sorgfalt und denselben Prozessen zu betreiben, die auch für den Rest der Service-Flotte und -Infrastruktur verwendet werden. Unterschätzen Sie diesen Aufwand nicht. Geben Sie Kennzahlen zur Cache-Auslastung und Trefferquote ein, um sicherzustellen, dass der Cache entsprechend optimiert ist. Überwachen Sie wichtige Indikatoren (wie z. B. CPU und Arbeitsspeicher), um sicherzustellen, dass die externe Caching-Flotte fehlerfrei arbeitet und angemessen skaliert ist. Richten Sie Alarme für diese Metriken ein. Stellen Sie sicher, dass die Caching-Flotte ohne Ausfallzeit oder Ungültigmachung des Massencaches vergrößert werden kann (stellen Sie also sicher, dass das konsistente Hashing wie erwartet funktioniert.)
• Seien Sie bei der Auswahl der Cache-Größe, der Ablaufrichtlinie und der Bereinigungsrichtlinie bewusst und empirisch. Führen Sie Tests durch und verwenden Sie die im vorherigen Aufzählungszeichen genannten Metriken, um diese Auswahl zu validieren und zu optimieren.
• Stellen Sie sicher, dass Ihr Dienst angesichts der Nichtverfügbarkeit des Caches ausfallsicher ist. Dies umfasst eine Reihe von Umständen, die dazu führen, dass Anforderungen nicht mit zwischengespeicherten Daten bedient werden können. Dazu gehören Kaltstarts, das Zwischenspeichern von Flottenausfällen, Änderungen im Verkehrsverhalten oder erweiterte Ausfälle im nachgelagerten Bereich. In vielen Fällen kann dies bedeuten, dass Sie einen Teil Ihrer Verfügbarkeit austauschen, um sicherzustellen, dass Ihre Server und abhängigen Dienste nicht beeinträchtigt werden (z. B. indem Sie die Last reduzieren, Anforderungen an abhängige Dienste begrenzen oder veraltete Daten bereitstellen). Führen Sie Auslastungstests mit deaktivierten Caches durch, um dies zu überprüfen.
• Berücksichtigen Sie die Sicherheitsaspekte bei der Verwaltung zwischengespeicherter Daten, einschließlich Verschlüsselung, Transportsicherheit bei der Kommunikation mit einer externen Caching-Flotte sowie die Auswirkungen von Cachevergiftungsangriffen und Seitenkanalangriffen.
• Entwerfen Sie das Speicherformat für zwischengespeicherte Objekte, das sich im Laufe der Zeit entwickelt (verwenden Sie beispielsweise eine Versionsnummer), und schreiben Sie einen Serialisierungscode, der ältere Versionen lesen kann. Hüten Sie sich vor ungewollten Überraschungen in Ihrer Cache-Serialisierungslogik.
• Bewerten Sie, wie der Cache Downstream-Fehler behandelt, und erwägen Sie, einen negativen Cache mit einer eindeutigen TTL zu verwalten. Verursachen oder verstärken Sie einen Ausfall nicht, indem Sie wiederholt nach derselben Downstream-Ressource fragen und die Fehlerantworten verwerfen.

Viele Serviceteams bei Amazon verwenden Caching-Techniken. Trotz der Vorteile dieser Techniken entscheiden wir uns nicht für die leichte Integration von Caching, da die Nachteile häufig die Nachteile überwiegen. Wir hoffen, dass dieser Artikel Ihnen bei der Bewertung der Zwischenspeicherung in Ihren eigenen Diensten hilft.


Über die Autoren

Matt Brinkley ist Principal Engineer für Emerging Devices bei Amazon, d. h. er arbeitet an Software und Services für neue Verbrauchergeräte. Zuvor arbeitete er bei AWS Elemental und leitete das Team, das MediaTailor, einen serverseitigen personalisierten Adinsertion-Service für Live- und On-Demand-Videos, auf den Markt gebracht hat. Unterwegs half er dabei, PrimeVideos erste Staffel mit NFL Thursday Night Football zu starten. Vor seiner Zeit bei Amazon war Matt 15 Jahre in der Sicherheitsbranche tätig, unter anderem bei McAfee, Intel und einigen Startups, wo er sich mit Sicherheitsmanagement für Unternehmen, Anti-Malware- und Anti-Exploit-Technologien, hardwaregestützten Sicherheitsmaßnahmen und DRM befasste.

Jas Chhabra ist Principal Engineer bei AWS. Er kam 2016 zu AWS und arbeitete einige Jahre bei AWS IAM, bevor er seine derzeitige Position bei AWS Machine Learning antrat. Vor AWS arbeitete er bei Intel in verschiedenen technischen Funktionen in den Bereichen IoT, Identität und Sicherheit. Aktuelle Interessen sind Machine Learning, Sicherheit und verteilte Großsysteme. Zu den bisherigen Interessen zählen IoT, Bitcoins, Identität und Kryptografie. Er hat einen Master in Informatik.

Vermeiden von Fallback in verteilten Systemen Load Shedding zur Vermeidung von Überlastzuständen Gewährleistung von Rollback-Sicherheit während der Bereitstellung