cluster-file-backend — TYPO3-Cache für Kubernetes, ohne Shared Filesystem.
TYPO3-Datei-Caches sicher über mehrere Kubernetes-Pods betreiben — ohne RWX-Volume. cluster-file-backend hält Cache-Payloads pod-lokal und Cache-Gültigkeit cluster-weit. Es ersetzt TYPO3s FileBackend und SimpleFileBackend für Cache-Workloads, unterstützt tag-basierte Invalidierung und entkoppelt Multi-Pod-Deployments vom Shared-Filesystem.
- Composer-Paket:
moselwal/cluster-file-backend:^2.3 - Extension-Key:
cluster_file_backend - TYPO3: 14.3+ (Composer-Mode only) · PHP: 8.5+ · MIT
TL;DR — Passt das zu meinem Setup?
Nutzen Sie es wenn …
- TYPO3 auf mehreren Kubernetes-Pods läuft oder laufen soll
- kein RWX-Volume zwischen den Pods existiert oder gewünscht ist
- tag-basierte Cache-Invalidierung clusterweit zuverlässig funktionieren muss
- Sie TYPO3s Standard-
FileBackendoderSimpleFileBackendersetzen möchten
Nutzen Sie es nicht wenn …
- Sie FAL, fileadmin oder einen Blob-Store suchen — das ist nicht der Anwendungsfall
- Ihre Installation single-pod läuft — Standard-
FileBackendreicht dann - PHP < 8.5 oder TYPO3 < 14.3 im Einsatz ist
Migrations-Aufwand
Einmaliges Setup: Metadaten-Cache registrieren, Cache-Konfigurationen umschreiben, emptyDir mounten. Keine TYPO3-Core-Änderungen, kein Code-Eingriff in die Applikation.
Empfohlenes Metadaten-Backend
Produktion: moselwal/keyvalue-store mit KeyValueBackend (Valkey/Redis, sub-ms Latenz, taggable). Einstieg ohne extra Dependency: TYPO3-Cores Typo3DatabaseBackend.
Was ist neu in v2.2 / v2.3
PhpCapableBackendInterface (v2.2)
Seit v2.2 implementiert ClusterFileBackend auch PhpCapableBackendInterface — damit bedient ein Backend sowohl VariableFrontend-Caches (pages, extbase, …) als auch PhpFrontend-Caches (typoscript, fluid_template). Der Payload-Store hängt bei PhpFrontend-Caches ein .php-Suffix an, damit OPcache die Dateien direkt ingestet. Kompression wird für PHP-Code erzwungen auf none. Cluster-Kohärenz kommt aus dem BackendVersion-gefalteten Hash-Pfad — jedes Deploy ergibt einen neuen Pfad, OPcache kühlt automatisch ab, kein opcache_invalidate() nötig.
Ein-Byte-Kompressionsmarker (v2.2)
0x00 = unkomprimiert, 0x01 = zstd, 0x02 = gzip. Der Leser wählt den Dekompressor anhand des Markers — der Schreiber kann Codecs mischen (z. B. kein Komprimieren bei kleinen Payloads, zstd für den Rest).
Skip-Compress für kleine Payloads (v2.2)
Option minCompressedBytes (Default 1024): Payloads unterhalb dieser Schwelle werden unkomprimiert gespeichert. Vermeidet den Fixed-Cost von gzdeflate/zstd_compress bei winzigen Werten. 0 = immer komprimieren.
Request-scoped Metadata-L1 (v2.2)
has() / get() / remove() treffen eine In-Memory-Map der jüngsten CacheMetadata-Lookups. Identische Identifier im selben Request überspringen den Valkey/DB-Roundtrip — ca. 200× schneller bei wiederholten has()-Aufrufen.
Request-scoped Payload-L1 (v2.3)
Vollständig dekodierte Payloads werden per LRU im RAM gecacht (Default: 32 Einträge / 4 MB). Wiederholte get()-Aufrufe auf ein heißes Identifier fallen von ca. 90 µs auf ca. 0,5 µs — 9–20× schneller als SimpleFileBackend auf jedem wiederholten Lesevorgang. Steuerbar über payloadL1MaxEntries und payloadL1MaxBytes. PhpFrontend-Caches überspringen den Payload-L1 (OPcache ist die bessere In-Memory-Repräsentation).
Prometheus-Counter cache_l1_hit_total (v2.3)
Neben cache_hit_total und cache_miss_total ermöglicht der neue Counter cache_l1_hit_total die Sichtbarkeit der drei-Schichten-Hit-Verteilung im Betrieb. Alert auf cache_miss_total{reason=metadata-error} für Frühwarnung bei Ausfall des Metadaten-Cache.
Drei-Schichten-Speicher und Architektur
┌─ FrankenPHP-Worker RAM ─────────────────────────────────────────┐
│ OPcache (kompilierter PHP-Code) → PhpFrontend .php-Dateien │
│ Payload-L1 (dekomprimierte Bytes) → VariableFrontend-Caches │
│ Metadata-L1 (CacheMetadata-Objekte) → alle Caches │
└─────────────────────────────────────────────────────────────────┘
▲
┌─ Pod-lokales emptyDir ──────────────────────────────────────────┐
│ /<localPath>/<shard>/<sha256>[.php] │
│ • VariableFrontend: 1-Byte-Marker + komprimierte Bytes │
│ • PhpFrontend: Klartext-PHP mit .php-Suffix │
│ → Source of Truth für die Payload-Bytes │
└─────────────────────────────────────────────────────────────────┘
▲
┌─ Metadaten-Cache (Valkey / DB) ─────────────────────────────────┐
│ ~300-Byte-Records: { hash, checksum, lifetime, tags, state } │
│ → Source of Truth für cluster-weite Cache-Gültigkeit │
│ → kein PHP-Code, keine Payload-Bytes │
└─────────────────────────────────────────────────────────────────┘Dieses Paket weiß nichts über Redis/Valkey/KV-Stores. Es spricht ausschließlich mit der TYPO3-Cache-API und delegiert die Cluster-Persistenz an ein vom Anwender gewähltes TYPO3-Cache-Backend.
TYPO3 Cache API → ClusterFileBackend
│
├─► Metadata-Cache (zweites TYPO3-Cache-Frontend,
│ Backend frei wählbar: Typo3DatabaseBackend,
│ KeyValueBackend, MemcachedBackend, …)
│
└─► Local Payload Store (pod-lokal, emptyDir)
Was es ist
- Kein RWX-Volume zwischen Pods erforderlich
- Zentrale Cache-Gültigkeit über die TYPO3-Cache-API
- Deterministische Re-Materialisierung über sha256-Hash-Validierung
- Tag-basierte Invalidierung clusterweit (via TYPO3
TaggableBackendInterface) - Garbage Collection über CLI (
clusterfilebackend:gc), delegiert ans Metadata-Cache-Backend - Deployment-Time-Warmup über CLI (
clusterfilebackend:warmup) plus Event-Listener auf TYPO3sCacheWarmupEvent
Was es nicht ist
- Kein Ersatz für TYPO3 FAL, fileadmin, den TYPO3-Core-Code-Cache (
var/cache/code/corebleibt im Container-Image), keinen Session-Store, keinen generischen Blob-Store, kein Distributed Filesystem - Bringt kein eigenes Redis/Valkey-Wissen mit — wer Redis als Cluster-Storage will, installiert ein TYPO3-Cache-Backend dafür (z. B. den
KeyValueBackendausmoselwal/keyvalue-store) und verweistClusterFileBackendpermetadataCacheIdentifierdarauf
Voraussetzungen
- TYPO3 14.3+ (Composer-Mode-only, kein
ext_emconf.php, kein Classic-Mode) - PHP 8.5+
- Composer-Paket
moselwal/cluster-file-backendab^2.3, Extension-Keycluster_file_backend, NamespaceMoselwal\Typo3ClusterCache\ - Lizenz MIT
TYPO3-Kubernetes-Migration geplant oder bereits im Betrieb? Wir helfen bei Architektur-Review, Cache- und Storage-Design, Migration und Plattform-Setup. Anfragen →
Setup-Voraussetzungen — was Sie einmalig tun
Das Paket registriert die Caches bewusst nicht automatisch. Hostnamen, Ports, TLS, Pfade sind grundsätzlich site-spezifisch. Die folgenden Schritte sind ein einmaliges Setup.
Fünf erforderliche Schritte
- Composer-Installation:
composer require moselwal/cluster-file-backend:^2.3 - Cluster-fähiges Cache-Backend für die Metadaten bereitstellen. Der Default verwendet TYPO3-Cores
Typo3DatabaseBackend— funktioniert ohne zusätzliche Dependency, solange die Datenbank von allen Pods erreichbar ist (Galera, RDS Multi-AZ, …). Für höhere Performancemoselwal/keyvalue-storeinstallieren und dessenKeyValueBackendnutzen. - TYPO3-Cache-Frontend (Konvention:
cluster_meta) für die Metadaten registrieren. - Die dateibasierten TYPO3-Caches (
pages,pagesection,rootline,imagesizes,assets,hash) aufClusterFileBackendumstellen und permetadataCacheIdentifieraufcluster_metaverweisen. - Pod-lokales
emptyDirunter/app/var/cache/cluster/(oder dem konfiguriertenlocalPath) mounten.
Was das Paket mitliefert
| Artefakt | Pfad | Zweck |
|---|---|---|
| Default-Config (ohne Extra-Deps) | Configuration/Example/cache-configurations.example.php | Datenbank-basierte Metadaten plus Cluster-File-Caches — läuft auf jeder TYPO3-Installation |
| Redis/Valkey-Config (optional) | Configuration/Example/cache-configurations-redis.example.php | Performance-Variante mit moselwal/keyvalue-store |
| JSON-Schema | Configuration/Backend/ClusterFileBackend.options.schema.json | Wird beim Backend-Konstruktor validiert — fehlerhafte Konfiguration führt zu InvalidCacheException mit Feldname |
| CLI-Kommandos | Configuration/Commands.php | clusterfilebackend:gc, clusterfilebackend:warmup |
| Event-Listener | Configuration/Services.yaml | Hängt sich in TYPO3s CacheWarmupEvent — bin/typo3 cache:warmup triggert die Cluster-Warmup mit |
| DI-Bindings | Configuration/Services.yaml | Auto-Discovery für MetricsPort, ClockPort, CompressorPort |
Konstruktor-Validation
Der ClusterFileBackend-Konstruktor validiert seine Optionen gegen ein JSON-Schema. Pflichtfelder (sonst InvalidCacheException): localPath (absoluter Pfad), metadataCacheIdentifier (Name des Metadata-Cache-Frontends), namespace.environment (prod, staging, testing oder development) und namespace.instance (Slug [a-z0-9-]{1,64}). Wenn der konfigurierte metadataCacheIdentifier nicht als TYPO3-Cache registriert ist, scheitert der Konstruktor sofort mit einer Nachricht, die den Config-Pfad benennt — kein stilles Fehlschlagen beim ersten set().
| Optionsfeld | Default | Bedeutung |
|---|---|---|
compression | zstd | zstd | gzip | none. Bei PhpFrontend-Caches immer auf none erzwungen. |
serializer | igbinary | igbinary | php. Wechsel invalidiert bestehende Einträge. |
defaultLifetimeSeconds | 3600 | TTL wenn der Caller null übergibt. Minimum 1 (Schema lehnt 0 ab). |
maxPayloadBytes | 10485760 (10 MB) | Schreibvorgänge darüber werden mit InvalidDataException abgelehnt. Obere Grenze für unkomprimierte Reads (zstd-Bomb-Mitigierung). |
minCompressedBytes | 1024 | Payloads unterhalb dieser Schwelle werden unkomprimiert gespeichert (1-Byte-Marker 0x00). Spart den Fixed-Cost von zstd_compress/gzdeflate bei winzigen Werten. 0 = immer komprimieren. |
payloadL1MaxEntries | 32 | Maximale Einträge im request-scoped Payload-L1. 0 deaktiviert den Payload-L1 (Metadata-L1 bleibt aktiv). LRU-Eviction. |
payloadL1MaxBytes | 4194304 (4 MB) | Soft-Memory-Budget für den Payload-L1. Einträge, die das Budget überschreiten würden, werden in Insertionsreihenfolge evictet. Ein einzelner Payload größer als dieses Budget umgeht den L1 komplett. 0 = kein Byte-Budget. |
backendVersionEnvVar | IMAGE_TAG | Env-Variable mit dem Deploy-Identifier. Wird in jeden Payload-Hash gefaltet — jedes Deploy bekommt einen frischen Pfad-Baum. Überschreiben für CI-Konventionen wie CI_COMMIT_SHA. |
Konfiguration — Quick-Start und Varianten
Quick-Start (null Extra-Dependencies)
Den Inhalt von vendor/moselwal/cluster-file-backend/Configuration/Example/cache-configurations.example.php in die config/system/settings.php (oder additional.php) kopieren und environment, instance und localPath auf das Deployment anpassen. Dieses Beispiel nutzt TYPO3-Cores Typo3DatabaseBackend für den Metadaten-Cache — cluster-sicher, sobald die Datenbank cluster-betrieben ist.
Redis/Valkey-Variante
Für Sub-Millisekunden-Latenz auf den Metadaten Configuration/Example/cache-configurations-redis.example.php nehmen. Verwendet den KeyValueBackend aus moselwal/keyvalue-store mit optionaler TLS- und Sentinel-Unterstützung.
Manuelles Setup
Schritt 1: Ein TYPO3-Cache-Frontend für die Metadaten definieren. Jedes Backend, das TaggableBackendInterface implementiert (für flushByTag), funktioniert.
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cluster_meta'] = [
'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
'backend' => \TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend::class,
'options' => [],
'groups' => ['system'],
];
Schritt 2:ClusterFileBackend auf den Metadaten-Cache verweisen — für alle dateibasierten Caches gleichzeitig.
foreach (['pages', 'pagesection', 'rootline'] as $cacheName) {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$cacheName] = [
'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
'backend' => \Moselwal\Typo3ClusterCache\Infrastructure\Cache\Backend\ClusterFileBackend::class,
'options' => [
'localPath' => '/app/var/cache/cluster/' . $cacheName,
'metadataCacheIdentifier' => 'cluster_meta',
'namespace' => [
'environment' => 'prod',
'instance' => 'website-a',
],
],
'groups' => ['pages'],
];
}Kubernetes-Deployment, Warmup und Garbage Collection
Pod-Volume für Payloads
volumes:
- name: cluster-cache
emptyDir: { sizeLimit: 2Gi }
volumeMounts:
- name: cluster-cache
mountPath: /app/var/cache/cluster
Deployment-Time-Warmup
Nach einem Rolling-Deploy sollen neue Pods typischerweise erst prüfen, ob sie den Metadaten-Cache erreichen und ob localPath beschreibbar ist, bevor sie Traffic annehmen. Der Warmup lässt sich explizit triggern:
./vendor/bin/typo3 clusterfilebackend:warmup \
--namespace=cfb:prod:website-a:pages \
--namespace=cfb:prod:website-a:pagesection \
--namespace=cfb:prod:website-a:rootline
Das Kommando emittiert eine JSON-Zeile pro Namespace und beendet sich mit Exit-Code ≠ 0, wenn irgendein Namespace die Health-Checks nicht besteht. Damit lässt es sich in Readiness-/Startup-Probes oder Post-Deploy-Jobs einbinden.
Alternativ den TYPO3-Standard-Warmup nutzen — der Event-Listener hängt sich automatisch ein:
./vendor/bin/typo3 cache:warmup
Garbage Collection als CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: clusterfilebackend-gc-pages
spec:
schedule: "*/15 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: typo3-cli
args: ["clusterfilebackend:gc", "--namespace=cfb:prod:website-a:pages"]
Architektur intern
DDD-4-Layer (Domain → Application → Infrastructure → Presentation), enforced via deptrac. Die einzige Außenschnittstelle für „zentrale Wahrheit" ist der MetadataCachePort, implementiert vom Typo3MetadataCache-Adapter, der jeden beliebigen TYPO3-FrontendInterface annimmt.
Cluster-Konsistenz — was passiert beim Cache-Clear?
Häufige Frage: „Wenn ein Redakteur im TYPO3-Backend auf Alle Caches löschen klickt, woher wissen alle Pods davon?"
Kurze Antwort: Der Pod, der den Klick verarbeitet, löscht den zentralen Metadaten-Cache. Alle anderen Pods sehen das beim nächsten get(), weil sie den zentralen Metadaten-Cache abfragen und nicht ihr lokales Filesystem. Kein Pod-zu-Pod-Sync nötig, weil die Metadaten-Wahrheit nie auf einem Pod liegt.
Detailliert
Pod A: TYPO3-Backend „Alle Caches löschen" / Editor speichert Page /
`bin/typo3 cache:flush`
│
▼
ClusterFileBackend::flush() auf Pod A
│
▼ delegiert an Metadaten-Cache-Frontend (z. B. cluster_meta)
$metadataCache->flush()
│
▼ TYPO3-Cache-API ruft das konfigurierte Backend
KeyValueBackend / DatabaseBackend / MemcachedBackend → flush()
│
▼ passiert SERVER-SEITIG (Redis FLUSHDB, SQL TRUNCATE, Memcached flush_all)
Alle Pods sehen die leeren Metadaten sofort
Beim nächsten get(id) auf irgendeinem Pod:
$metadata = $this->metadataCache->get($identifier); // → null (Cache geflushed)
if ($metadata === null) {
// cache_miss_total{reason=no-metadata}++
return null; // ← Pod konsultiert sein lokales FS gar nicht erst
}
Test-Verifikation
Tests/Unit/Deployment/CrossPodFlushTest.php enthält fünf Tests, die das belegen: flush() propagiert ohne Sync sofort zu Pod B; flushByTag() invalidiert nur passende Einträge; lokale Files überleben den Flush als harmlose Waisen; Re-Write nach Flush stellt Konsistenz wieder her; Flush funktioniert für beliebige Pod-Anzahlen (keine Skalierungs-Annahme).
Komplexität — warum es im Cluster schneller ist
Sei C die Menge aller Cache-Einträge und Ct ⊆ C die Teilmenge der Einträge mit Tag t. Wir schreiben n := |C| für die Gesamtanzahl und m := |Ct| für die Anzahl der mit t getaggten Einträge — es gilt m ≤ n. Damit lässt sich der Unterschied sauber benennen: ClusterFileBackend liegt nicht nur bei kleinerem Argument, sondern in einer anderen Komplexitätsklasse, weil es Backend-native Algorithmen nutzt und keinen Pod-Faktor multipliziert.
Sei zusätzlich P die Anzahl der Pods und e ≤ n die Anzahl der TTL-abgelaufenen Einträge.
| Operation | TYPO3-Core-FileBackend | ClusterFileBackend | Speedup |
|---|---|---|---|
flushByTag | Θ(n) pro Pod — DirectoryIterator über jede Cache-Datei, 2× file_get_contents pro Datei | Θ(m) — Backend liest Tag-Index direkt | Andere Komplexitätsklasse plus Tag-Indizes |
findIdentifiersByTag | Θ(n) pro Pod | O(m) | dito |
collectGarbage | Θ(n) pro Pod, gesamt Θ(n · P) | O(1) aktiv (Redis TTL Auto-Expire) oder O(e) server-seitig (DB) | Backend-native plus Cluster-once |
flush | Θ(n) pro Pod, gesamt Θ(n · P) | Θ(n) einmal server-seitig | Pod-Faktor entfällt, Konstanten ~100–1000× kleiner |
Konkretes Beispiel
n = 10 000 Cache-Einträge, davon m = 100 mit Tag site_1, P = 5 Pods.
| Setup | File-Reads | unlink-Calls | Round-Trips |
|---|---|---|---|
Core-FileBackend bei flushByTag('site_1') | 2 · n = 20 000 | m = 100 | ≈ 2 n + m = 20 100 lokale FS-I/O pro Pod |
| ClusterFileBackend (Redis) | 0 | 0 | 2 (SMEMBERS + Pipeline DEL) einmal cluster-weit |
Rolling-Deploys mit Version-Skew
Während eines Rolling-Deploys liefern alte und neue Pods gleichzeitig Traffic aus. ClusterFileBackend bewahrt in jedem Skew-Szenario die Korrektheit, aber zwei Fälle ändern das Performance-Profil während des Deploy-Fensters — die sollte man verstehen.
A) Anwendungs-Code mit geändertem Cache-Layout
Wenn das neue Image für denselben Cache-Identifier eine andere Payload-Struktur schreibt (zusätzliche Felder, geänderte serialisierte Klassen, anders aufgebaute Value-Objects) und Sie nicht explizit invalidieren, passiert Folgendes:
- Pod-alt schreibt Payload v1 → Metadaten enthalten hashv1.
- Pod-neu liest, sieht hashv1, hat lokal keinen Blob → Blob-Miss → TYPO3-Frontend ruft den Rebuild des Callers → Pod-neu schreibt Payload v2 → Metadaten werden mit hashv2 überschrieben.
- Pod-alt liest, sieht hashv2, hat lokal keinen Blob → Blob-Miss → baut v1 neu → Metadaten zurück auf hashv1.
- Hash-Thrashing für die Dauer des Rolling-Deploys.
Das größere Risiko ist stiller Layout-Drift: kann Pod-neu die Bytes von Pod-alt zwar technisch deserialisieren, das resultierende Objekt ist aber falsch (fehlende Felder, alte Enum-Cases, entfernte Properties), sieht der User stale oder korrupten Content. PHPs unserialize verifiziert die Klassen-Shape jenseits des Klassennamens nicht.
Empfehlung: Cache-Identität an den Deploy koppeln
Damit jedes Release automatisch eine neue BackendVersion bekommt und stale Einträge unerreichbar werden, liest ClusterFileBackend eine Environment-Variable — per Default IMAGE_TAG — und faltet ihren Wert via crc32 in den Payload-Hash. Im Deployment-Manifest:
# Helm-Values, Kustomize-Patch oder plain Pod-Spec
env:
- name: IMAGE_TAG
value: "{{ .Values.image.tag }}" # oder $CI_COMMIT_SHA, Release-Semver, ...
Pro Cache lässt sich der Variablenname überschreiben, falls die CI-Konvention anders heißt:
'options' => [
'localPath' => '/app/var/cache/cluster/pages',
'metadataCacheIdentifier' => 'cluster_meta',
'namespace' => ['environment' => 'prod', 'instance' => 'site'],
'backendVersionEnvVar' => 'CI_COMMIT_SHA',
],
Ist die Variable unset oder leer, fällt das Backend auf die package-interne BackendVersion::current() zurück — sicher für lokale Entwicklung, in Production sollten Sie die Variable aber explizit verdrahten, um deploy-scoped Invalidierung zu bekommen.
Alternative Invalidierungs-Strategien
- Pre-Flush via
clusterfilebackend:warmupin der Deploy-Pipeline — dränt stale Einträge ab, bevor das neue Image Traffic annimmt. - Cache-Identifier umbenennen (z. B.
pages→pages_v2in dencacheConfigurations). Schwerer Hammer, nur für größere Schema-Umbauten.
Bei nicht-brechenden Layout-Änderungen (additiv, vom alten Code ignoriert) kann man das temporäre Thrashing akzeptieren — die Korrektheit bleibt erhalten.
B) PHP-Major/Minor-Version-Wechsel
Der Identity-Hash enthält PHP_MAJOR.PHP_MINOR (Classes/Application/Hash/ComputePayloadHash.php). PHP 8.4 ↔ 8.5 (oder jeder andere Major/Minor-Sprung) erzeugt automatisch divergente Hashes — keine manuelle Aktion nötig. Korrektheit garantiert. Die Kosten sind dasselbe Thrashing wie in (A) für die Dauer des Rollouts. blob_miss_total in Prometheus beobachten; ein anhaltender Spike über das Deploy-Fenster hinaus deutet darauf hin, dass die Version-Skew nicht konvergiert (z. B. ein Pod im alten Image hängen geblieben).
PHP-Patch-Updates (8.5.4 → 8.5.5) invalidieren nicht — nur Major und Minor sind im Hash.
Operative Empfehlung
- Patch-Updates (igbinary-Patch, PHP-Patch, App-Bugfix ohne Cache-Layout-Wechsel): normaler Rolling-Deploy, keine Extra-Schritte.
- Minor- und Major-Updates (PHP-Minor-Bump, BackendVersion-Bump, Cache-Layout-Änderung): Rolling-Deploy bleibt sicher, aber mit erwartetem Blob-Miss-Spike. Für Zero-Degradation-Deploys eine
Recreate-Strategie oder einen Pre-Flush via Warmup-Kommando fahren.
Operative Anforderungen
Pod-Uhrsynchronisation
Cache-Lifetimes werden gegen die lokale Uhr jedes Pods ausgewertet. Weichen Pods in der Wanduhrzeit um mehr als ein paar Sekunden ab, behandelt ein Pod mit vorausgehender Uhr Einträge als abgelaufen, bevor Peers das tun — Korrektheit bleibt erhalten, Performance leidet. In Kubernetes ist das normalerweise kein Problem (Nodes synchronisieren per chrony/systemd-timesyncd gegen Cluster-NTP). Sanity-Check bei Incidents:
kubectl exec deploy/typo3 -- date -u
Ein Skew >30 Sekunden über Pods hinweg ist die Schwelle, ab der blob_miss_total und cache_miss_total{reason=expired} in Prometheus sichtbar auseinanderdriften.
Metadaten-Cache-Verfügbarkeit
Der Metadaten-Cache (Redis/Valkey/DB) ist die einzige Source of Truth. Ist er nicht erreichbar:
- Reads degradieren graceful zu Cache-Misses — das TYPO3-Frontend triggert Caller-Rebuilds. Die Applikation bleibt verfügbar, aber Upstream-Last (DB-Queries, Render-Zeit) steigt.
- Writes surfacen die zugrundeliegende Exception — das ist Absicht, um Ausfälle nicht zu verschweigen.
Kompatible Metadaten-Cache-Backends
Der Metadaten-Cache muss ein TaggableBackendInterface-Backend nutzen, sonst wird flushByTag zur No-Op. Geprüfte Backends:
| Backend | Taggable | Hinweis |
|---|---|---|
Typo3DatabaseBackend | ✅ | Zero-Dependency-Default |
KeyValueBackend (moselwal/keyvalue-store) | ✅ | Redis/Valkey, empfohlen für hohen Traffic |
MemcachedBackend (TYPO3 Core) | ❌ | Unterstützt keine Tags — inkompatibel |
RedisBackend (TYPO3 Core) | ❌ | Nicht taggable — stattdessen moselwal/keyvalue-store verwenden |
IMAGE_TAG-Konsistenz über alle Pods
Jeder Container, der auf denselben Metadaten-Cache zeigt, muss denselben IMAGE_TAG (oder die konfigurierte backendVersionEnvVar) sehen. Läuft der Web-Pod mit IMAGE_TAG=1.2.3 und ein Worker/Cron-Pod noch mit IMAGE_TAG=1.2.2, berechnen beide unterschiedliche BackendVersion-Werte und behandeln gegenseitige Writes als Blob-Misses. Symptom: persistentes Thrashing in gemischten Deployments. Helm/Kustomize-Tipp: Tag in einen einzigen Value extrahieren und von jeder Pod-Spec aus referenzieren.
Y2K38-Einschränkung für Unlimited-Lifetime-Einträge
Lifetime::unlimited() mappt auf expiresAt = 2147483647 (spiegelt TYPO3-Cores Typo3DatabaseBackend::FAKED_UNLIMITED_EXPIRE). Am 2038-01-19 03:14:07 UTC wird dieser Timestamp zu „jetzt“ und als unbegrenzt gespeicherte Einträge gelten als abgelaufen. Praktische Auswirkung bis dahin: keine.
crc32-basierte BackendVersion-Faltung
BackendVersion::fromString(…) faltet den Deploy-Identifier via crc32 auf ein 32-Bit-Integer. Geburtstags-Kollisionen treten bei ca. 77 000 eindeutigen Deploy-Identifiern auf — bei realistischer Release-Kadenz (einige Deploys pro Tag über die Laufzeit eines Projekts) bleibt die Kollisionswahrscheinlichkeit unter 1:10⁵. Wer regelmäßig Tausende unterschiedliche Identifier durchläuft, sollte auf einen stabilen, menschenlesbaren Semver-String statt rohen Commit-SHAs setzen.
Häufige Fallstricke
localPathmuss schreibbar sein. Bei read-only/app-Image einemptyDirodertmpfsan diesem Pfad mounten.- Identisches Container-Image für alle Pods. Unterschiedliche PHP- oder igbinary-Versionen führen zu abweichenden Hashes → permanente Blob-Misses. Major-Versionen reichen — Patch-Versionen sind seit v1.0.1 nicht mehr Teil des Hashes.
IMAGE_TAG(oder Ihr Äquivalent) in Production verdrahten. Ohne diese Variable nutzt das Backend eine package-interne Versions-Konstante, die sich über Deploys NICHT ändert — brechende Cache-Layout-Änderungen können dann stillschweigend stale oder korrupten Content ausliefern. Siehe „Rolling-Deploys mit Version-Skew".metadataCacheIdentifiermuss vor jedem Cache registriert sein, derClusterFileBackendnutzt. TYPO3 lädtcacheConfigurationsin Array-Insertion-Order — alsocluster_metazuerst definieren.- Nur Composer-Mode. Kein
ext_emconf.php, kein Classic-Mode.
TYPO3 unter Kubernetes betreiben?
Wenn Sie TYPO3 in einem K8s-Cluster ohne RWX-Volume betreiben oder ein bestehendes FileBackend-Setup für Multi-Pod auslegen, hilft cluster-file-backend. Sprechen Sie uns für Architektur-Beratung, Migration oder Plattform-Setup an.
Oder direkt schreiben: kontakt@moselwal.de
Setzen wir ein bei …
Dieses Paket übernimmt die dateibasierte Cache-Schicht in TYPO3 Kubernetes — eine der Voraussetzungen für Multi-Pod-Cluster, die unter Open Source & Digitale Souveränität beschrieben sind. In der betreuten Variante: AI-Ready CMS as a Service.
