(G/08) Suche

Shopware 6.7 Suche: der Silent Fallback, der Sie Umsatz kostet

Elasticsearch/OpenSearch in Shopware 6.7 korrekt konfigurieren: die .env-Variable, die den Silent Fallback auf MySQL verhindert, die Sharding-Entscheidung, die Sie nur einmal treffen, und wo Relevanz-Tuning tatsächlich stattfindet.

Von Huzaifa Mustafa 11 Min. Lesezeit 8. Juli 2026

Die Kurzfassung

Ab etwa 10.000 SKUs hält MySQL-basierte Suche und Filterung nicht mehr mit: Listing-Seiten laufen in Timeouts, Facetten werden unter Last langsam, und Relevanz ist Glückssache. OpenSearch löst alle drei Probleme, aber Shopware bietet drei Wege, wie das Setup unbemerkt fehlschlägt. Lassen Sie SHOPWARE_ES_THROW_EXCEPTION weg, und die Suche fällt still auf MySQL zurück, ohne Fehler im Storefront. Wählen Sie eine Shard-Anzahl ohne nachzudenken, und Sie sitzen bis zum vollständigen Reindexing darauf fest. Optimieren Sie Relevanz im Code, obwohl die Administration bereits einen Einstellungsbildschirm dafür hat, und Sie haben etwas gebaut, das niemand in Ihrem Team pflegen kann.

Alles Folgende ist gegen eine echte Shopware 6.7.1.2-Installation geprüft, keine paraphrasierte Dokumentation: die tatsächlichen Umgebungsvariablen, die tatsächliche Fallback-Logik in ElasticsearchHelper, die tatsächlichen mitgelieferten CLI-Befehle und die tatsächliche Datenbanktabelle, die Relevanz steuert.

Elasticsearch oder OpenSearch? Was Shopware tatsächlich nutzt

Das Composer-Paket heißt weiterhin shopware/elasticsearch, und die meisten Stellen in Administration und Dokumentation sprechen noch von „Elasticsearch". Der PHP-Client dahinter ist jedoch opensearch-project/opensearch-php, und die Verbindungsvariable heißt OPENSEARCH_URL, nicht ELASTICSEARCH_URL. Wenn Sie Infrastruktur bereitstellen, richten Sie einen OpenSearch-Cluster ein. Die Benennung in Shopwares eigenem Code hat das nur noch nicht nachvollzogen.

Das ist aus zwei praktischen Gründen relevant: Zahlen Sie nicht für ein lizenziertes Elastic-Stack-Deployment, das Sie nicht brauchen, und suchen Sie bei der Fehlersuche nach beiden Begriffen, denn Shopwares Fehlermeldungen, Log-Zeilen und Community-Threads verwenden sie synonym.

Das Setup korrekt aufsetzen

Schritt 1: Die Verbindung

Setzen Sie Folgendes in .env.local. Jeder Wert hier ist ein echter Default aus Shopwares eigener elasticsearch.yaml, keine Annäherung:

# .env.local
SHOPWARE_ES_ENABLED=1
SHOPWARE_ES_INDEXING_ENABLED=1
SHOPWARE_ES_INDEXING_BATCH_SIZE=100
OPENSEARCH_URL=http://opensearch:9200
SHOPWARE_ES_INDEX_PREFIX=sw
SHOPWARE_ES_THROW_EXCEPTION=1

SHOPWARE_ES_INDEXING_BATCH_SIZE ist standardmäßig auf 100 Dokumente pro Bulk-Request gesetzt. Erhöhen Sie den Wert bei einem großen Katalog mit Spielraum auf dem Cluster, senken Sie ihn, wenn Bulk-Indexierungsanfragen in Timeouts laufen.

Schritt 2: Shard- und Replica-Einstellungen

Shopwares Standard-index_settings für den Produktindex liefern number_of_shards und number_of_replicas mit dem Wert null aus, sodass OpenSearchs eigene Defaults greifen, solange Sie nichts überschreiben. Tun Sie das in config/packages/elasticsearch.yaml vor dem ersten Indexaufbau (warum das nicht warten kann, steht im Abschnitt zur Sharding-Planung unten):

# config/packages/elasticsearch.yaml
shopware:
    elasticsearch:
        index_settings:
            number_of_shards: 3
            number_of_replicas: 1

Schritt 3: Index aufbauen und prüfen

bin/console es:create:alias
bin/console es:index
bin/console es:status

es:status ist ein echter, mitgelieferter Shopware-Befehl. Er meldet Cluster-Health und ob die Indexierung aktuell ist. Schreiben Sie dafür keinen eigenen Health-Check, er existiert bereits.

Die Silent-Fallback-Falle

Hier ist der genaue Mechanismus, aus ElasticsearchHelper::logAndThrowException() im ausgelieferten Quellcode:

public function logAndThrowException(\Throwable $exception): bool
{
    $this->logger->critical($exception->getMessage());

    if ($this->environment === 'test' || $this->throwException) {
        throw $exception;
    }

    return false;
}

