Redis 7.4 in Kubernetes: warum der Rollback auf 7.2 crasht

11.08.2026

Thema: alle Beiträge zu Valkey

Ein Image-Tag im StatefulSet hebt das RDB-Format von 11 auf 12. Danach startet Redis 7.2 nicht mehr, und der Rollback holt nur das Image zurück.

Inhaltsverzeichnis

Eine Zeile im Pull Request

In einem Pull Request steht folgende Zeile: aus redis:7.2.4 wird redis:7.4. Ein Bot hat sie vorgeschlagen, ein Mensch hat durchgewinkt, die Pipeline war grün. Alltag.

In dieser einen Zeile stecken zwei Entscheidungen, die niemand bewusst getroffen hat.

Die erste ist bekannt: Ab 7.4 ist Redis kein Open Source mehr im Sinne der Open Source Initiative. Die zweite kennt fast niemand: Der Rückweg ist zu. Nicht schwierig, nicht teuer. Zu.

Beides passiert im selben Release. Wer nur auf die Lizenz schaut, übersieht, dass gleichzeitig das Datenformat weiterspringt. Wer nur auf die Technik schaut, hält genau diesen Sprung für Routine. Beide liegen falsch, und dieser Beitrag zeigt an gemessenen Ausgaben, warum.

Die Zeitleiste in vier Daten

Wann Was
März 2024 Redis wechselt von BSD-3-Clause auf die Doppellizenz RSAL und SSPL. Keine der beiden ist OSI-anerkannt. Letzte freie Version: 7.2.4
März 2024 Die Linux Foundation kündigt Valkey an, einen Fork an genau dieser Version 7.2.4, weiter unter BSD
Juli 2024 Redis 7.4 erscheint. Neue Hash-Kodierungen für Feld-Ablaufzeiten, RDB-Format 11 wird 12
Mai 2025 Redis 8 bringt AGPLv3 als dritte Option. Diese Lizenz ist anerkannt

Der letzte Punkt ist eine echte Bewegung, und ich rede sie nicht klein. Er beantwortet aber nicht die Frage, um die es hier geht: Wenn Redis wieder offen ist, hat sich der Fork dann erledigt? Die Antwort steht nicht im Lizenztext. Sie liegt auf dem Volume.

Die Ausgangslage

Alles Folgende ist in einem k3d-Cluster nachgemessen, mit einem StatefulSet, einem PersistentVolume unter /data und drei Images, die weiter unten mit Digest stehen.

kubectl apply -f redis-statefulset.yaml
kubectl exec redis-0 -- redis-cli INFO server | grep redis_version
redis_version:7.2.15

Ein bisschen Inhalt, damit es echte Daten sind, dann ein Snapshot:

kubectl exec redis-0 -- redis-cli SET kunde:1 "Trutz GmbH"
kubectl exec redis-0 -- redis-cli BGSAVE
kubectl exec redis-0 -- head -c 9 /data/dump.rdb
REDIS0011

REDIS ist die Magic-Zahl, an der jedes Werkzeug erkennt, was für eine Datei das ist. Dahinter steht 0011, die Formatversion. Merk dir diese Elf.

Und noch etwas zu dieser Datei, weil daran alles hängt: Den Container tauschst du aus, wann du willst. Diese Datei tauschst du nicht aus. Sie bleibt auf dem Volume liegen, egal welches Image du als Nächstes startest. Das Image ist austauschbar, das Volume nicht.

Der Tag-Bump

Jetzt das Upgrade, eine Zeile im Manifest:

kubectl set image statefulset/redis redis=redis:7.4-alpine
kubectl rollout status statefulset/redis
partitioned roll out complete: 1 new pods have been updated...

Rolling Update sauber durch, Pod läuft, Readiness grün, alle Daten da. Aus Sicht von Kubernetes ein unauffälliger Vorgang, und genau das ist das Problem: Nichts im Cluster meldet dir, dass gerade etwas Unumkehrbares passiert ist.

Denn Redis 7.4 schreibt den Snapshot neu. Beim nächsten BGSAVE, spätestens aber, wenn der Pod sauber herunterfährt. In der Standardkonfiguration ist nämlich save gesetzt, und dann löst auch ein SIGTERM ein Speichern aus:

kubectl exec redis-0 -- redis-cli CONFIG GET save
save
3600 1 300 100 60 10000

