Wenn Sie einen Shopware 6 Shop mit hohem Traffic betreiben, ist der Standard Doctrine DBAL Transport für den Symfony Messenger wahrscheinlich Ihr erster Performance-Engpass. Jede E-Mail, Bestandssynchronisation und Indexierungsaufgabe schreibt in die messenger_messages-Tabelle. Unter Last erzeugen die Polling-Abfragen, die Worker zum Konsumieren von Nachrichten verwenden, Lock-Konflikte. Bei Traffic-Spitzen verbringt Ihre Datenbank Ressourcen mit der Queue-Verwaltung anstatt Bestellungen zu bedienen.
Diese Anleitung bietet ein produktionsreifes Setup zum Wechsel auf Redis Streams in Shopware 6.7, plus zwei Praxisszenarien, in denen diese architektonische Änderung einen messbaren Unterschied macht.
Das symfony/redis-messenger-Paket erfordert die PHP Redis Extension. Installieren Sie diese zuerst, falls noch nicht geschehen:
pecl install redis
Oder verwenden Sie Ihren Paketmanager, zum Beispiel:
apt install php8.3-redis
Starten Sie PHP-FPM nach der Installation neu.
composer require symfony/redis-messenger
Shopware definiert drei Messenger-Transports: async, low_priority und failed. Jeder hat seine eigene Umgebungsvariable. Setzen Sie die ersten beiden auf Redis und behalten Sie failed auf Doctrine, damit Sie fehlgeschlagene Nachrichten per SQL inspizieren können.
Verwenden Sie dbindex=1, um Queue-Daten von Ihrem Cache zu isolieren (der typischerweise Datenbank 0 verwendet). Die Flags delete_after_ack und delete_after_reject verhindern, dass verarbeitete Nachrichten unbegrenzt im Stream verbleiben.
# .env.local
MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages?dbindex=1&delete_after_ack=true&delete_after_reject=true
MESSENGER_TRANSPORT_LOW_PRIORITY_DSN=redis://localhost:6379/messages_low_priority?dbindex=1&delete_after_ack=true&delete_after_reject=true
MESSENGER_TRANSPORT_FAILURE_DSN=doctrine://default?queue_name=failed&auto_setup=false
Hinweis zum DSN-Format: Das Pfadsegment nach dem Port ist der Stream-Name, kein Datenbank-Index. redis://localhost:6379/1/messages würde einen Stream namens 1 mit der Consumer Group messages erstellen. Verwenden Sie stattdessen immer den dbindex-Query-Parameter.
Shopwares integrierte framework.yaml definiert bereits die Transports async, low_priority und failed mit dem korrekten Serializer (messenger.transport.symfony_serializer), der Retry-Strategie (3 Versuche, exponentielles Backoff) und dem Message-Routing. Das Setzen der Umgebungsvariablen ist alles, was zum Wechsel des Transport-Backends nötig ist.
Shopwares Standard-Transport-Routing (keine Änderungen nötig):
# vendor/shopware/core/.../framework.yaml (schreibgeschützt, nicht bearbeiten)
routing:
'Shopware\...\AsyncMessageInterface': async
'Shopware\...\LowPriorityMessageInterface': low_priority
'Symfony\...\SendEmailMessage': async
Konsumieren Sie beide Transports. Verwenden Sie --time-limit und --memory-limit, damit Worker sauber neustarten und über die Zeit keinen Speicher leaken.
bin/console messenger:consume async low_priority --time-limit=60 --memory-limit=512M
Beim Betrieb mehrerer Worker-Prozesse muss jeder Worker einen eindeutigen Consumer-Namen innerhalb desselben Streams und derselben Gruppe haben. Ohne dies können Nachrichten an mehrere Worker gleichzeitig zugestellt werden. Setzen Sie den Consumer-Namen über die DSN oder verwenden Sie eine prozessspezifische Umgebungsvariable:
# Consumer-Name pro Worker-Instanz an die DSN anhängen
MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages?dbindex=1&delete_after_ack=true&consumer=worker-1
failed auf Doctrine behalten?
Fehlgeschlagene Nachrichten werden manuell inspiziert und erneut versucht. Die Speicherung in MySQL ermöglicht SQL-Abfragen, Filterung nach Fehlertyp und selektives Retry über bin/console messenger:failed:show und messenger:failed:retry. Redis funktioniert dafür auch, aber Sie verlieren die Abfragbarkeit.
Das Problem: Tausende von Nutzern kaufen gleichzeitig. Der Standard MySQL-Transport löst Row-Level-Lock-Konflikte auf der messenger_messages-Tabelle aus, während Worker nach neuen Nachrichten pollen. Dies führt zu Query-Timeouts und fehlgeschlagener Bestellverarbeitung.
Die Lösung: Leiten Sie die beim Checkout ausgelösten asynchronen Nachrichten (Bestellbestätigungs-E-Mails, Bestandsverringerungen, Indexierung) zu Redis um. Redis Streams verarbeiten die Aufnahme nahezu sofort. Hintergrund-Worker konsumieren diese Nachrichten dann in kontrolliertem Tempo und halten den Checkout-Ablauf responsiv und frei von Datenbank-Deadlocks.
Das Problem: Ihr ERP sendet 200.000 Preisaktualisierungen über die API. Die synchrone Verarbeitung führt entweder zu HTTP-Request-Timeouts oder beeinträchtigt die Storefront-Performance für aktive Käufer.
Die Lösung: Erstellen Sie eine benutzerdefinierte ImportPriceMessage und dispatchen Sie sie von Ihrem Controller an die Redis-gestützte Queue. Der Controller gibt sofort eine 202 Accepted-Antwort zurück. Ein dedizierter Worker konsumiert die Queue und verarbeitet Preise in Batches, völlig entkoppelt von der Storefront.
RabbitMQ bietet Vorteile, wenn Sie dauerhafte Nachrichtenpersistenz, komplexes Routing oder Dead-Letter-Queue-Handling benötigen. Redis Streams funktionieren am besten, wenn Geschwindigkeit und Einfachheit Priorität haben und Sie Redis bereits für Caching in Ihrem Stack haben.
Redis ist nicht nur für Caching. Es ist das Rückgrat einer entkoppelten, skalierbaren Shopware-Architektur. Indem Sie Ihre Message Queue von der Datenbank trennen, befreien Sie MySQL-Ressourcen für das Wichtigste: Ihre Kunden zu bedienen.
Entdecken Sie weitere Shopware-Entwicklungsthemen:
Ob Sie sich auf Black Friday Traffic vorbereiten oder Ihre Message Queue Architektur optimieren - holen Sie sich Expertenberatung von einem zertifizierten Shopware-Entwickler.
Mit Entwickler sprechen