News

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

Von Erol Demirkoparan
9 min
Shopware Server Optimierung: Performance Optimierung mit Varnish, Redis & Core Web Vitals - Cloudox Software Agentur Blog

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())
FeatureDetails
TTFBProxy-/App-/DB-Latenz sichtbar machen; Ziel: stabil niedrige P95-Werte, besonders für Category/PDP/Checkout.
Core Web VitalsLCP profitiert von schneller HTML-Auslieferung + Komprimierung; INP/CLS eher Frontend, aber Server-Blocking kann indirekt schaden.
Varnish CacheHTTP Reverse Proxy für Full Page Caching; senkt TTFB massiv bei Cache-Hit.
Redis CachingSchneller In-Memory Cache für App-Daten/Session/Locks; reduziert DB-Last und PHP-Work.
Gzip CompressionReduziert 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.

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

Screenshot einer Lighthouse-Auswertung mit Fokus auf TTFB und LCP, sowie Hinweis auf aktivierte Text-Komprimierung
Screenshot einer Lighthouse-Auswertung mit Fokus auf TTFB und LCP, sowie Hinweis auf aktivierte Text-Komprimierung

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

Performance Optimierung

Häufig gestellte Fragen

Autor

Erol Demirkoparan

Erol Demirkoparan

Senior Software Architect

Full-Stack & Cloud-Native Systems Expert. Spezialisiert auf AWS, Next.js und skalierbare SaaS-Architekturen. Building the future of automated SEO.

AWSNext.jsScalable SaaSSystem Architecture

Veröffentlicht am

6. Januar 2026

Das könnte Sie auch interessieren