Nach dem ersten Speichern unter 7.4:

kubectl exec redis-0 -- redis-cli BGSAVE
kubectl exec redis-0 -- head -c 9 /data/dump.rdb
REDIS0012

Neun Bytes, eine Ziffer Unterschied. Elf ist Zwölf geworden. Es gibt keinen Befehl, der das zurückdreht.

Der Rollback crasht

Sagen wir, irgendetwas ist langsamer, irgendetwas verhält sich anders. Du machst, was du sonst auch machst: Du gehst zurück.

kubectl set image statefulset/redis redis=redis:7.2.4-alpine
kubectl get pod redis-0
NAME      READY   STATUS             RESTARTS      AGE
redis-0   0/1     CrashLoopBackOff   2 (11s ago)   24s
kubectl logs redis-0
1:M 28 Jul 2026 17:19:33.423 # Can't handle RDB format version 12
1:M 28 Jul 2026 17:19:33.423 # Fatal error loading the DB, check server logs. Exiting.

Mit kubectl rollout undo geht es genauso schief, denn der eingebaute Mechanismus rollt das falsche Ding zurück. Er holt das Image zurück, nicht die Daten. Bei GitOps ist der Commit zurückgenommen, das Volume nicht. Das ist keine Redis-Eigenheit, sondern die Grundregel bei jedem zustandsbehafteten Dienst: Git beschreibt den gewünschten Zustand, deine Daten besitzt es nicht.

Ein Detail am Rande, das in der Praxis Zeit kostet: Ein Pod im CrashLoopBackOff wird von einem StatefulSet mit podManagementPolicy: OrderedReady nicht automatisch ersetzt. Der Controller wartet darauf, dass der Pod bereit wird, bevor er ihn anfasst. Ein neuer Image-Tag ändert daran nichts, du musst den Pod von Hand löschen. Bei einem Deployment sieht das anders aus, dort legt das neue ReplicaSet einfach einen zusätzlichen Pod an.

Das passiert auch nicht nur im Labor. Es ist Argo CD passiert, also ausgerechnet dem GitOps-Werkzeug: Im Helm-Chart 7.9.0 wurde das mitgelieferte Redis von 7.4.2 auf 7.2.8 zurückgestuft, und die Redis-Pods in HA-Setups sind sofort mit exakt dieser Zeile ausgestiegen (argo-helm 3272). Im Redis-Projekt selbst ist das Verhalten als Issue 14064 dokumentiert.

Was dir dann bleibt, ist unschön: Entweder du löschst die RDB-Datei, dann sind die Daten weg. Oder du gehst wieder auf 7.4 hoch. Einen Konverter gibt es nicht.

Warum das Format bricht

Ohne diesen Teil ist das alles nur eine Anekdote. Eine RDB-Datei ist ein binärer Snapshot des kompletten Datensatzes. Vorne die Magic-Zahl und die Formatversion, dahinter die Daten, Eintrag für Eintrag. Jeder Eintrag beginnt mit einem einzelnen Byte, dem Opcode, und der sagt an, was jetzt folgt.

Der Datensatz von oben, ein einziger String, sind ganze 26 Bytes. Darin steht kein Trennzeichen und kein Inhaltsverzeichnis. Jedes Byte bekommt seine Bedeutung von dem Byte davor:

Bytes Bedeutung
fe 00 wähle Datenbank 0
fb 01 00 ein Schlüssel, keiner mit Ablaufzeit
00 Typ: String
07 Länge des Schlüssels: 7
6b 75 6e 64 65 3a 31 kunde:1
0a Länge des Werts: 10
54 72 75 74 7a 20 47 6d 62 48 Trutz GmbH

Oben die Verwaltung, dann die Zeile, um die es geht: eine einzelne Null. Das ist der Opcode, im Quelltext heißt die Konstante RDB_TYPE_STRING. Mehr ist ein Typ nicht, ein Byte. Darunter die Sieben, die Länge des Schlüssels, dann der Schlüssel selbst, dann die Zehn und die zehn Bytes Trutz GmbH.

Entscheidend ist die Reihenfolge: Die Länge steht hinter dem Typ.

Redis 7.4 hat Ablaufzeiten für einzelne Felder innerhalb eines Hashes eingeführt, HEXPIRE. Nützliches Feature, aber es braucht neue Kodierungen, konkret zwei neue Eintragstypen im RDB (redis 13303). Und wenn neue Opcodes dazukommen, muss die Formatversion hoch. Elf wird Zwölf.

