Magento-Indexer-Performance: Von Full-Reindex zu Schema-Updates
60fps
ms
Performance · Indexer · Cron · Magento 2
Magento-Indexer-Performance: Von Full-Reindex zu Schema-Updates
Teure Indexer erkennen, Full-Reindex vermeiden, Cron gezielt tunen

Ein hängender oder ständig neu berechnender Indexer bremst den gesamten Shop aus, von veralteten Preisen bis zu leeren Suchergebnissen. Dieser Artikel erklärt, welche Indexer-Modi es gibt, warum der Price Index und Category Products am teuersten sind, welche Änderungen einen ungewollten Full-Reindex auslösen und wie ihr hängende Indexer über indexer:status, die Tabelle indexer_state und wachsende Mview-Changelogs zuverlässig diagnostiziert und behebt.

17 Min. Lesezeit Indexer · Mview · Cron · Reindex Magento 2.4.8 · Hyvä Theme · MySQL 8

1. Wie Magentos Indexer-Architektur grundsätzlich funktioniert

Magentos Indexer übersetzen normalisierte, transaktionale Daten (Produkte, Kategorien, Preise, Attribute) in denormalisierte, lesefreundliche Tabellen, die der Storefront ohne teure Joins zur Laufzeit ausliefern kann. Jeder Indexer ist in indexer.xml deklariert und implementiert Magento\Framework\Indexer\ActionInterface mit den vier Methoden executeFull(), executeList(), executeRow() und executePartial(), die Magento je nach Modus und Umfang der Änderung gezielt aufruft. Diese Trennung ist der Grund, warum ein einzelner Preis-Update im Adminpanel nicht zwingend eine komplette Neuberechnung aller Produktpreise auslöst.

Standardmäßig liefert Magento neun Indexer aus: catalog_product_attribute, catalog_category_product, catalog_product_category, catalog_product_price, cataloginventory_stock, catalog_product_flat (nur bei Flat Catalog), catalogrule_rule, catalogrule_product, catalogsearch_fulltext und customer_grid. Jeder dieser Indexer schreibt in eine eigene Zieltabelle mit dem Suffix _cl für das Changelog beziehungsweise _idx/_replica für die eigentlichen Indextabellen im Live-Betrieb, was atomare Umschaltungen ohne Downtime ermöglicht.

2. Update on Save vs. Update by Schedule: die Grundsatzentscheidung

Im Modus Update on Save reindexiert Magento synchron innerhalb des HTTP-Requests, sobald ein Produkt, eine Kategorie oder eine Preisregel gespeichert wird. Das ist für kleine Kataloge mit wenigen tausend SKUs praktikabel, weil Änderungen sofort im Frontend sichtbar sind, ohne auf einen Cron-Lauf zu warten. Bei größeren Katalogen oder häufigen Massenimporten führt derselbe Mechanismus jedoch zu Timeouts im Admin, weil ein Speichervorgang plötzlich Sekunden oder Minuten dauert, während im Hintergrund tausende abhängige Datensätze neu berechnet werden.

Update by Schedule entkoppelt diesen Vorgang: Speicheroperationen schreiben lediglich einen Eintrag in die zugehörige _cl-Changelog-Tabelle über einen MySQL-Trigger, und ein Cron-Job arbeitet diese Änderungen asynchron in konfigurierbaren Batches ab. Der Admin bleibt dadurch responsiv, allerdings mit einer Verzögerung zwischen Speichern und sichtbarer Änderung im Storefront, typischerweise im Bereich weniger Minuten je nach Cron-Intervall. Für Produktivsysteme mit mehr als etwa 10.000 Produkten oder regelmäßigen Bulk-Importen ist Update by Schedule für catalog_product_price, catalog_category_product und catalogsearch_fulltext praktisch Pflicht.


# Aktuellen Modus aller Indexer anzeigen
bin/magento indexer:status

# Einzelnen Indexer auf Schedule-Modus umstellen
bin/magento indexer:set-mode schedule catalog_product_price

