Shopware Server Optimierung: Performance Optimierung mit Varnish, Redis & Core Web Vitals

1) Messen vor Optimieren: TTFB, Core Web Vitals & Engpässe isolieren
Was du zuerst messen solltest (damit Optimierungen nicht „blind“ sind)
- TTFB (Time To First Byte): Server-/Backend-Latenz; stark beeinflusst durch PHP/DB, Cache-Hits, Netzwerk, TLS.
- Core Web Vitals: Primär Client-seitig, aber stark abhängig von Server-Delivery (z. B. langsame HTML-Antwort verschiebt LCP nach hinten).
- Cache-Hit-Rate: Für Varnish (HTTP Full Page Cache) und Redis (Application/Data Cache) getrennt bewerten.
- Komprimierung: Gzip Compression (oder Brotli) reduziert Transferzeit, wirkt indirekt auf LCP/INP.
Python Script for Benchmarking
import argparse
import statistics
import sys
import time
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
import requests
@dataclass
class Sample:
status: int
ttfb_ms: float
total_ms: float
bytes_in: int
cache_header: str
def measure_once(url: str, headers: Dict[str, str], timeout: float) -> Sample:
"""Measures TTFB via streamed request.
Note: requests doesn't expose true TCP-level TTFB, but this is a practical approximation:
time until first chunk is received.
"""
start = time.perf_counter()
with requests.get(url, headers=headers, stream=True, timeout=timeout) as r:
r.raise_for_status()
first_byte_at: Optional[float] = None
bytes_in = 0
for chunk in r.iter_content(chunk_size=64 * 1024):
if chunk:
bytes_in += len(chunk)
if first_byte_at is None:
first_byte_at = time.perf_counter()
break
# consume remaining to measure total time
for chunk in r.iter_content(chunk_size=64 * 1024):
if chunk:
bytes_in += len(chunk)
end = time.perf_counter()
if first_byte_at is None:
first_byte_at = end
ttfb_ms = (first_byte_at - start) * 1000
total_ms = (end - start) * 1000
cache_header = (
r.headers.get("X-Cache", "")
or r.headers.get("X-Cache-Hits", "")
or r.headers.get("Age", "")
or ""
)
return Sample(status=r.status_code, ttfb_ms=ttfb_ms, total_ms=total_ms, bytes_in=bytes_in, cache_header=cache_header)
def summarize(samples: List[Sample]) -> Dict[str, float]:
ttfb = [s.ttfb_ms for s in samples]
total = [s.total_ms for s in samples]
def p95(values: List[float]) -> float:
values_sorted = sorted(values)
if not values_sorted:
return 0.0
idx = int(round(0.95 * (len(values_sorted) - 1)))
return values_sorted[idx]
return {
"count": float(len(samples)),
"ttfb_avg_ms": statistics.mean(ttfb) if ttfb else 0.0,
"ttfb_p95_ms": p95(ttfb),
"total_avg_ms": statistics.mean(total) if total else 0.0,
"total_p95_ms": p95(total),
"bytes_avg": statistics.mean([s.bytes_in for s in samples]) if samples else 0.0,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Benchmark Shopware endpoints (TTFB & total time).")
parser.add_argument("--url", required=True, help="URL to test (e.g. https://shop.example.com)")
parser.add_argument("--runs", type=int, default=20, help="Number of requests")
parser.add_argument("--timeout", type=float, default=30.0, help="Request timeout")
parser.add_argument("--warmup", type=int, default=3, help="Warmup requests")
parser.add_argument("--header", action="append", default=[], help="Extra header, e.g. 'Cookie: foo=bar'")
args = parser.parse_args()
headers: Dict[str, str] = {
"User-Agent": "Shopware-Perf-Benchmark/1.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
for h in args.header:
if ":" not in h:
raise ValueError("Header must be in 'Key: Value' format")
k, v = h.split(":", 1)
headers[k.strip()] = v.strip()
# Warmup (helps caches stabilize)
for _ in range(args.warmup):
try:
measure_once(args.url, headers=headers, timeout=args.timeout)
except Exception:
pass
samples: List[Sample] = []
for i in range(args.runs):
try:
s = measure_once(args.url, headers=headers, timeout=args.timeout)
samples.append(s)
print(
f"#{i+1:02d} status={s.status} ttfb_ms={s.ttfb_ms:.1f} total_ms={s.total_ms:.1f} bytes={s.bytes_in} cache='{s.cache_header}'"
)
except Exception as e:
print(f"#{i+1:02d} ERROR: {e}", file=sys.stderr)
if not samples:
print("No successful samples.", file=sys.stderr)
return 2
stats = summarize(samples)
print("\nSummary")
for k, v in stats.items():
if k.endswith("_ms"):
print(f"- {k}: {v:.1f}")
else:
print(f"- {k}: {v:.0f}" if float(v).is_integer() else f"- {k}: {v}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
| Feature | Details |
|---|---|
| TTFB | Proxy-/App-/DB-Latenz sichtbar machen; Ziel: stabil niedrige P95-Werte, besonders für Category/PDP/Checkout. |
| Core Web Vitals | LCP profitiert von schneller HTML-Auslieferung + Komprimierung; INP/CLS eher Frontend, aber Server-Blocking kann indirekt schaden. |
| Varnish Cache | HTTP Reverse Proxy für Full Page Caching; senkt TTFB massiv bei Cache-Hit. |
| Redis Caching | Schneller In-Memory Cache für App-Daten/Session/Locks; reduziert DB-Last und PHP-Work. |
| Gzip Compression | Reduziert Payload (HTML/CSS/JS); verbessert Übertragungszeit, speziell mobil/hohe RTT. |
2) Quick Wins: Komprimierung, HTTP-Header & statische Assets
Gzip Compression sauber aktivieren (und was es bringt)
Aktiviere Gzip Compression für HTML, CSS, JS, JSON und SVG. Ergebnis: weniger Bytes, schnellerer Transfer, oft bessere LCP-Werte. Achte darauf, dass Komprimierung vor Varnish (oder auf dem Edge/Load Balancer) konsistent konfiguriert ist, damit Cache-Varianten nicht explodieren.
Interessiert an diesem Thema?
Kontaktieren Sie uns für eine kostenlose Beratung →Cache-Control für Assets
- Statische Assets mit Fingerprints (z. B.
app.9c1a2.js) sollten langfristig gecacht werden (immutable). - HTML eher kurz, aber über Varnish sauber invalidieren (Tags/Purge/ban).
3) Varnish Cache in Shopware: Full Page Caching für niedrige TTFB
Was ist Varnish Cache (und warum ist er so effektiv)?
Varnish Cache ist ein HTTP Reverse Proxy, der Antworten (z. B. Category Listing, PDP, Landingpages) im RAM cached. Bei einem Cache-Hit wird die Seite ausgeliefert, ohne dass PHP-FPM, Shopware oder die Datenbank arbeiten müssen. Das senkt TTFB typischerweise drastisch und stabilisiert P95-Werte – ein direkter Hebel für bessere Core Web Vitals (insbesondere LCP).
Worauf du bei Shopware achten musst
- Variantenbildung (Vary): Sprache, Währung, Device, Login-Status, A/B-Tests – alles kann Cache fragmentieren.
- Cookies: Personalisierung und Warenkorb-Status können Cache-Hits verhindern. Strategie: so wenig Cookies wie möglich für anonyme Seiten, “grace” nutzen.
- Invalidierung: Produkt-/Kategorieänderungen müssen Cache gezielt invalidieren (Tags/Purge), statt alles zu leeren.
Mermaid: Request Flow mit Varnish & Redis
flowchart LR
U[User / Browser] -->|HTTP| V[Varnish Cache]
V -->|Cache HIT: low TTFB| U
V -->|Cache MISS| W[Web Server / PHP-FPM]
W --> S[Shopware Application]
S -->|Query / Write| DB[(Database)]
S --> R[(Redis Cache)]
R --> S
S --> W --> V --> U
4) Redis Caching: weniger Datenbank, schnellere Shops, stabilere Peaks
Was ist Redis Caching (und wo es in Shopware wirkt)?
Redis Caching ist In-Memory Caching. In Shopware wird Redis typischerweise genutzt, um häufig verwendete Daten/Strukturen schneller bereitzustellen, teure Berechnungen zu vermeiden und Datenbankzugriffe zu reduzieren. Ergebnis: geringere Backend-Latenz, geringere DB-CPU, stabilere Performance unter Last (z. B. Kampagnen, Sale).
Praxis-Effekte, die du erwarten kannst
- Niedrigere Backend-Zeit pro Request (wirkt auf TTFB bei Cache-Miss im Varnish).
- Bessere Stabilität bei hoher Parallelität (weniger DB-Contention).
- Schnellere API- und Storefront-Requests, wenn Caches sinnvoll konfiguriert sind.
5) YAML Config for Caching Setup (Varnish + Redis, realistisch als Deploy-Template)
Das folgende YAML ist als praxisnahes Setup-Template gedacht (z. B. für Docker Compose oder als Infrastruktur-Baustein). Es zeigt Varnish Cache vor Shopware sowie Redis Caching als separaten Service. Passe Hostnames, Ports und Secrets an deine Umgebung an.
YAML Config for Caching Setup
services:
varnish:
image: varnish:7.5
container_name: shopware_varnish
ports:
- "80:80"
depends_on:
- web
environment:
# Some images support env templating; otherwise mount a VCL.
VARNISH_SIZE: "512M"
volumes:
- ./varnish/default.vcl:/etc/varnish/default.vcl:ro
command:
- "varnishd"
- "-F"
- "-a"
- ":80"
- "-f"
- "/etc/varnish/default.vcl"
- "-s"
- "malloc,512M"
web:
image: nginx:1.27
container_name: shopware_web
depends_on:
- app
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
environment:
# If terminating TLS elsewhere, keep HTTP internally.
UPSTREAM: "app:9000"
app:
image: my-shopware-fpm:8.3
container_name: shopware_app
depends_on:
- redis
- db
environment:
APP_ENV: "prod"
# Redis endpoint used by Shopware/Symfony cache adapters (implementation depends on your image/runtime)
REDIS_URL: "redis://redis:6379/0"
# Example toggles (names vary by stack); document your runtime expectations
SHOPWARE_HTTP_CACHE_ENABLED: "1"
redis:
image: redis:7.4-alpine
container_name: shopware_redis
ports:
- "6379:6379"
command:
- "redis-server"
- "--appendonly"
- "yes"
- "--maxmemory"
- "512mb"
- "--maxmemory-policy"
- "allkeys-lru"
db:
image: mariadb:11.4
container_name: shopware_db
environment:
MARIADB_DATABASE: "shopware"
MARIADB_USER: "shopware"
MARIADB_PASSWORD: "change-me"
MARIADB_ROOT_PASSWORD: "change-me-too"
volumes:
- db_data:/var/lib/mysql
volumes:
db_data: {}
6) Checkliste: So erreichst du dauerhaft hohe Cache-Hit-Rates
- Varnish: Anonyme Seiten konsequent cachebar machen (Cookies reduzieren, Vary diszipliniert halten).
- Redis: Maxmemory + Eviction-Policy passend wählen (z. B. allkeys-lru), Persistenz nur wenn nötig.
- Invalidierung: Lieber gezielt (Tags/Purge) als globales Flushen.
- Beobachten: TTFB P95, Cache-Hit-Rate, DB-CPU/Locks, Redis Hit-Rate, Fehlerquoten.
- Komprimierung: Gzip Compression (oder Brotli) aktiv, korrekte Content-Types, keine Doppelkomprimierung.
7) Typische Ursachen für schlechte Core Web Vitals trotz „schnellem Server“
- Zu viel render-blocking JS/CSS (Server schnell, Browser aber blockiert).
- Große Hero-Bilder ohne Preload/optimierte Formate.
- Third-Party Tags (Consent, Tracking) verschlechtern INP/LCP.
- Zu aggressive Personalisierung verhindert Varnish Cache-Hits → TTFB steigt.
8) Nächste Schritte für deine Performance Optimierung
- Benchmark (TTFB/Total/P95) für Startseite, Kategorie, PDP und Checkout erstellen.
- Varnish Cache-Hit-Rates auswerten und Cookie/Vary-Bremsen entfernen.
- Redis Caching stabil dimensionieren (Memory, Eviction, Monitoring).
- Komprimierung und Asset-Caching finalisieren (Gzip Compression, Cache-Control, immutable).