Bleibt die Frage, die sich sofort aufdrängt: Warum verweigert der alte Server die ganze Datei? Er könnte doch überspringen, was er nicht kennt.

Kann er nicht, und die Tabelle oben zeigt warum. Ohne die Null davor ist die Sieben nur irgendein Byte. Erst der Typ sagt dir, dass danach eine Länge kommt. Kennst du den Typ nicht, weißt du auch nicht, wie weit du springen müsstest. Ein Parser, der an dieser Stelle weiterrät, produziert stillen Datenverlust, und der ist schlimmer als ein Absturz, weil ihn niemand bemerkt. Abbrechen ist die richtige Entscheidung.

Damit ist auch klar, was hier eigentlich vorliegt: ein ganz normaler Format-Bump, sauber implementiert. Bemerkenswert ist nur das Timing. Er kommt im selben Release, in dem die Lizenz wechselt. Wer 7.4 nimmt, nimmt beides.

Und wenn ich AOF benutze?

Die naheliegende Rückfrage, und die Antwort ist unangenehmer als erwartet. appendonly yes erzeugt seit Redis 7 kein einzelnes Logfile mehr, sondern ein Verzeichnis mit drei Dateien:

/data/appendonlydir/
  appendonly.aof.2.base.rdb     <- Basis-Datei, im RDB-Format
  appendonly.aof.2.incr.aof     <- seither eingegangene Kommandos, im Klartext
  appendonly.aof.manifest       <- welche Datei welche Rolle hat

Die Endung sagt es schon: Die Basis-Datei ist eine RDB-Datei, weil aof-use-rdb-preamble standardmäßig auf yes steht. Damit hängt auch das AOF an der Formatversion.

Solange kein Rewrite läuft, bleibt die Basis-Datei auf REDIS0011, und der Rückweg auf 7.2.4 funktioniert tatsächlich. Nur läuft ein Rewrite nicht auf Zuruf, sondern von allein, sobald die Inkrement-Datei genug gewachsen ist (auto-aof-rewrite-percentage). Danach steht dort REDIS0012, und der Rückweg ist zu, ohne dass jemand etwas getan hätte.

AOF verschiebt das Problem also, es löst es nicht. Der Unterschied zum Snapshot ist sogar der gefährlichere: Beim RDB weißt du, wann es passiert. Beim AOF testest du den Rollback, er gelingt, und Wochen später ist die Tür zu.

Die Kompatibilitätsmatrix

Jetzt die andere Frage: Wenn der Rückweg über das Image zu ist, wie kommt man dann überhaupt noch weg? Über Replikation. Valkey stammt aus derselben Codebasis und spricht dasselbe Protokoll, deshalb geht der Wechsel ohne Downtime, in vier Schritten mit REPLICAOF.

Der interessante Teil ist, wie lange dieser Weg offen bleibt. Ich habe alle neun Kombinationen gemessen, mit einem einzelnen String als Datensatz:

Master Replica Link Befund
Redis 7.2 Redis 7.2 up OK
Redis 7.2 Redis 7.4 up OK
Redis 7.2 Valkey 9.1.1 up OK
Redis 7.4 Redis 7.2 down Can’t handle RDB format version 12
Redis 7.4 Redis 7.4 up OK
Redis 7.4 Valkey 9.1.1 down Can’t handle RDB format version 12
Valkey 9.1.1 Redis 7.2 up OK
Valkey 9.1.1 Redis 7.4 up OK
Valkey 9.1.1 Valkey 9.1.1 up OK

Eine Replikation ist im Kern nichts anderes als ein Snapshot über die Leitung: Der Master schickt eine RDB-Datei, die Replica lädt sie. Kann sie das Format nicht lesen, gibt es keinen Sync. Dieselbe Grenze wie auf der Platte, nur an einer anderen Stelle sichtbar.

Stell die beiden Richtungen nebeneinander. Redis 7.4 schickt einen Snapshot, den Valkey nicht lesen kann. Valkey schickt einen, den Redis lesen kann, sogar 7.4. Der Fork hat die Kompatibilität behalten, das Original hat sie aufgegeben.

Warum Valkey das kann, steht in der Versionsangabe:

kubectl exec valkey-0 -- valkey-cli INFO server | grep -E "server_name|version"
server_name:valkey
valkey_version:9.1.1
redis_version:7.2.4

