Shopware 6 & Redis: Wenn falsche Konfiguration mehr schadet als nutzt

Redis ist in Shopware 6 das Mittel der Wahl, um Cache, Sessions und HTTP-Cache aus der Datenbank bzw. dem Dateisystem zu holen und die Performance drastisch zu steigern. In der Praxis sehen wir aber regelmäßig Setups, in denen Redis zum Flaschenhals wird — nicht, weil Redis langsam ist, sondern weil es falsch konfiguriert wurde.

Dieser Artikel zeigt die häufigsten Fehler und wie man sie vermeidet.

Mit dem Klick stimmst du zu, dass YouTube (Google) Daten lädt. Der erweiterte Datenschutzmodus ist aktiv.


Fehler 1: Eine Redis-Instanz für alles

Das ist der Klassiker. Eine einzige Redis-Instanz auf Port 6379 wird für Cache, Sessions, HTTP-Cache, Cart-Persistenz, Number-Ranges, Locks, Increment-Storage und Message-Queue verwendet.

Warum das schadet:

  • Cache-Daten konkurrieren mit Session-Daten um den verfügbaren Speicher. Wenn maxmemory greift und die Eviction-Policy Keys räumt, können das Session- oder Warenkorb-Keys sein — Kunden fliegen aus dem Checkout oder verlieren ihren Warenkorb.
  • Ein unbedachtes FLUSHALL im Debugging löscht alles auf einen Schlag — Sessions, Warenkörbe, Number-Range-States.
  • Unterschiedliche Datentypen brauchen unterschiedliche Persistenz- und Eviction-Strategien. Cache-Daten sind flüchtig, Warenkörbe und Number-Ranges sind es nicht.

Lösung: Mindestens zwei getrennte Redis-Instanzen — ephemeral und persistent.

Seit Shopware 6.6.8.0 werden benannte Redis-Connections direkt in der shopware.yaml unterstützt:

# config/packages/shopware.yaml
shopware:
    redis:
        connections:
            ephemeral:
                dsn: '%env(REDIS_EPHEMERAL)%'
            persistent:
                dsn: '%env(REDIS_PERSISTENT)%'
REDIS_EPHEMERAL=redis://redis-cache:6379
REDIS_PERSISTENT=redis://redis-session:6380

Diese Connection-Namen können dann in allen Subsystemen referenziert werden — Cache, Sessions, Cart, Number-Ranges, Locks, Increment-Storage.

InstanzZweckPersistenzEviction-Policy
ephemeralApp-Cache, HTTP-Cache, Locks, IncrementAusallkeys-lru
persistentSessions, Warenkörbe, Number-RangesAn (RDB + AOF)volatile-lru

Docker-Compose-Auszug:

services:
    redis-cache:
        image: redis:7-alpine
        command: >
            redis-server
              --maxmemory 2gb
              --maxmemory-policy allkeys-lru
              --save ""
              --appendonly no
        volumes: [] # Kein Volume — Cache ist flüchtig

    redis-session:
        image: redis:7-alpine
        command: >
            redis-server
              --maxmemory 256mb
              --maxmemory-policy volatile-lru
              --appendonly yes
              --appendfsync everysec
              --save "3600 1" --save "300 100"
        volumes:
            - redis-session-data:/data

Die Shopware-Docs empfehlen volatile-lru für die persistente Instanz — damit werden nur Keys mit gesetztem TTL evictet, während Keys ohne Ablauf (z. B. Number-Ranges) immer erhalten bleiben. Das ist der entscheidende Unterschied zu allkeys-lru, das auch Keys ohne TTL löschen würde.


Fehler 2: Persistenz auf der Cache-Instanz aktiviert

Standardmäßig startet Redis mit aktivierten RDB-Snapshots (save 3600 1 300 100 60 10000). Das bedeutet: Redis schreibt regelmäßig den gesamten Datensatz auf die Festplatte, auch wenn es sich nur um flüchtigen Cache handelt.