# Alle teuren Indexer gleichzeitig auf Schedule umstellen
bin/magento indexer:set-mode schedule \
    catalog_product_price \
    catalog_category_product \
    catalogsearch_fulltext \
    catalogrule_rule

# Modus pruefen und Zeitstempel der letzten Aktualisierung sehen
bin/magento indexer:show-mode

3. Welche Indexer am teuersten sind und warum

Der Price Index (catalog_product_price) ist in fast jedem realen Shop der teuerste Indexer, weil er nicht nur einen Preis pro Produkt berechnet, sondern die Kombinatorik aus Kundengruppe, Website und aktiven Preisregeln vollständig durchrechnet. Bei fünf Kundengruppen und drei Websites entstehen pro Produkt bis zu 15 Zeilen in catalog_product_index_price, multipliziert mit der Katalogsgröße. Configurable- und Bundle-Produkte verschärfen das zusätzlich, weil deren Preis von allen Kindprodukten abhängt und bei jeder Kind-Preisänderung neu aggregiert werden muss.

catalog_category_product und catalogsearch_fulltext sind aus einem anderen Grund teuer: Beide benötigen für jedes Produkt mehrere EAV-Attribut-Joins über catalog_product_entity_varchar, _int, _decimal und _text, da Produktattribute in Magentos EAV-Modell nicht in einer einzigen Tabelle liegen. Bei Elasticsearch als Suchmaschine kommt zusätzlich die Serialisierung und der Netzwerk-Roundtrip beim Bulk-Indexing hinzu. Ein praktischer Hebel ist, nicht durchsuchbare oder nicht filterbare Attribute konsequent aus dem Suchindex auszuschließen, da jedes zusätzliche indexierbare Attribut die Joins pro Produkt linear erhöht.

4. Partial vs. Full Reindex: was einen kompletten Neuaufbau auslöst

Ein Partial Reindex verarbeitet nur die tatsächlich geänderten IDs aus der Changelog-Tabelle über executeList() und ist in der Regel um Größenordnungen schneller als ein kompletter Neuaufbau. Ein Full Reindex über executeFull() berechnet dagegen den gesamten Datenbestand neu und wird typischerweise durch bin/magento indexer:reindex ohne Argumente, durch Änderungen an globalen Attribut-Einstellungen (z. B. "Use in Layered Navigation" oder "Searchable"), durch das Anlegen oder Löschen einer Website beziehungsweise Kundengruppe sowie durch Systemupgrades mit Schema-Änderungen an Indextabellen ausgelöst.

Ein häufig übersehener Trigger ist der Wechsel des Indexer-Modus selbst: Ein indexer:set-mode schedule markiert den Indexer als invalid, bis der nächste Cron-Lauf ihn vollständig neu aufbaut, da das bisherige Changelog keine historischen Änderungen enthält. Auch bin/magento indexer:reset setzt einen Indexer explizit auf invalid und erzwingt beim nächsten Lauf einen Full Reindex, was in CI/CD-Pipelines nach einem Deployment mit Attribut-Änderungen sinnvoll, im laufenden Tagesbetrieb aber ein teurer Fehlgriff sein kann.


-- Groesse der Mview-Changelog-Tabelle fuer den Price-Index pruefen
SELECT COUNT(*) AS pending_rows
FROM catalog_product_price_cl;

-- Alte, bereits verarbeitete Changelog-Eintraege manuell bereinigen
-- (nur ausfuehren, wenn der zugehoerige Indexer nachweislich 'valid' ist)
DELETE FROM catalog_product_price_cl
WHERE version_id <= (
    SELECT MIN(version_id) FROM catalog_product_price_cl
) + 0
LIMIT 50000;

-- Aktuelle Version des Change-Data-Capture je View pruefen
SELECT * FROM mview_state WHERE view_id = 'catalog_product_price_cl';

5. indexer:status lesen und den indexer_state richtig interpretieren