Solange throwException nicht true ist, protokolliert eine fehlgeschlagene OpenSearch-Verbindung oder -Abfrage eine einzelne critical-Zeile und gibt false zurück. Die Suche bedient dann still MySQL-Ergebnisse. Keine Fehlerseite, kein 500er, nichts, was ein Kunde bemerken oder ein synthetischer Uptime-Check erfassen würde. Facetten werden langsamer, Relevanz wird schlechter, und die einzige Spur ist ein Log-Eintrag, auf den die meisten Teams keinen Alert gesetzt haben.

Warum das echte Shops trifft: Der ausgelieferte Default für SHOPWARE_ES_THROW_EXCEPTION ist "1", aber Deployment-Templates, die aus einer 6.4- oder 6.5-Installation übernommen wurden, oder die Umgebungskonfiguration eines Infrastruktur-Teams lassen Umgebungsvariablen, die sie nicht kennen, regelmäßig fallen oder leeren. Setzen Sie den Wert explizit in jeder Umgebungs-.env. Verlassen Sie sich nicht darauf, dass der mitgelieferte Default Ihre Deployment-Pipeline übersteht.

Erkennen Sie es auf zwei Wegen: Führen Sie bin/console es:status als Teil Ihrer Deployment- oder Monitoring-Checks aus, und alarmieren Sie bei critical-Log-Einträgen aus dem Elasticsearch-Channel. Beides ist günstiger, als während einer Traffic-Spitze festzustellen, dass die Suche schon eine Woche degradiert war.

Sharding-Planung: Die Entscheidung, die Sie nicht neu treffen können

Die Shard-Anzahl steht in dem Moment fest, in dem ein Index erstellt wird. Sie zu ändern bedeutet, einen neuen Index mit der neuen Einstellung zu bauen und den Alias umzuschalten, also vollständiges Reindexing, kein Config-Reload. Shopwares eigener Admin-Suchindex liefert number_of_shards: 3 und number_of_replicas: 3 fest codiert aus, ein brauchbarer Referenzpunkt, da dieser Index einen deutlich kleineren, vorhersehbareren Datensatz verwaltet als ein großer Produktkatalog.

Grobe Größenrichtwerte, kein Ersatz für Lasttests Ihres eigenen Katalogs:

  • <100K Unter 100.000 Produkten auf einem einzelnen Node: 1 bis 2 Shards reichen meist aus. Mehr Shards als Nodes bedeutet nur zusätzlichen Koordinationsaufwand ohne Nutzen.
  • 100K–1M 100.000 bis 1.000.000 Produkte: 3 bis 5 Shards, an der Node-Anzahl ausgerichtet, damit Shards gleichmäßig verteilt werden.
  • 1M+ Multi-Node-Cluster in dieser Größenordnung: Die Shard-Anzahl sollte direkt mit der Node-Anzahl mitwachsen, und die Replica-Anzahl sollte den tatsächlich benötigten Lese-Durchsatz widerspiegeln, nicht einen Default, den nie jemand überprüft hat.

Übermäßiges Sharding eines kleinen Katalogs ist eine häufige Überkorrektur. Jeder Shard ist ein eigener Lucene-Index mit eigenem Overhead. Zehn Shards für 20.000 Produkte verlangsamen Abfragen, statt sie zu beschleunigen.

Relevanz-Tuning, richtig gemacht

Die Relevanz der Produktsuche wird in 6.7 durch Daten gesteuert, nicht durch Code. Die Tabelle product_search_config_field speichert für jedes durchsuchbare Feld einen field-, tokenize-, searchable- und ranking-Wert, und genau diese Konfiguration liest der Query Builder zum Suchzeitpunkt aus.

Bearbeiten Sie sie über Einstellungen → Suche in der Administration, im Tab Durchsuchbare Inhalte. Erhöhen Sie das Ranking auf name oder ein individuelles SKU-Feld, wenn Produktnamen oder -codes nicht zuerst erscheinen, oder passen Sie tokenize an, wenn Teilübereinstimmungen zu locker oder zu streng wirken. Hierher gehört Relevanz-Tuning für die überwiegende Mehrheit der Shops, kein Deployment nötig, und jeder Admin-Benutzer mit entsprechender Berechtigung kann es anpassen.

Eigene Analyzer brauchen Sie vermutlich auch nicht. Shopware liefert sw_english_analyzer, sw_german_analyzer, sw_ngram_analyzer und einen sw_lowercase_normalizer mit, automatisch pro Storefront-Sprache über language_analyzer_mapping verdrahtet. Einen eigenen Analyzer von Grund auf zu schreiben löst ein Problem, das Shopware für Standard-Storefronts mit mehreren Sprachen bereits gelöst hat.