Warum das schadet:

  • Der BGSAVE-Prozess forkt den Redis-Prozess. Bei 2 GB Cache-Daten werden kurzzeitig 2 GB zusätzlicher RAM durch Copy-on-Write belegt. Auf einer VM mit 4 GB RAM kann das zum OOM-Kill führen.
  • Disk-I/O durch den Dump belastet das System unnötig, besonders auf Shared-Hosting oder kleinen VPS mit langsamen Platten.
  • Beim Neustart lädt Redis den Dump und stellt veraltete Cache-Daten wieder her. Das klingt erstmal gut, ist aber kontraproduktiv: Der Cache enthält nach einem Deployment möglicherweise veraltete Einträge, die Shopware dann ausliefert, statt sie neu zu generieren.

Lösung:

# redis-cache.conf (ephemeral)
save ""
appendonly no

Zwei Zeilen. Kein RDB, kein AOF. Die Cache-Instanz startet leer — genau so soll es sein. Shopware baut den Cache bei Bedarf neu auf.

Für die persistente Instanz (Sessions, Carts, Number-Ranges) ist Persistenz dagegen zwingend:

# redis-session.conf (persistent)
appendonly yes
appendfsync everysec
save 3600 1
save 300 100

Fehler 3: Kein maxmemory gesetzt

Ohne maxmemory-Limit wächst Redis so lange, bis der Arbeitsspeicher des Hosts voll ist. Der Linux-OOM-Killer beendet dann den Redis-Prozess — oder schlimmer: den MySQL-Prozess, weil der mehr RAM belegt.

Warum das schadet:

  • Unkontrollierter Speicherverbrauch führt zu Systeminstabilität.
  • Swap-Nutzung macht Redis um Größenordnungen langsamer. Redis und Swap vertragen sich nicht — eine Redis-Instanz, die swappt, ist langsamer als ein Cache, der direkt auf MariaDB liegt.

Lösung:

Faustregel für einen typischen Shopware-Shop:

  • Cache-Instanz (ephemeral): 1–2 GB (je nach Produktanzahl und Kategorientiefe)
  • Persistent-Instanz: 128–256 MB (abhängig von gleichzeitigen Nutzern und Warenkörben)

Immer explizit setzen:

maxmemory 2gb
maxmemory-policy allkeys-lru  # für ephemeral

Und überwachen. Ein INFO memory-Check gehört ins Monitoring:

redis-cli -p 6379 INFO memory | grep used_memory_human
redis-cli -p 6379 INFO memory | grep maxmemory_human

Fehler 4: Falsche Eviction-Policy

Redis bietet verschiedene Eviction-Policies. Die Wahl hat direkten Einfluss auf das Verhalten des Shops.

Häufige Fehlkonfiguration:

  • noeviction auf der Cache-Instanz → Redis lehnt Schreiboperationen ab, sobald maxmemory erreicht ist. Shopware wirft Fehler, der Shop steht.
  • allkeys-random statt allkeys-lru → Redis löscht zufällige Keys statt des am längsten nicht genutzten. Häufig angefragte Cache-Einträge werden gelöscht, selten gebrauchte bleiben.
  • allkeys-lru auf der persistenten Instanz → Redis kann Number-Range-Keys löschen, die kein TTL haben. Das führt zu doppelt vergebenen Bestellnummern.

Die richtige Zuordnung (laut Shopware-Docs):

InstanzPolicyWarum
Ephemeral (Cache)allkeys-lruÄlteste Einträge fliegen zuerst, neue werden immer geschrieben
Persistent (Sessions, Carts, Number-Ranges)volatile-lruNur Keys mit TTL werden evictet; Keys ohne TTL (Number-Ranges) bleiben erhalten

Der Unterschied ist subtil, aber kritisch: volatile-lru schützt alle Keys ohne explizites TTL vor dem Löschen. Sessions und Warenkörbe haben TTLs und können bei Speicherdruck evictet werden. Number-Ranges haben kein TTL und bleiben sicher.