bin/magento indexer:status zeigt pro Indexer den Status Ready, Processing oder Invalid sowie den Modus und den Zeitpunkt der letzten Aktualisierung. Bleibt ein Indexer dauerhaft auf Processing stehen, deutet das meist auf einen abgebrochenen Cron-Prozess hin, etwa durch ein PHP-Memory-Limit, einen Deployment-Neustart mitten im Lauf oder einen Datenbank-Lock durch eine parallele Import-Operation. Die zugrunde liegende Tabelle indexer_state speichert diesen Status persistent; ein Blick mit SELECT * FROM indexer_state zeigt exakt, welcher indexer_id auf welchem Status hängt, auch wenn die CLI-Ausgabe wegen eines Cache-Problems veraltet wirkt.

Ein hängender Indexer lässt sich in der Regel mit bin/magento indexer:reindex <indexer_id> gezielt neu anstoßen, was den Status zurücksetzt und einen frischen Full Reindex erzwingt. Bricht der Reindex erneut ab, lohnt sich ein Blick in var/log/system.log und var/log/exception.log sowie eine Prüfung von php -d memory_limit=-1 bin/magento indexer:reindex, um Speicherlimits als Ursache auszuschließen. Bei MySQL-Locks hilft SHOW ENGINE INNODB STATUS, um konkurrierende Transaktionen zu identifizieren, die die Zieltabelle während des Reindex blockieren.


# Status aller Indexer mit Modus und letztem Update anzeigen
bin/magento indexer:status

# Beispielausgabe:
# Title                     Status    Update Mode   Schedule Update
# Product Price             Invalid   Schedule      2026-07-11 08:12:03
# Category Products         Ready     Schedule      2026-07-11 09:00:11
# Catalog Search             Processing Schedule     2026-07-11 09:14:55

# Nur einen haengenden Indexer gezielt neu aufbauen
bin/magento indexer:reindex catalog_product_price

# Reindex mit erhoehtem Memory-Limit bei grossen Katalogen
php -d memory_limit=-1 bin/magento indexer:reindex catalogsearch_fulltext

6. Mview-Changelog-Tabellen: Wachstum erkennen und kontrollieren

Jeder Indexer im Schedule-Modus besitzt eine Mview-Changelog-Tabelle (z. B. catalog_product_price_cl), die bei jeder relevanten Änderung über einen Datenbank-Trigger eine neue Zeile mit der betroffenen Entity-ID erhält. Läuft der zugehörige Cron-Job seltener als neue Änderungen anfallen, etwa weil der Cron-Prozess deaktiviert oder blockiert ist, wächst diese Tabelle unbegrenzt weiter und kann bei Millionen Zeilen selbst einfache Lese-Queries auf der Basistabelle verlangsamen, da die Trigger bei jedem Insert/Update mitschreiben müssen.

Nach jedem erfolgreichen Lauf löscht Magento normalerweise die verarbeiteten Einträge automatisch anhand der in mview_state gespeicherten version_id. Bleibt dieses Aufräumen aus, etwa nach einem manuell abgebrochenen Cron-Job, hilft ein kontrollierter manueller Cleanup der _cl-Tabellen, gefolgt von einem indexer:reindex, um den Mview-Status neu zu synchronisieren. Ein regelmäßiger Monitoring-Check per Cronjob, der die Zeilenzahl aller %_cl-Tabellen gegen einen Schwellenwert wie 500.000 prüft, verhindert, dass dieses Problem unbemerkt eskaliert.


<?php
declare(strict_types=1);

namespace Mironsoft\IndexerMonitor\Console\Command;

use Magento\Framework\App\ResourceConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Checks all Mview changelog (_cl) tables for unbounded growth
 * and warns when a table exceeds the configured row threshold.
 */
class CheckChangelogSizeCommand extends Command
{
    private const int ROW_THRESHOLD = 500000;

