---
title: "Shopware 6 &amp; Redis: Wenn falsche Konfiguration mehr schadet als nutzt - Kickbyte"
canonical_url: "https://kickbyte.de/blog/shopware-6-redis-fehlkonfiguration"
last_updated: "2026-08-21T17:54:06.720Z"
meta:
  description: "Häufige Redis-Fehlkonfigurationen in Shopware 6, die Performance kosten statt sie zu verbessern – von Persistenz auf Cache-Instanzen bis fehlenden Eviction-Policies – und wie man sie behebt."
  "og:description": "Häufige Redis-Fehlkonfigurationen in Shopware 6, die Performance kosten statt sie zu verbessern – von Persistenz auf Cache-Instanzen bis fehlenden Eviction-Policies – und wie man sie behebt."
  "og:title": "Shopware 6 & Redis: Wenn falsche Konfiguration mehr schadet als nutzt - Kickbyte"
---

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

Redis ist in [Shopware 6](https://kickbyte.de/leistungen/ecommerce-entwicklung) 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.

---

## 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.

| Instanz | Zweck | Persistenz | Eviction-Policy |
| --- | --- | --- | --- |
| `ephemeral` | App-Cache, HTTP-Cache, Locks, Increment | **Aus** | `allkeys-lru` |
| `persistent` | Sessions, Warenkörbe, Number-Ranges | **An** (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):**

| Instanz | Policy | Warum |
| --- | --- | --- |
| Ephemeral (Cache) | `allkeys-lru` | Älteste Einträge fliegen zuerst, neue werden immer geschrieben |
| Persistent (Sessions, Carts, Number-Ranges) | `volatile-lru` | Nur 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

| Fehler | Auswirkung | Fix |
| --- | --- | --- |
| Eine Instanz für alles | Eviction löscht Sessions/Carts | Getrennte ephemeral/persistent Instanzen |
| Persistenz auf Cache | OOM-Kill, veralteter Cache nach Restart | `save ""`, `appendonly no` |
| Kein `maxmemory` | OOM-Kill, Swap-Nutzung | Explizites Limit setzen |
| Falsche Eviction-Policy | Shop-Fehler oder Datenverlust | `allkeys-lru` (Cache), `volatile-lru` (persistent) |
| Unvollständige Shopware-Config | Redis läuft, wird aber nicht genutzt | Alle Subsysteme konfigurieren |
| `?persistent=1` verwechselt | Keine Datenpersistenz trotz Annahme | Server-seitige Persistenz konfigurieren |
| Cart-Migration vergessen | Warenkörbe weg nach Redis-Umstieg | `bin/console cart:migrate` |
| Kein Monitoring | Probleme bleiben unentdeckt | Hit-Rate, Memory, Slow-Log überwachen |
| TCP statt Socket | Unnötiger Overhead | Unix-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.