Fehler 5: Shopware-seitige Konfiguration unvollständig

Redis läuft, die Verbindung steht — aber Shopware nutzt es nicht für alle Subsysteme, weil die Konfiguration nur teilweise gemacht wurde.

Typische Fehler:

  • Nur den App-Cache auf Redis, aber Session-Handler, Cart-Storage, Locks und Increment vergessen.
  • Lock-DSN zeigt noch auf flock statt Redis → bei Horizontal-Scaling knallt es.
  • Increment-Storage bleibt auf MySQL → unnötige DB-Last durch Locking-Queries.

Vollständige Konfiguration mit benannten Connections (≥ 6.6.8.0):

# config/packages/shopware.yaml
shopware:
    redis:
        connections:
            ephemeral:
                dsn: '%env(REDIS_EPHEMERAL)%'
            persistent:
                dsn: '%env(REDIS_PERSISTENT)%'

    cache:
        invalidation:
            delay: 0
            count: 150
            delay_options:
                storage: redis
                connection: 'ephemeral'

    cart:
        redis_url: '%env(REDIS_PERSISTENT)%'

    number_range:
        redis_url: '%env(REDIS_PERSISTENT)%'

    increment:
        message_queue:
            type: redis
            config:
                url: '%env(REDIS_EPHEMERAL)%'
        user_activity:
            type: redis
            config:
                url: '%env(REDIS_EPHEMERAL)%'
# config/packages/prod/framework.yaml
framework:
    cache:
        default_redis_provider: '%env(REDIS_EPHEMERAL)%'
        pools:
            cache.object:
                adapter: cache.adapter.redis
                tags: true
            cache.http:
                adapter: cache.adapter.redis
                tags: true

    session:
        handler_id: '%env(REDIS_PERSISTENT)%'

    lock:
        main: '%env(REDIS_EPHEMERAL)%'

Wer den Increment-Storage nicht braucht (keine Nutzung der Admin-Live-Statistiken), kann ihn auf array setzen und spart sich die Last komplett:

shopware:
    increment:
        message_queue:
            type: array
        user_activity:
            type: array

Fehler 6: ?persistent=1 falsch verstanden

Ein Klassiker, den man in vielen Tutorials sieht:

shopware:
    cart:
        redis_url: 'redis://redis:6380?persistent=1'

Der Parameter ?persistent=1 hat nichts mit der Datenpersistenz zu tun. Er aktiviert Persistent Connections — also Connection Pooling auf PHP-Seite. Damit wird die TCP-Verbindung zu Redis über mehrere Requests hinweg offen gehalten, statt sie bei jedem Request neu aufzubauen.

Warum die Verwechslung schadet:

  • Entwickler setzen ?persistent=1 und glauben, damit sei die Datenpersistenz geregelt. Die eigentliche Redis-Persistenz (RDB/AOF) wird nicht konfiguriert. Nach einem Redis-Neustart sind alle Warenkörbe weg.
  • Umgekehrt: ?persistent=1 wird weggelassen, weil man keine Persistenz will (Cache-Instanz). Dabei hätte Connection Pooling dort die beste Wirkung, weil Cache-Requests häufig sind.

Lösung:

  • Datenpersistenz wird auf Redis-Server-Seite konfiguriert (save, appendonly)
  • Connection Pooling wird in der DSN konfiguriert (?persistent=1) — sinnvoll für beide Instanzen

Fehler 7: Cart-Migration vergessen

Shopware speichert Warenkörbe standardmäßig in MySQL. Der Umstieg auf Redis erfordert eine explizite Migration, sonst sind bestehende Warenkörbe nach dem Config-Wechsel weg.

# Warenkörbe von MySQL zu Redis migrieren
bin/console cart:migrate