    /**
     * @param ResourceConnection $resourceConnection Database resource connection.
     */
    public function __construct(
        private readonly ResourceConnection $resourceConnection
    ) {
        parent::__construct('mironsoft:indexer:check-changelog-size');
    }

    /**
     * Iterates all *_cl tables and reports row counts above the threshold.
     *
     * @param InputInterface $input CLI input.
     * @param OutputInterface $output CLI output.
     * @return int Exit code.
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $connection = $this->resourceConnection->getConnection();
        $tables = $connection->fetchCol("SHOW TABLES LIKE '%\_cl'");

        foreach ($tables as $table) {
            $count = (int) $connection->fetchOne("SELECT COUNT(*) FROM `{$table}`");
            if ($count > self::ROW_THRESHOLD) {
                $output->writeln("<error>{$table}: {$count} rows pending, cron may be stalled</error>");
            }
        }

        return Command::SUCCESS;
    }
}

7. Cron-basiertes Scheduled Indexing tunen

Scheduled Indexing läuft über die Cron-Gruppe index, deklariert in Magento_Indexer/etc/crontab.xml mit Standardintervall von einer Minute. Jeder Lauf verarbeitet offene Changelog-Einträge in Batches, deren Größe über Magento\Indexer\Model\ProcessManager und die Konfiguration indexer/batch_size pro Indexer in indexer.xml gesteuert wird. Ein zu klein gewähltes Batch von etwa 100 Zeilen erzeugt viel Overhead durch wiederholte Query-Ausführung, während ein zu großes Batch von mehreren zehntausend Zeilen einzelne Cron-Läufe so lange blockiert, dass parallele Cron-Gruppen wie default in einen Rückstau geraten.

In der Praxis bewährt sich, den batch_size für catalog_product_price in mittelgroßen Katalogen (50.000 bis 200.000 SKUs) auf 5.000 bis 10.000 anzuheben und gleichzeitig cron_schedule regelmäßig auf verwaiste Einträge zu prüfen, da hängende Jobs mit Status running, deren Prozess längst beendet ist, nachfolgende Läufe derselben Gruppe blockieren. Ein eigener Cron-Pool für die index-Gruppe mit cron/index/use_separate_process entkoppelt Indexer-Läufe zusätzlich von Standard-Cronjobs wie E-Mail-Versand oder Sitemap-Generierung, sodass ein langsamer Reindex nicht das gesamte Cron-System blockiert.


<!-- app/code/Mironsoft/IndexerMonitor/etc/indexer.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd">
    <indexer id="catalog_product_price" class="Magento\Catalog\Model\Indexer\Product\Price">
        <title translate="true">Product Price</title>
        <description translate="true">Product Price index</description>
        <!-- Larger batches reduce per-run overhead on medium catalogs -->
        <saveHandler batchSize="7500"/>
        <fieldset name="catalog_product">
            <field name="price" xsi:type="string" origin="price"/>
        </fieldset>
    </indexer>
</config>

8. Eigene Indexer und Mview-Deklarationen sauber aufsetzen

Ein eigener Indexer benötigt neben der ActionInterface-Implementierung eine mview.xml-Deklaration, die festlegt, auf welche Tabellen und Spalten der Indexer per Trigger reagiert. Fehlt diese Deklaration oder ist sie unvollständig, bleibt der Indexer im Schedule-Modus dauerhaft Invalid, weil nie ein Changelog-Eintrag erzeugt wird, den der Cron-Job verarbeiten könnte. Ein typischer Fehler ist, nur die Haupttabelle zu überwachen, aber verknüpfte Tabellen wie eine eigene Preis- oder Attributzuordnung zu vergessen, wodurch Änderungen an diesen Nebentabellen den Index niemals invalidieren.

Für Custom Indexer gilt zusätzlich: executeRow() und executeList() sollten niemals einfach executeFull() intern aufrufen, auch wenn das der schnellste Weg zur Implementierung wäre, da dadurch aus jedem Partial Reindex faktisch ein Full Reindex wird und der gesamte Sinn von Update by Schedule verloren geht. Stattdessen sollte die Update-Logik gezielt nur die übergebenen IDs neu berechnen, etwa über ein WHERE entity_id IN (...) in der zugrunde liegenden Query.


<!-- app/code/Mironsoft/IndexerMonitor/etc/mview.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">
    <view id="mironsoft_custom_price_rule_cl" class="Mironsoft\IndexerMonitor\Model\Indexer\CustomPriceRule"
          group="indexer">
        <subscriptions>
            <!-- Trigger on the main entity table -->
            <table name="mironsoft_custom_price_rule" entity_column="rule_id"/>
            <!-- Also trigger on the linked product assignment table -->
            <table name="mironsoft_custom_price_rule_product" entity_column="rule_id"/>
        </subscriptions>
    </view>
</config>

9. Standard-Indexer im direkten Vergleich

Die sechs wichtigsten Standard-Indexer unterscheiden sich stark in relativen Kosten, empfohlenem Modus und dem typischen Auslöser für einen Full Reindex. Die folgende Übersicht fasst die praxisrelevanten Unterschiede zusammen.

Indexer Relative Kosten Empfohlener Modus Typischer Full-Reindex-Trigger
Product Price Sehr hoch Schedule Neue Kundengruppe/Website, Massen-Preisänderung
Category Products Hoch Schedule Attribut-Konfiguration geändert (z. B. Sichtbarkeit)
Catalog Search / Elasticsearch Hoch Schedule Suchbares Attribut hinzugefügt/entfernt
Product EAV Mittel Schedule Attribut-Set-Änderung, Store-View-Anlage
Catalog Rule Mittel Save (klein) / Schedule (groß) Regel-Gültigkeitszeitraum manuell verändert
Customer Grid Niedrig Save oder Schedule Neues Kunden-Attribut im Grid-Filter

In der Praxis dominieren Product Price, Category Products und Catalog Search die Cron-Laufzeit fast jedes größeren Shops, während Customer Grid selbst im Update-on-Save-Modus kaum spürbar ist. Wer die drei teuren Indexer konsequent auf Schedule stellt, Batch-Größen an die Kataloggröße anpasst und Full-Reindex-Trigger kennt, reduziert die durchschnittliche Cron-Laufzeit oft um 60-80 % gegenüber einer unkonfigurierten Standardinstallation.

Mironsoft

Indexer-Performance, Cron-Tuning und Backend-Optimierung für Magento-Shops

Hängende Indexer und lange Reindex-Läufe beheben?

Wir analysieren eure Indexer-Konfiguration, identifizieren die teuersten Läufe und setzen gezielte Optimierungen um, von Cron-Tuning bis zur Entwicklung eigener, effizienter Indexer.

Indexer-Audit

Analyse aller Indexer-Laufzeiten, Modi und Mview-Changelogs

Cron-Tuning

Batch-Größen, Cron-Gruppen und Prozess-Trennung optimal konfigurieren

Custom-Indexer-Entwicklung

Eigene Indexer und Mview-Deklarationen nach Magento-Best-Practices

10. Zusammenfassung

Die Magento-Indexer-Performance entscheidet direkt darüber, wie aktuell Preise, Kategorien und Suchergebnisse im Storefront sind und wie stark Cron-Läufe die Serverlast prägen. Update by Schedule entkoppelt Speichervorgänge im Admin von der eigentlichen Neuberechnung und ist für Kataloge ab etwa 10.000 Produkten praktisch unverzichtbar, besonders für die drei teuersten Indexer Product Price, Category Products und Catalog Search. Ein Full Reindex sollte bewusst ausgelöst werden, nicht versehentlich durch globale Attribut-Änderungen oder falsch konfigurierte Custom Indexer, die intern immer executeFull() aufrufen.

Monitoring über indexer:status, die Tabelle indexer_state und regelmäßige Kontrolle der Mview-Changelog-Tabellen verhindert, dass hängende Indexer oder unbegrenzt wachsende _cl-Tabellen unbemerkt zum Problem werden. Wer zusätzlich Batch-Größen an die Kataloggröße anpasst und die index-Cron-Gruppe von anderen Cron-Jobs entkoppelt, reduziert Laufzeiten spürbar und hält den Shop auch bei häufigen Datenänderungen performant.

Magento-Indexer-Performance - Das Wichtigste auf einen Blick

Update on Save vs. Schedule

Schedule entkoppelt Admin-Speichern von der Neuberechnung. Ab ca. 10.000 Produkten für Price, Category Products und Search Pflicht.

Teuerste Indexer

Product Price wegen Kundengruppen/Website-Kombinatorik, Category Products und Search wegen EAV-Attribut-Joins.

Full-Reindex-Trigger vermeiden

Globale Attribut-Änderungen, neue Website/Kundengruppe und schlecht implementierte Custom Indexer lösen ungewollt einen Full Reindex aus.

Monitoring & Cron-Tuning

indexer:status, indexer_state und _cl-Tabellengröße regelmäßig prüfen, Batch-Größe und Cron-Gruppen anpassen.

11. FAQ: Magento-Indexer-Performance

1Was ist der Unterschied zwischen Update on Save und Update by Schedule?
Update on Save reindexiert synchron beim Speichern und kann bei großen Katalogen zu Timeouts führen. Update by Schedule verarbeitet Änderungen asynchron per Cron, mit leichter Verzögerung, aber responsivem Admin.
2Warum ist der Price Index der teuerste Magento-Indexer?
Er berechnet jede Kombination aus Kundengruppe, Website und aktiven Preisregeln pro Produkt neu, was bei Configurable- und Bundle-Produkten zusätzlich verstärkt wird.
3Wie behebe ich einen hängenden oder invaliden Indexer?
Mit bin/magento indexer:reindex gezielt neu anstoßen. Bei erneutem Abbruch Logs prüfen, Memory-Limit erhöhen und mit SHOW ENGINE INNODB STATUS nach Locks suchen.
4Warum wachsen Mview-Changelog-Tabellen unbegrenzt?
Wenn der Cron-Job seltener läuft als Änderungen anfallen, sammeln sich unverarbeitete Einträge in der _cl-Tabelle. Manueller Cleanup und ein voller Reindex synchronisieren den Status neu.
5Was löst einen ungewollten Full Reindex aus?
Globale Attribut-Änderungen, neue Websites oder Kundengruppen, Schema-Updates sowie ein Modus-Wechsel per indexer:set-mode.
6Wie konfiguriere ich die Batch-Größe für Scheduled Indexing?
Über den saveHandler-Parameter batchSize in der indexer.xml. Für 50.000-200.000 SKUs sind 5.000 bis 10.000 Zeilen ein guter Ausgangswert.
7Was ist der Unterschied zwischen einem Reindex und einem Cache-Flush?
Ein Reindex berechnet Daten wie Preise neu, ein Cache-Flush leert nur bereits berechnete Ausgaben. Ohne aktuellen Index liefert auch ein geleerter Cache veraltete Daten.
8Wie stelle ich die index-Cron-Gruppe auf einen eigenen Prozess?
Über cron/index/use_separate_process. Das entkoppelt lange Reindex-Läufe von der default-Cron-Gruppe, sodass andere Jobs nicht blockiert werden.
9Warum bleibt mein Custom Indexer im Schedule-Modus dauerhaft invalid?
Meist fehlt eine vollständige mview.xml mit allen relevanten Nebentabellen in den subscriptions, wodurch nie ein Changelog-Eintrag entsteht.
10Wie erkenne ich, welcher Indexer die Cron-Laufzeit dominiert?
Über die cron_schedule-Tabelle lässt sich pro Job die Laufzeit auswerten. Meist dominieren Product Price, Category Products und Catalog Search.