Ein Loblied auf memcached
(jchri.st)- Caches werden eingeführt, um die Last auf Datenbanken zu verringern, aber leicht nutzbare Werkzeuge wie Redis werden mit der Zeit leicht wie ein persistenter Speicher genutzt, auf den man sich verlässt
- Das Problem ist nicht die Persistenzfunktion von Redis, sondern der Betriebsablauf, bei dem eine ursprünglich als flüchtiger Cache eingeführte Komponente mit dem Kernzustand der Anwendung verflochten wird
- memcached ist schon in seiner offiziellen Definition ein verteiltes In-Memory-Objekt-Caching-System und nicht auf Speicherung auf Festplatte ausgelegt, weshalb es sich leicht als zustandsloser Cache-Workload behandeln lässt
- Mehrere memcached-Instanzen werden nicht serverseitig, sondern vom Client anhand einer URL-Liste und eines Schlüssel-Hashs aufgeteilt; ausgefallene Knoten werden aus dem Hasher entfernt und später erneut verbunden
- Statt bei „die Datenbank ist langsam“ zuerst einen Cache hinzuzufügen, sollte man zunächst langsame Queries und fehlende Indizes prüfen
Der Moment, in dem Redis vom Cache zum Speicher wird
- Wer Infrastruktur betreibt, hört oft die Anforderung „Wir brauchen einen Cache“, und dann fällt einem schnell das vertraute und funktionsreiche Redis ein
- Auf der Redis-Website steht Redis Iris, eine Echtzeit-Context-Engine für AI-Apps, im Vordergrund, aber dass Redis als Unternehmen Umsatz machen muss, macht diese Richtung nachvollziehbar
- Wenn man Redis ausrollt und einen Connection-String weitergibt, verhält es sich zunächst wie ein zuverlässiger Cache
Probleme, die ein paar Monate später auftreten
- Mit der Zeit ist
cache.set("key", "value")viel einfacher alsINSERT INTO table VALUES ('key', 'value'), sodass Leute Redis allmählich wie Folgendes behandeln- eine Komponente, die immer da ist, ein Ort zum Aufbewahren von Daten: faktisch eine Datenbank
- REmote DIctionary Server wird nicht mehr als flüchtiger Cache, sondern als permanenter Speicher wahrgenommen
- Weder Sie noch Ihre Kolleginnen und Kollegen im Ops-Team merken das, und auch das Alerting-System erkennt es nicht, weil alle davon ausgehen, dass der Cache flüchtig behandelt wird
- Erst wenn man Redis irgendetwas antut, etwa bei einem Upgrade, einer Node-Migration oder wenn bei einem RAID0-Server ein HDD-Tray herausrutscht, tritt das Problem zutage
- Das Kernproblem ist nicht, dass Redis keine Persistenz hätte, sondern dass die Annahme nicht stimmt, dass ein als Cache eingeführtes Redis auch wie ein Cache behandelt wird
- Entdeckt man diese Abhängigkeit erst spät, ist Redis schon zu tief in die Anwendung verflochten, um es leicht zu entfernen, und muss am Ende wie ein „Haustier“ gewartet und überwacht werden
Warum memcached direkter die Cache-Rolle erfüllt
- memcached ist ein „freies Open-Source-, hochperformantes verteiltes In-Memory-Objekt-Caching-System“ und ein universeller Cache, der die Last auf Datenbanken senken soll, um dynamische Webanwendungen zu beschleunigen
- In Frameworks wie Django, die plugbares Caching unterstützen, lässt sich das Caching-Backend austauschen
- Auch wenn Redis mehr Funktionen hat, gibt es gute Gründe, memcached zu wählen: Seine Betriebseigenschaften sind einfacher
- Downtime lässt sich leicht behandeln: Client-Bibliotheken ignorieren Verbindungsfehler oft, und ein einfaches
getkann selbst bei ausgefallenem Server einen Default-Wert oderNonezurückgeben - memcached hat keine eingebaute Clustering-Funktion, was Clustering paradoxerweise bequemer macht
- Konfiguriert man in der Client-Bibliothek mehrere URLs, wählt sie per Schlüssel-Hash die Zielinstanz aus
- Erkennt der Client-Aufruf einen Instanzausfall, wird der Knoten aus dem Hasher entfernt, und nach einiger Zeit wird automatisch ein erneuter Verbindungsversuch unternommen
- Die Persistenzlast sinkt strukturell: memcached speichert nicht auf Festplatte und lässt sich daher gut überall als zustandsloser Workload einplanen
- Downtime lässt sich leicht behandeln: Client-Bibliotheken ignorieren Verbindungsfehler oft, und ein einfaches
- Man kann mit Redis einen ähnlichen Betriebsstil aufbauen, aber die Architektur von memcached liegt näher an dieser Richtung und ist intuitiver als Cache zu handhaben
- memcached ist eine vergleichsweise einfache Anwendung, und ein weiterer Grund für die Wahl ist, dass es kaum Overhead gibt, selbst wenn man Dutzende Instanzen mit etwa 64 MB Cache-Größe betreibt
- Viele Probleme der Art „die Datenbank ist langsam“ beginnen in Wirklichkeit bei langsamen Queries oder fehlenden Indizes; deshalb sollte man neben dem Hinzufügen eines Caches auch die Query-Optimierung prüfen
- Wer sich für die Designentscheidungen von memcached interessiert, findet im memcached blog viele interessante Beiträge, darunter den im Mai veröffentlichten Artikel „How Long Does That Response Take… For Real?“
1 Kommentare
Hacker-News-Kommentare
Redis ist eine großartige Technologie, hat aber meiner Ansicht nach Schwierigkeiten damit, zwei unterschiedliche Rollen gleichzeitig gut zu erfüllen: persistente Datenstrukturen und flüchtigen Cache
Auch in Redis selbst lassen sich diese beiden Dinge nicht gut mischen, daher wird Persistenz im Grunde global ein- oder ausgeschaltet.
Für reinen Cache würde ich memcached oder etwas Gleichwertiges verwenden und Redis mit aktivierter Persistenz nur dann, wenn man Datenstrukturen wie etwa ein Scoreboard braucht.
Bei $WORK haben wir keines von beiden eingeführt; für die Cache-Schicht langsamer Aufgaben halten wir Daten sowohl im Dateisystem als auch in einer DB-Tabelle, die wie ein Key-Value-Store genutzt wird.
Die DB hilft dabei, eine thundering herd zu koordinieren, Lesezugriffe auf demselben Server treffen nur das Dateisystem, und Lesezugriffe von anderen Servern schauen einmal in die DB und halten es danach im Dateisystem.
Man könnte die Dateisystem-Schicht auch durch memcached ersetzen, aber bisher funktioniert das sehr gut.
Redis hatte ganz klar mehr Funktionen, und antirez war auch eine charismatische und erstaunlich bescheidene Person, daher ist nachvollziehbar, warum Redis populärer geworden ist.
Trotzdem war memcached für mich immer der Höhepunkt von wähle langweilige Technologie.
Als Plattformingenieur kann ich beides unterstützen, aber wenn Entwickler anfangen, fortgeschrittene Redis-Funktionen wie Persistenz, Replikation und Clustering zu nutzen, versuche ich sicherzustellen, dass sie die Nachteile dieser Entscheidung wirklich verstehen.
Jedes Mal, wenn ich so eine Lösung vorgeschlagen habe, habe ich endlos mit unerfahrenen Leuten und Leuten aus dem Engineering gestritten, die das Gefühl hatten, ein Cache müsse zwingend in einem dedizierten Store liegen.
Nur weil es memcache ist, vermeidet man diese Probleme keineswegs automatisch.
Mitte der 2000er habe ich mit einem Skalierungssystem gearbeitet, das memcache nutzte, und die Entwickler sind in genau dieselben Fallen getappt, die im Artikel bei Redis als Beispiele genannt werden.
Sie versuchten, mit memcache die Gesetze verteilter Systeme zu umgehen, und wegen Cache-Sucht wurde die Serverflotte unter der Annahme dimensioniert, dass memcache läuft; bei einem Ausfall explodierte das Ganze dann plötzlich wie ein DDoS.
Wenn ein Host einen Key mit hoher TPS verlor, gab es außerdem write amplification, weil alle anderen Hosts versuchten, diesen Key neu zu befüllen und dabei den abhängigen Dienst bombardierten; Hot Keys erzeugten Hot Hosts, und memcached lief zusammen mit dem Service-Daemon, was zu rätselhaften CPU-Spitzen führte.
Wegen der Hartnäckigkeit alter DNS-Einträge verschwanden memcache-Aufrufe manchmal auch in einem Black Hole.
All das hätte man mit besserer memcache-Nutzung vermeiden können, aber die Versuchung zum Missbrauch war einfach zu groß.
Ich glaube, fast alle vom Autor erwähnten Redis-/Valkey-Probleme habe ich in Produktion schon gesehen.
Es gab einen Ausfall, bei dem Valkey keine Memory-Policy hatte, den gesamten Speicher auffraß und Fehler beim Schreiben der append-only file verursachte; in einem anderen Fall war die Platte selbst voll, sodass AOF-Schreibvorgänge fehlschlugen.
Es gab auch schon 500er-Fehler, weil vollständig vorausgesetzt wurde, dass Redis lebt, läuft und mit allen Benutzerdaten gefüllt ist, ohne irgendeinen Mechanismus, auf den langsamen Pfad zurückzufallen.
Ich habe auch kreative Nutzungen von Sorted Sets und anderen Datenstrukturen gesehen, bei denen man sich darauf verließ, dass diese Mengen niemals evicted werden.
Trotz all dieser Beobachtungen finde ich es weiterhin schwer, memcache vor Redis zu empfehlen.
Es kann knifflig sein, eine Anwendung so zu entwerfen, dass sie ein memcache-freundliches Cache-Layout hat, und wenn ein ausreichend großes Team memcache nutzt, ist die Wahrscheinlichkeit sehr hoch, dass es am Ende doch einen Weg findet, Redis zu brauchen.
Dann muss man zwei Cache-Technologien pflegen.
Eine als Cache konfigurierte Redis-Instanz kann nicht für andere Zwecke genutzt werden; die Cache-Instanz muss Eviction haben, die Nicht-Cache-Instanz darf keine Eviction haben.
Am Ende braucht man also ein zweites Redis mit anderer Konfiguration.
Ehrlich gesagt ist es beim Entwurf einer App für ein memcache-freundliches Cache-Layout genauso wie bei einem Redis-freundlichen Cache-Layout.
Die Muster für solche Anwendungs-Caches sind identisch: holen, und wenn nichts da ist, berechnen und setzen.
var value = cache.lookup( keyname, () => db.query(...), TimeSpan.FromMinutes(5) // oder CacheOptions );Damit kann man bei einem Cache-Miss sofort auf den Ersatzpfad gehen oder direkt einfügen.
Eine weitere Eigenschaft von memcache, die selten erwähnt wird, ist, dass alle Operationen per Design O(1) sind.
Das ist eine bewusst gewählte Architektur der Autoren; sie bringt Einschränkungen mit sich, garantiert aber, dass es bei einfachen Operationen keine zufälligen Hänger gibt.
Redis dagegen hat ein Single-Thread-Core-Design und kann Operationen beliebiger Komplexität ausführen; aus Entwicklersicht fühlt man sich damit vielleicht schlau, aber bis diese Operation fertig ist, müssen alle anderen warten.
In Open-Source-Projekten oder Programmen, die langfristig gepflegt werden, passiert so etwas häufig.
Wenn die Codebasis wächst, beginnt man am Ende Dinge zu unterstützen, die ursprünglich nicht geplant waren.
Mit mehr Funktionen kommen auch mehr Nutzer: Manche verwenden nur die alten Funktionen, andere übernehmen die neuen, und am Ende wird ein bestimmter Wert zum De-facto-Standard und wirkt nicht mehr wie eine echte Option.
Nimmt man Redis als Beispiel, dann arbeitet es mit deaktiviertem AOF als flüchtiger In-Memory-Cache, aber die meisten sehen es gar nicht so.
Daher kommt die Argumentation, dass weniger Funktionen und mehr Einfachheit besser seien, und in diesem Kontext ist Memcached ein Beispiel für diesen westeingrenzenden Ansatz.
Für große Teams ist das vollkommen nachvollziehbar, aber Open-Source-Projekte brauchen regelmäßige Updates, um weiterhin Finanzierung oder Beiträge zu erhalten, also gibt es eine inhärente Spannung.
Manchmal führt das zu Forks oder abgeleiteten Projekten, die auf eine bestimmte Nische spezialisiert sind.
Ich persönlich denke, dass es keine richtige Antwort gibt und alles vom Kontext abhängt.
Denn auch Kommunikation ist nicht kostenlos.
Entwickler wirken oft, als wüssten sie das überhaupt nicht.
Ich denke, das liegt daran, dass sie Memcached durch Redis ersetzt haben und dasselbe erwartet haben.
Trotzdem ist es ein hervorragender Cache.
Ich habe in den letzten Jahren ziemlich viel mit Flask gearbeitet und es, nicht in Vollzeit, aber als Teil des Tech-Stacks eines kleinen E-Commerce-Unternehmens eingesetzt.
Mit MongoEngine, SQLAlchemy, Celery und dem Python-Stack für Google/eBay/Shopify bin ich auf allerlei Tretminen und Merkwürdigkeiten gestoßen, aber bei Redis nie.
Vielleicht liegt es daran, dass wir niemandem Admin-Rechte geben, der Redis für einen persistenten Datenspeicher hält, aber ehrlich gesagt würde ich Redis als sehr robuste und gut entworfene Technologie bezeichnen.
Die API ist extrem einfach, und immer wenn man etwas leicht Ungewöhnliches tun muss, gibt es dafür einen vernünftigen und gut durchdachten Weg.
Man kann vielleicht ein Invalidierungssystem wie Tagging verwenden.
Mich interessiert ehrlich, was für merkwürdige Dinge man mit einem Cache-System machen kann, also wofür Leute Caches außer dem bloßen Zwischenspeichern von Daten noch verwenden.
Ich mag memcached, aber wenn Redis als flüchtiger Cache konfiguriert ist und Leute es trotzdem wie einen persistenten Datenspeicher behandeln, dann ist das nicht die Schuld von Redis.
Der Vergleich ist besonders seltsam, weil memcached ebenfalls nicht persistent ist.
Ohne gegenteilige Hinweise ist es nicht unvernünftig, wenn neue Entwickler davon ausgehen.
Memcached war bei seiner Veröffentlichung der Retter des Cachings.
Ich finde auch gut, dass Brad Fitzpatrick es 2003 für LiveJournal entwickelt hat.
Für jeden Beitrag in einem Nutzer-Feed konnten andere Zugriffsbeschränkungen gelten, und dadurch war es möglich, Beiträge oder ganze Seiten zu cachen.
Ich habe es zusammen mit Ruby on Rails viele Jahre verwendet, die Seiten wurden schneller, und es hat einfach funktioniert.
Der Nachteil und zugleich Geschwindigkeitsvorteil war, dass der Cache im Arbeitsspeicher statt auf der Festplatte lag.
Wenn die zu cachenden Daten breit gefächert sind und man eine große Website betreibt, können die Hosting-Kosten hoch werden.
In solchen Fällen war Solid Cache für mich der Retter.
In dem Projekt, an dem ich gerade arbeite, umfasst der Cache über 100 GB, wird auf PostgreSQL-Datenträgern gespeichert, per Index schnell abgefragt, und Rails kümmert sich automatisch um das Ablaufdatum und löscht die entsprechenden Zeilen.
Wenn der Cache kleiner wäre und ich Redis ohnehin schon nutzen würde, würde ich wahrscheinlich einfach Redis verwenden.
Aber wenn Geschwindigkeit oberste Priorität hat, würde ich Memcached und Redis gegeneinander benchmarken.
Dass memcached flüchtig ist und ob Leute es so benutzen, als wäre es persistent, sind zwei verschiedene Dinge.
Wenn die Cache-Trefferquote bei 99,9 % zu liegen scheint und die Daten immer da sind, wird früher oder später jemand Code schreiben, der sich auf dieses Verhalten verlässt.
Vielleicht könnte im Entwicklungsmodus die Client-Bibliothek helfen, indem sie in etwa 10 % der Fälle
nullzurückgibt.memcached ist bei einfachen Key-Value-Cache-Aufgaben absurd viel schneller als Redis.
Es hat Threads und ist stark darauf optimiert, eine Sache sehr gut zu machen.
Redis fühlt sich dagegen eher wie ein beliebiger gemeinsam genutzter Python-Heap mit all seinen Datenstrukturen und dem Single-Thread-Modell an.
Bei Notion wird Redis für vieles verwendet, aber das eigentliche Caching übernimmt memcached.
Im Mittel sind es etwa 300 Mikrosekunden gegenüber 350 Mikrosekunden pro Lesezugriff.
Dass Redis Single-Threaded ist, spielt ebenfalls keine große Rolle, weil der Flaschenhals nicht die CPU, sondern reaktive I/O ist.
Sie erlauben die Nutzung von mehr CPU-Kernen, aber wenn die Last nicht besonders hoch ist, verbraucht ein Single-Thread-memcached weniger CPU als ein Multi-Thread-System.