Das Kommando liest die Warenkörbe aus der Datenbank und schreibt sie in die konfigurierte Redis-Instanz. Es nutzt automatisch die redis_url aus der shopware.yaml. Der Befehl ist idempotent und kann im Deployment-Script ausgeführt werden.

Ohne diesen Schritt verlieren alle Kunden mit aktiven Warenkörben ihre Artikel nach dem Switch.


Fehler 8: Kein Monitoring

Redis läuft still vor sich hin. Niemand merkt, dass die Hit-Rate bei 12 % liegt, der Speicher zu 98 % voll ist oder die Latenz bei 50 ms statt 0,2 ms liegt.

Mindest-Monitoring:

# Hit-Rate prüfen
redis-cli -p 6379 INFO stats | grep -E "keyspace_hits|keyspace_misses"

# Auslastung
redis-cli -p 6379 INFO memory | grep used_memory_peak_human

# Verbundene Clients
redis-cli -p 6379 INFO clients | grep connected_clients

# Slow-Log
redis-cli -p 6379 SLOWLOG GET 10

Die Hit-Rate für die Cache-Instanz sollte über 80 % liegen. Liegt sie darunter, stimmt entweder das TTL nicht, die Eviction-Policy ist falsch, oder maxmemory ist zu niedrig und Redis räumt ständig auf.

Für ein sauberes Setup: Prometheus mit dem redis_exporter und ein Grafana-Dashboard. Die Investition von 30 Minuten Einrichtung spart Stunden Debugging.


Fehler 9: TCP-Konfiguration ignoriert

Redis und Shopware laufen auf demselben Host, kommunizieren aber über TCP statt Unix-Socket. Oder Redis läuft auf einem separaten Host, aber TCP-Keepalive und Timeout sind auf Default.

Wenn Redis auf demselben Host läuft:

Unix-Socket statt TCP spart den gesamten TCP-Stack-Overhead. Das sind bei hohem Durchsatz messbare Mikrosekunden pro Request:

# redis-cache.conf
unixsocket /var/run/redis/redis-cache.sock
unixsocketperm 770
port 0
REDIS_EPHEMERAL=redis:///var/run/redis/redis-cache.sock

Wenn Redis remote läuft:

tcp-keepalive 60
timeout 300
tcp-backlog 511

Ohne tcp-keepalive können tote Verbindungen minutenlang offen bleiben und den Connection-Pool blockieren.

Bei Nutzung eines Redis-Clusters zusätzlich in der php.ini setzen:

redis.clusters.cache_slots=1

Das überspringt den Cluster-Node-Lookup bei jeder Verbindung.


Zusammenfassung

FehlerAuswirkungFix
Eine Instanz für allesEviction löscht Sessions/CartsGetrennte ephemeral/persistent Instanzen
Persistenz auf CacheOOM-Kill, veralteter Cache nach Restartsave "", appendonly no
Kein maxmemoryOOM-Kill, Swap-NutzungExplizites Limit setzen
Falsche Eviction-PolicyShop-Fehler oder Datenverlustallkeys-lru (Cache), volatile-lru (persistent)
Unvollständige Shopware-ConfigRedis läuft, wird aber nicht genutztAlle Subsysteme konfigurieren
?persistent=1 verwechseltKeine Datenpersistenz trotz AnnahmeServer-seitige Persistenz konfigurieren
Cart-Migration vergessenWarenkörbe weg nach Redis-Umstiegbin/console cart:migrate
Kein MonitoringProbleme bleiben unentdecktHit-Rate, Memory, Slow-Log überwachen
TCP statt SocketUnnötiger OverheadUnix-Socket bei Co-Location

Redis ist ein mächtiges Werkzeug — aber nur, wenn die Konfiguration zum Use-Case passt. Ein falsch konfiguriertes Redis ist schlimmer als kein Redis, weil es eine zusätzliche Fehlerquelle einführt und dabei den Eindruck erweckt, die Performance-Arbeit sei bereits erledigt.