Valkey ist längst bei 9.1.1 und meldet trotzdem bis heute redis_version:7.2.4, den Fork-Punkt von damals. Das ist keine Nostalgie, sondern die Kompatibilitätsangabe, an der sich Clients und eben auch die Replikation orientieren.

Der leise Fehler

Die Rückrichtung hat eine Bedingung, und die musst du kennen: Es darf nichts im Datensatz liegen, das nur Valkey kennt.

Ich habe auf dem Valkey-Master einen Hash mit Feld-Ablaufzeiten angelegt, ein Feature von Valkey 9:

kubectl exec valkey-0 -- valkey-cli HSET sess:1 token abc user chris
kubectl exec valkey-0 -- valkey-cli HEXPIRE sess:1 3600 FIELDS 1 token

Danach scheitert derselbe Sync, gegen beide Redis-Versionen. Hash gelöscht, Sync läuft wieder. Gleiche Instanzen, gleiche Versionen, nur andere Daten. Es liegt übrigens nicht am fehlenden Befehl: Redis 7.4 kennt HTTL durchaus und scheitert trotzdem, es geht um die Kodierung im Replikationsstrom.

Unangenehm ist vor allem, wie es scheitert:

1:S 28 Jul 2026 20:24:11.291 # I/O error reading bulk count from MASTER: No error information
1:S 28 Jul 2026 20:24:11.291 * Reconnecting to MASTER valkey-0.valkey:6379 after failure

Kein Wort über Format, kein Wort über Kompatibilität, nur eine Reconnect-Schleife, die ewig weiterläuft. Der Master-Log sieht dabei nach ganz normalem Betrieb aus. Es gibt also zwei Fehlerbilder, und das gefährlichere ist das leise:

  laut leise
Auslöser Formatgrenze, RDB v12 Feature-Drift im Datensatz
Abhängig von nur den Versionen den tatsächlichen Daten
Meldung Can't handle RDB format version 12 I/O-Fehler, Reconnect-Schleife
Diagnose sofort Suche

Die Regel dazu ist einfach: Wer sich den Rückweg offenhalten will, benutzt keine forkeigenen Datentypen.

Was bringt Valkey dann überhaupt?

An dieser Stelle kommt berechtigt die Rückfrage: Wenn ich auf die neuen Sachen sowieso verzichten muss, was habe ich dann davon?

Stell die beiden Seiten nebeneinander. Redis 7.4 hat den Rückweg zugemacht, ohne zu fragen. Ein Tag-Bump, ein BGSAVE, fertig, gemerkt hast du es hinterher. Bei Valkey bleibt er offen, bis du ihn selbst zumachst, mit einem bestimmten Befehl, an einem Tag, den du aussuchst, für ein Feature, das du wirklich wolltest.

Der Grund für Valkey sind also nicht die neuen Funktionen. Der Grund ist, dass der Stand von 2024 unter BSD weiterlebt und dass die Tür in deiner Hand bleibt. Was du an Features dazunimmst, bezahlst du mit dieser Tür. Solange du nichts dazunimmst, kostet dich das Offenhalten nichts.

Und weil das in beide Richtungen gilt: Redis 8 unter AGPLv3 ist wieder anerkannt offen, das ist echt. Aber AGPL ist nicht BSD, und am getrennten Datenformat ändert die Lizenz nichts.

Drei Konsequenzen für den Betrieb

Erstens: bei Datenbanken keine gleitenden Tags. Der Tag ist eine Bitte, der Digest ist die Tatsache. Genau deshalb stehen hier alle drei Images mit Digest:

redis:7.2-alpine            7.2.15   REDIS0011
  docker.io/library/redis@sha256:05a97a479bc73de66f087dc05b569010772880f778cc8671fa6b8aadee32e5c6
redis:7.4-alpine            7.4.10   REDIS0012
  docker.io/library/redis@sha256:e7723ff73d963f5cc6d9c4643ea3d989527a402a319239054e9472a7fb9219a2
valkey/valkey:9.1.1-alpine  9.1.1    VALKEY080
  docker.io/valkey/valkey@sha256:ee91f7a174ac4d6a6b0685b3a60e321f0a9dbbb691f9b0e285be2ba1d1be8328

