Varnish-Cache: Dimensionierung des Cache

Die üblicherweise vom Development-Team propagierte Konfiguration nutzt eine Cachedatei im Filesystem. Diese Cachedatei wird jedoch erst dann verwendet, wenn der Cache nicht mehr vollständig im Hauptspeicher gehalten werden kann. Viele Administratoren legen diese Cachedatei nun auf ein tmpfs um trotzdem sämtliche Daten im Hauptspeicher zu halten und swappen zu vermeiden.

Jedoch gibt es auch die Möglichkeit den Varnish mittels Startparameter -s malloc,2G (Beispiel) komplett im Hauptspeicher cachen zu lassen (also ohne Anlegen einer Cachedatei im Filesystem). Sehr wichtig dabei ist jedoch die korrekte Konfiguration der Cachegröße, da das Betriebssystem sonst sehr schnell mit dem Swappen beginnen kann. Die Performance nimmt dann natürlich massiv ab.

Es gibt also eine goldene Grundregel:

Varnish-Aktivitäten sollten niemals Disk-I/O erzeugen!

Über das Commando varnishstat -f sma_nbytes,sma_balloc,sma_bfree kann eingesehen werden, wie viel vom konfigurierten Cache tatsächlich verwendet wird. Eine Vergrößerung des Caches wird keinerlei Ergebnis bringen, wenn der bislang konfigurierte Wert sowieso nicht genutzt wird.

Wer also denkt „Je größer der Cache, desto besser“, der liegt absolut falsch!

Weiterhin ist die Cache-Hitrate IMMER die wichtigste Maßzahl. Liegt die Hitrate über 90%, bringt auch eine Vergrößerung des Cache keine wesentliche Verbesserung mehr. Liegt die Hitrate jedoch bei 50% und der verwendete Cache (SMA outstanding bytes) geht gegen das konfigurierte Maximum, so wird eine Vergrößerung des Caches eine deutliche Verbessung der Hitrate nach sich ziehen (korrekte VCL bzgl. Caching voraus gesetzt).

Beim Varnish-Cache muss das Zusammenspiel der einzelnen Parameter verstanden werden. Einerseits gibt es die Anzahl der Objekte die im Cache liegen, davon häufig mehrere Varianten (unterschiedliche Header, z.B. compressed, plain) und sehr wichtig natürlich die TTL, welche angibt, wie lange ein Objekt im Cache verbleibt. Außerdem ist die Speicherauslastung des Cache natürlich auch noch davon abhängig wie groß die einzelnen Objekte im Speicher eigentlich sind (z.B. Webseiten, Bild- und Mediadateien, usw).

Betrachten wir einen „Screenshot“ aus der Praxis:

Hitrate ratio:       10       36       36
Hitrate avg:     0.9783   0.9783   0.9783

   425956413          .            .   SMA outstanding bytes
  3036623137          .            .   SMA bytes allocated
  2610666724          .            .   SMA bytes free

Es darf gerechnet werden: SMA bytes allocated abzüglich SMA bytes free ergeben die aktuelle Cache-Größe SMA outstanding bytes. Dieser Wert wird die vorher konfigurierte Größe (hier: 2G) niemals überschreiten. SMA bytes allocated gibt den aufsummierten für neu zu cachende Objekte verwendeten Speicher an, SMA bytes free gibt den aufsummierten Speicher an, der nach expire oder purge wieder freigeben wurde (jeweils über die gesamte Laufzeit des Varnish-Workers).

Nehmen wir an, das Gesamtsystem hätte 8GB RAM. Will man mehr von den zur Verfügung stehenden 8GB RAM für das Caching verwenden, so muss außerdem berücksichtigt werden, dass jeder Worker-Prozess üblicherweise 8 MB RAM zusätzlich verbraucht (vgl. Stack Size, ulimit -s).

Nehmen wir weiterhin an, der Varnish-Cache sei mit 200 Worker-Threads konfiguriert, d.h. 200 x 8 MB = 1.6 GB werden für die Worker-Threads alloziiert. Hinzu kommen noch einige MB für regular Expressions, Libraries und die kompilierte VCL (~90 MB). Ein ‚pmap‘ auf dem Varnish-Worker zeigt nun den gesamten Speicherverbrauch (etwas über 2 GB) des Prozessimages an.

[...]
Total:           2395388K 607116K 606367K 547924K      0K

2288932K writable-private, 24044K readonly-private, 82412K shared, and 607028K referenced

Um also für Betriebssystem und auch andere Prozesse genügend Raum zu lassen, sollten im ersten Schritt nicht mehr als 4 GB insgesamt für den Varnish-Cache per malloc reserviert werden (in Bezug auf unser 8 GB RAM Beispiel). Möglicherweise kann nach Beobachtung der Speicherausnutzung nach mehreren Tagen auf 5 GB erhöht werden, falls die Maschine ein dedizierter Varnish-Server ist. Von mehr als 5 GB rate ich sicherheitshalber ab.

Generell sollte eine Vergrößerung des Cache-Speichers erst dann in Angriff genommen werden, wenn die Hitrate sinkt oder der aktuell konfigurierte Speicher zu nahezu 100% verwendet wird. (Siehe oben)

Übrigens: Mein Lieblingskommando ist
varnishstat -f n_object,n_objecthead,sma_nbytes,cache_hit,cache_miss,n_lru_nuked,n_lru_moved

Ausgabe:

Hitrate ratio:       10       40       40
Hitrate avg:     0.9695   0.9449   0.9449

     2267600       158.97        45.10 Cache hits
       49690         0.00         0.99 Cache misses
       15611          .            .   N struct object
       14323          .            .   N struct objecthead
      378271          .            .   N LRU moved objects
   487760313          .            .   SMA outstanding bytes

Erklärung:

  • n_object – Anzahl der gecachten Objekte (inkl. Variationen gleicher Objekte)
  • n_objecthead – Anzahl der gecachten Objekte (Host/URL-Tupel, ohne Variationen)
  • cache_hit – Selbsterklärend: Anzahl der Cache-Hits
  • cache_miss – Selbsterklärend: Anzahl der Cache-Misses
  • n_lru_nuked – Anzahl der aus dem Cache verworfenen Objekte weil der Cache voll war
  • n_lru_moved – Anzahl der in den Cache eingelesenen (und bewegten) Objekte
  • sma_nbytes – Größe des insgesamt aktuell verwendeten Caches in Bytes

Es mag verwundern, dass die Zeile für den Wert n_lru_nuked gar nicht vorhanden ist. Varnishstat optimiert hier generell: Zähler die den Wert 0 haben, werden nicht ausgegeben. Tatsächlich es ist es so, dass wenn für n_lru_nuked ein Wert größer Null ausgegeben wird, der Cache bereits zu klein ist, nicht mehr optimal arbeiten kann und daher Objekte deren TTL noch nicht abgelaufen ist trotzdem verwirft um Platz für neue Objekte zu machen.

Mit anderen Worten: „Wenn n_lru_nuked > 0, dann ging’s bereits schief…“