Der Notausgang für Fälle, die die Ranking-Tabelle wirklich nicht abbilden kann, ist das Dekorieren von AbstractProductSearchQueryBuilder und das direkte Anpassen der Query. Greifen Sie erst darauf zurück, wenn die Admin-Einstellungen ausprobiert und für unzureichend befunden wurden, etwa bei individueller Scoring-Logik oder Boosting nach einem Feld, das sich pro Sales Channel ändert:

class CustomProductSearchQueryBuilder extends AbstractProductSearchQueryBuilder
{
    public function __construct(
        private readonly AbstractProductSearchQueryBuilder $decorated
    ) {}

    public function getDecorated(): AbstractProductSearchQueryBuilder
    {
        return $this->decorated;
    }

    public function build(Criteria $criteria, Context $context): BuilderInterface
    {
        $query = $this->decorated->build($criteria, $context);
        // Individuelle Scoring-Logik auf Basis der Standard-Query
        return $query;
    }
}

Reindexing ohne Downtime

Eine Shard-Änderung, eine Mapping-Änderung oder ein beschädigter Index erfordern jeweils einen Neuaufbau. Shopwares Alias-basierte Indexierung sorgt dafür, dass der Storefront weiterhin den alten Index bedient, während der neue aufgebaut wird:

# Neuen Index unter neuem Alias aufbauen
bin/console es:create:alias

# Befüllen, --no-queue für einen synchronen Lauf, --only zur Begrenzung der Entitäten
bin/console es:index --no-queue --only=product

# Feld-Mappings aktualisieren, falls sich das Schema geändert hat
bin/console es:mapping:update

# Sobald der neue Alias verifiziert ist, alte Indizes entfernen
bin/console es:index:cleanup --force

Führen Sie es:status zwischen Indexierung und Cleanup aus. Führen Sie es:index:cleanup erst aus, wenn Sie bestätigt haben, dass der neue Index vollständig ist und korrekt bedient, er löscht den alten.

Praxisszenarien

Szenario 1: Katalogmigration auf OpenSearch

Das Problem: Ein Shop überschreitet 60.000 SKUs und betreibt MySQL-basierte Suche. Kategorie-Listing-Seiten mit aktiven Filtern brauchen unter normalem Traffic mehrere Sekunden zum Rendern und laufen bei Aktionen komplett in Timeouts.

Die Lösung: OpenSearch aktivieren, Shards direkt für den Katalog dimensionieren (3 Shards in dieser Größenordnung) und Shopwares mitgelieferte Analyzer die beiden Storefront-Sprachen übernehmen lassen. Listing und Filterung verlassen MySQL vollständig, und die Datenbank kümmert sich wieder um Bestellungen und Warenkörbe.

Szenario 2: Black Friday, Suche eine Woche degradiert, niemand hat es bemerkt

Das Problem: Eine Hosting-Migration hat Umgebungsvariablen zurückgesetzt, und SHOPWARE_ES_THROW_EXCEPTION ist dabei still verloren gegangen. Die Suche funktionierte weiter, nur eben gegen MySQL, langsamer und mit schlechterer Relevanz. Niemand bemerkte es, bis die Conversion bei suchgetriebenen Sessions während des Traffic-Anstiegs zu Black Friday zu sinken begann.

Die Lösung: es:status zur Post-Deploy-Checkliste hinzufügen und bei critical-Log-Einträgen aus dem Elasticsearch-Channel alarmieren. Ein Silent Fallback sollte in derselben Stunde im Monitoring auftauchen, in der er passiert, nicht drei Wochen später in einem Conversion-Report.

Szenario 3: Deutsche Suche liefert die falschen Produkte zuerst

Das Problem: Bei einem DE-Storefront tragen Produktnamen im Kundenverhalten deutlich mehr Suchgewicht als das SKU-Feld, aber das Standard-Ranking behandelt beide fast gleich, sodass exakte SKU-Suchen unter locker verwandten Namenstreffern verschwanden.

Die Lösung: Keine Code-Änderung. Ranking-Wert des SKU-Felds unter Einstellungen → Suche, Durchsuchbare Inhalte, erhöhen und bestätigen, dass sw_german_analyzer dem DE-Storefront zugeordnet ist. Reindexieren, prüfen, fertig.

Wichtigste Erkenntnis

OpenSearch schlägt in Shopware 6.7 standardmäßig still fehl. Setzen Sie SHOPWARE_ES_THROW_EXCEPTION explizit, legen Sie die Shard-Anzahl vor dem ersten Indexaufbau fest, und optimieren Sie Relevanz in der Administration, bevor Sie zu einem Decorator greifen. Diese drei Punkte richtig gemacht, und die Suche hört auf, das zu sein, was ausfällt, ohne es Ihnen zu sagen.

Geschrieben von

Teilen:

Suche langsam oder still auf MySQL zurückgefallen?

Ob Sie OpenSearch zum ersten Mal aufsetzen oder einen unbemerkten Fallback diagnostizieren, holen Sie sich Beratung von einem zertifizierten Shopware-Entwickler.

Mit Entwickler sprechen