Nebenbei: Valkey schreibt eine eigene Magic-Zahl, VALKEY080. Der Fork ist an dieser Stelle also ehrlicher als das Original, das seine Version stillschweigend weiterdreht.

Zweitens: Datenbank-Images raus aus der automatischen Aktualisierung. Ein Renovate- oder Dependabot-Vorschlag für einen zustandslosen Dienst ist harmlos. Für einen Dienst mit Volume ist er eine Migrationsentscheidung, die niemand als solche gelesen hat.

Drittens: vor jedem großen Sprung ein Snapshot des Volumes. Nicht als Ritual, sondern weil er hier das einzige Mittel ist, das den Rückweg tatsächlich offenhält. Ein VolumeSnapshot kostet Sekunden, das Nachbauen eines Datenbestands kostet ein Wochenende.

Die zwei Säulen

Diese eine Zeile vom Anfang ist eine Weiche. Ob du sie stellen kannst, hängt an zwei Dingen, die einzeln beide nichts nützen.

Die offene Lizenz hält die Option überhaupt erst offen. Ohne den Fork unter der Linux Foundation gäbe es kein Valkey, zu dem du wechseln könntest. Es ist dieselbe Frage wie bei Terraform und OpenTofu: nicht nur, welche Lizenz gilt, sondern wer sie jederzeit ändern kann.

Das technische Verständnis macht die Option nutzbar, solange sie offen ist. Also zu wissen, dass auf dem Volume eine Datei liegt, die jeden Image-Wechsel überlebt. Dass ihre ersten neun Bytes darüber entscheiden, welche Version sie noch öffnet. Dass ein Rollback das Image zurückholt und nicht die Daten.

Die Lizenz ohne das Wissen ist ein Recht, das du nicht einlöst, weil du das Zeitfenster verpasst. Das Wissen ohne die Lizenz ist Kompetenz an der Leine, weil es nichts gibt, wohin du wechseln könntest.

Fazit

  1. Redis 7.4 hebt das RDB-Format von 11 auf 12. Der Sprung passiert beim ersten Speichern, und in der Standardkonfiguration reicht dafür ein sauberes Herunterfahren des Pods.
  2. Danach startet Redis 7.2 auf demselben Volume nicht mehr. kubectl rollout undo hilft nicht, weil es das Image zurückholt und nicht die Daten.
  3. Das Format bricht nicht aus Nachlässigkeit: Ein RDB-Eintrag nennt erst den Typ, dann die Länge. Wer den Typ nicht kennt, kann nicht überspringen, und Weiterraten wäre stiller Datenverlust.
  4. AOF rettet dich nicht, es verzögert nur. Die Basis-Datei ist selbst eine RDB-Datei, und der Rewrite läuft automatisch.
  5. Von neun gemessenen Replikations-Kombinationen scheitern genau die beiden, in denen Redis 7.4 der Master ist. Valkey repliziert in beide Redis-Versionen zurück, solange keine forkeigenen Datentypen im Datensatz liegen.
  6. Praktisch: Digest statt Tag, Datenbank-Images raus aus der automatischen Aktualisierung, Volume-Snapshot vor jedem großen Sprung.

Der Satz, der davon bleibt, gilt nicht nur für Datenbanken, sondern für jedes Werkzeug, bei dem du dir eine Wahl offenhalten willst:

Entschieden wird das nicht bei der Migration. Entschieden wird es beim ersten Befehl, den es auf der anderen Seite nicht gibt.

Die Zeitleiste, die vollständige Matrix und den Cutover-Ablauf habe ich als Spickzettel zum Ausdrucken zusammengefasst, fünf Seiten, auch fürs Team. Wenn du solche Beiträge regelmäßig willst, trag dich in den Newsletter ein.

© 2026 Trutz Software Consulting GmbH. CC BY 4.0: Dieser Text darf mit Namensnennung weiterverwendet und bearbeitet werden, auch kommerziell. Code-Beispiele darin stehen unter Apache 2.0. Ausgenommen sind Marke und Logo sowie gekennzeichnete Zitate Dritter. Was das genau heißt.

Newsletter

Erhalte praxisnahe Tutorials, Best Practices und echte Lösungen für die häufigsten DevOps-Probleme direkt in Dein Postfach.

Datenschutz: Deine E-Mail-Adresse wird DSGVO-konform in Deutschland gespeichert. Keine Spam, nur relevante Inhalte. Eine E-Mail alle 1 bis 2 Wochen.