Eigene Indexer in Magento 2 schreiben: IndexerInterface, Mview und indexer.xml
AI generated
M2
di.xml
Magento 2 · Indexer · Mview · Architektur
Eigene Indexer in Magento 2 schreiben
von indexer.xml bis zum vollstaendigen Mview Zyklus

Wer eigene Daten performant durchsuchbar oder aggregierbar machen will, kommt an einem eigenen Indexer nicht vorbei. Statt teure Berechnungen bei jedem Frontend Request neu auszufuehren, verlagert ein eigener Indexer die Arbeit in einen kontrollierten, wiederholbaren Prozess, der genau dann laeuft, wenn sich die zugrunde liegenden Daten aendern.

19 Min. Lesezeit indexer.xml · ActionInterface · Mview · CLI Magento 2.4.x

1. Wann sich ein eigener Indexer wirklich lohnt

Ein eigener Indexer in Magento 2 ist immer dann sinnvoll, wenn eine Berechnung wiederholt gebraucht wird, aber selten neu berechnet werden muss. Klassisches Beispiel: ein aggregierter Lagerbestand ueber mehrere externe Lager, eine berechnete Beliebtheits-Kennzahl fuer Produkte, oder eine denormalisierte Preistabelle fuer ein individuelles B2B Preismodell. Ohne Indexer wuerde diese Berechnung bei jedem Seitenaufruf neu laufen, was Antwortzeiten unnoetig verlaengert und die Datenbank unter Last setzt.

Magento selbst zeigt mit den eingebauten Indexern wie catalog_product_price oder catalogsearch_fulltext das Muster, das ein eigener Indexer uebernehmen sollte: Rohdaten liegen in normalisierten Tabellen, ein Indexer verdichtet sie zu einer schnell lesbaren Zieltabelle, und diese Zieltabelle wird im Frontend gelesen statt der Rohdaten. Der Indexer selbst laeuft entweder per Cron im Hintergrund oder synchron beim Speichern, je nach gewaehltem Modus.

Bevor man einen eigenen Indexer baut, lohnt sich die Frage, ob nicht ein einfacherer Mechanismus reicht. Kleine, selten geaenderte Daten kann ein Cache-Tag im Full Page Cache genauso gut abbilden. Ein Indexer lohnt sich erst, wenn die Berechnung selbst teuer ist, wenn viele Datensaetze betroffen sind, oder wenn ein inkrementeller Reindex bei einzelnen Aenderungen (Mview) einen echten Performance-Vorteil gegenueber vollstaendiger Neuberechnung bringt.

2. IndexerInterface und ActionInterface im Ueberblick

Ein eigener Indexer besteht in Magento 2 aus mehreren zusammenspielenden Teilen. Das Magento\Framework\Indexer\IndexerInterface ist die aeussere Fassade, die von bin/magento indexer:reindex und dem Admin Grid angesprochen wird. Die eigentliche Logik steckt aber nicht in dieser Schnittstelle, sondern in einer separaten Action Klasse, die das Magento\Framework\Indexer\ActionInterface implementiert. Diese Trennung erlaubt es, dieselbe Indexer Logik sowohl fuer vollstaendige als auch fuer partielle Reindex Laeufe wiederzuverwenden.

Zusaetzlich kommt bei inkrementellem Reindex das Magento\Framework\Mview\ActionInterface ins Spiel, das ueber execute(array $ids) nur die betroffenen Entity IDs verarbeitet. Ein eigener Indexer muss also mindestens zwei Interfaces bedienen: eines fuer den kompletten Lauf ueber alle Datensaetze und eines fuer den gezielten Lauf ueber eine ID Liste. Magento generiert daraus automatisch die noetigen Indexer Tabellen und Views, sobald die Deklaration in indexer.xml korrekt ist.

Wichtig ist das Verstaendnis, dass ein eigener Indexer in Magento kein einzelnes PHP Objekt ist, sondern ein Zusammenspiel aus Deklaration, Datenhaltung und Ausfuehrungslogik. Wer nur die Action Klasse schreibt, aber die Deklaration vergisst, bekommt einen Indexer, der weder in der CLI noch im Admin Grid sichtbar ist. Die folgenden Abschnitte bauen diese Teile in der Reihenfolge auf, in der sie beim Entwickeln eines eigenen Indexers tatsaechlich gebraucht werden.

3. Deklaration in indexer.xml

Jeder eigene Indexer beginnt mit einer Deklaration in etc/indexer.xml. Dort wird ein eindeutiger Indexer Code vergeben, die Action Klasse referenziert, ein View fuer den Mview Mechanismus verknuepft und ein sprechender Titel fuer das Admin Grid gesetzt. Der Indexer Code ist der zentrale Identifier, unter dem der Indexer spaeter per CLI angesprochen wird, etwa bin/magento indexer:reindex vendor_pricematrix.

Der indexer Knoten unterstuetzt zusaetzlich ein class Attribut fuer die konkrete Indexer Implementierung sowie optionale Kindknoten fuer title, description und fieldset. Fuer Datenaustausch mit anderen Indexern lassen sich ueber <dependencies> Abhaengigkeiten definieren, sodass ein eigener Indexer zum Beispiel erst nach catalog_product_price laufen kann, wenn er auf dessen Ergebnissen aufbaut.


<?xml version="1.0"?>
<!-- app/code/Vendor/PriceMatrix/etc/indexer.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd">
    <indexer id="vendor_pricematrix"
             view_id="vendor_pricematrix"
             class="Vendor\PriceMatrix\Model\Indexer\PriceMatrixIndexer">
        <title translate="true">B2B Preismatrix</title>
        <description translate="true">Aggregiert Kundengruppen-Preise in eine flache Tabelle</description>
        <!-- Runs only after the core price indexer has finished -->
        <dependencies>
            <indexer id="catalog_product_price"/>
        </dependencies>
    </indexer>
</config>

Parallel dazu wird in etc/mview.xml der View mit derselben view_id definiert, inklusive der Tabellen, deren Aenderungen ueberwacht werden sollen, und der Class Table Observer die daraus Aenderungs IDs ableiten. Ohne diesen View bleibt ein eigener Indexer auf vollstaendige Reindex Laeufe beschraenkt und kann nicht inkrementell aktualisiert werden, auch wenn der Indexer Modus im Admin Grid auf "Bei Speichern aktualisieren" steht.

4. Die Action Klasse: execute, executeFull, executeList, executeRow

Die Action Klasse eines eigenen Indexers implementiert IndexerActionInterface mit vier Methoden: executeFull() fuer den kompletten Neuaufbau, executeList(array $ids) fuer eine Liste betroffener IDs, executeRow($id) fuer einen einzelnen Datensatz und execute($ids) als generische Variante, die intern meist an executeList delegiert. Diese Aufteilung erlaubt es Magento, je nach Trigger die passende Methode aufzurufen: ein manueller indexer:reindex ruft executeFull, ein Speichervorgang im Admin ruft executeRow.

In der Praxis empfiehlt es sich, die eigentliche Datenverarbeitung in eine separate, injizierbare Klasse auszulagern und die Action Klasse selbst schlank zu halten. So kann dieselbe Verarbeitungslogik sowohl von der Action Klasse als auch von einem eigenen CLI Command oder einem Consumer aus der Message Queue wiederverwendet werden. Das reduziert Duplikate und macht den eigenen Indexer leichter testbar, weil die reine Berechnungslogik ohne Framework Abhaengigkeiten unit getestet werden kann.


<?php
declare(strict_types=1);

namespace Vendor\PriceMatrix\Model\Indexer;

use Magento\Framework\Indexer\ActionInterface;
use Magento\Framework\Mview\ActionInterface as MviewActionInterface;
use Vendor\PriceMatrix\Model\ResourceModel\PriceMatrix\PriceCalculator;

/**
 * Custom indexer action building the flattened B2B price matrix table.
 */
class PriceMatrixIndexer implements ActionInterface, MviewActionInterface
{
    /**
     * @param PriceCalculator $priceCalculator Handles the actual price aggregation.
     */
    public function __construct(
        private readonly PriceCalculator $priceCalculator
    ) {
    }

    /**
     * Full reindex, triggered by bin/magento indexer:reindex.
     *
     * @return void
     */
    public function executeFull(): void
    {
        $this->priceCalculator->rebuildAll();
    }

    /**
     * Partial reindex for a list of product IDs (Mview or CLI --id).
     *
     * @param int[] $ids
     * @return void
     */
    public function executeList(array $ids): void
    {
        $this->priceCalculator->rebuildForIds($ids);
    }

    /**
     * Partial reindex for a single product ID, used when saving in Admin.
     *
     * @param int $id
     * @return void
     */
    public function executeRow($id): void
    {
        $this->priceCalculator->rebuildForIds([(int) $id]);
    }

    /**
     * Generic entry point used by the Mview changelog processor.
     *
     * @param int[] $ids
     * @return void
     */
    public function execute($ids): void
    {
        $this->executeList((array) $ids);
    }
}

5. Eigene Index-Tabelle sauber entwerfen

Die Zieltabelle eines eigenen Indexers sollte immer flach und lesefreundlich sein, ohne Joins zur Laufzeit. Das bedeutet in der Praxis: alle Werte, die im Frontend gebraucht werden, liegen bereits denormalisiert in einer Zeile, indiziert auf die Spalten, nach denen tatsaechlich gefiltert wird. Fuer die B2B Preismatrix waere das eine Tabelle mit Produkt ID, Kundengruppe und berechnetem Preis als Primaerschluessel Kombination, ergaenzt um einen Index auf die haeufigste Abfragerichtung.

Ein haeufiger Fehler bei einem eigenen Indexer ist, die Zieltabelle waehrend eines Full Reindex direkt zu leeren und neu zu befuellen. Bei grossen Datenmengen fuehrt das zu einem Zeitfenster, in dem die Tabelle leer oder unvollstaendig ist und das Frontend falsche Ergebnisse liefert. Der etablierte Weg ist eine Replace Tabelle: es wird in eine temporaere Tabelle geschrieben, und erst am Ende per atomarem RENAME TABLE gegen die produktive Tabelle getauscht, genau wie es Magentos eigene Indexer intern handhaben.

Modus Ausloeser Methode Typischer Einsatz
Update on Save Admin Speichern executeRow Kleiner Datenbestand, sofortige Konsistenz noetig
Update by Schedule Cron via Mview executeList Grosser Katalog, Batchverarbeitung erwuenscht
Full Reindex indexer:reindex executeFull Initiale Befuellung, Deploy, Datenreparatur
Save deferred Nach Massenimport executeList Import Pipelines mit gebuendelten IDs

6. Mview anbinden: Changelog-Tabellen verstehen

Mview steht fuer "Materialized View" und ist der Mechanismus, mit dem Magento Aenderungen an Quelltabellen in Changelog Tabellen protokolliert, um sie spaeter inkrementell zu verarbeiten. Sobald ein eigener Indexer in mview.xml registriert ist, legt Magento automatisch eine Tabelle vendor_pricematrix_cl an und haengt Trigger an die ueberwachten Quelltabellen. Jede INSERT, UPDATE oder DELETE Operation auf diesen Tabellen schreibt die betroffene Entity ID in die Changelog Tabelle.

Der eigentliche Reindex Lauf liest dann diese Changelog Tabelle, sammelt die IDs seit dem letzten Versionsstand und ruft executeList mit genau diesen IDs auf. Das ist der Kern des Partial Reindex und der Grund, warum ein eigener Indexer mit korrekt konfiguriertem Mview deutlich schneller reagiert als ein vollstaendiger Neuaufbau. Wichtig ist, alle relevanten Quelltabellen im View zu erfassen, sonst werden Aenderungen an ihnen schlicht nicht erkannt.


<?xml version="1.0"?>
<!-- app/code/Vendor/PriceMatrix/etc/mview.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">
    <view id="vendor_pricematrix" class="Vendor\PriceMatrix\Model\Indexer\PriceMatrixIndexer" group="indexer">
        <subscriptions>
            <table name="catalog_product_entity" entity_column="entity_id"/>
            <table name="customer_group" entity_column="customer_group_id"/>
        </subscriptions>
    </view>
</config>

7. Registrierung ueber di.xml und Dependency Injection

Neben indexer.xml und mview.xml braucht ein eigener Indexer in der Regel Eintraege in etc/di.xml, um die Zieltabelle als Resource zu registrieren und den Indexer optional an bestehende Observer zu haengen, etwa um bei jedem Produkt Speichern eine gezielte Invalidierung auszuloesen. Das Framework bringt dafuer bereits einen Standard Observer Magento\Indexer\Observer\AbstractInvalidateIndexerObserver, der sich mit wenigen Zeilen wiederverwenden laesst.

Ein weiterer wichtiger Baustein ist die Registrierung des Indexers als Consumer fuer Message Queue basierte asynchrone Verarbeitung, falls die Berechnung so aufwendig ist, dass sie nicht mehr synchron im Cron Prozess laufen soll. Fuer die meisten eigenen Indexer reicht jedoch der Standard Mechanismus aus Cron und Mview vollstaendig aus, eine zusaetzliche Message Queue Ebene lohnt sich erst bei sehr hohem Datenvolumen oder externen API Abhaengigkeiten in der Berechnung.

8. CLI Integration, Debugging und Tests

Sobald Deklaration und Action Klasse stehen, erscheint der eigene Indexer automatisch in bin/magento indexer:info und kann einzeln mit bin/magento indexer:reindex vendor_pricematrix angestossen werden. Der Modus laesst sich mit indexer:set-mode schedule vendor_pricematrix oder realtime umschalten. Fuer die Fehlersuche ist indexer:status der erste Anlaufpunkt, um zu sehen, ob ein Indexer als "invalid" markiert ist und einen Reindex braucht.

Beim Debugging eines eigenen Indexer Laufs hilft es, die Changelog Tabelle direkt zu inspizieren, um zu pruefen, ob Trigger tatsaechlich Eintraege erzeugen. Fehlt ein Eintrag nach einer erwarteten Aenderung, liegt meist ein vergessenes <table> im mview.xml vor. Unit Tests fuer die Action Klasse sollten die Berechnungslogik isoliert von der Datenbank testen, waehrend Integration Tests mit echten Fixtures pruefen, ob ein executeFull Lauf tatsaechlich die korrekten Zeilen in der Zieltabelle erzeugt.


# Show all registered indexers, including the custom one
bin/magento indexer:info

# Full reindex for a single custom indexer
bin/magento indexer:reindex vendor_pricematrix

# Switch to scheduled (cron-driven) mode
bin/magento indexer:set-mode schedule vendor_pricematrix

# Check current status: valid, invalid, working
bin/magento indexer:status vendor_pricematrix

# Inspect the changelog table directly for debugging
bin/mysql -e "SELECT * FROM vendor_pricematrix_cl ORDER BY version_id DESC LIMIT 20;"

9. Eigener Indexer im Vergleich zu Alternativen

Nicht jede wiederkehrende Berechnung braucht sofort einen eigenen Indexer. Je nach Datenmenge, Aktualitaetsanforderung und Komplexitaet gibt es Alternativen, die weniger Implementierungsaufwand bedeuten, aber auch weniger Kontrolle ueber den Invalidierungszeitpunkt bieten.

Ansatz Aktualitaet Implementierungsaufwand Wann sinnvoll
Eigener Indexer Kontrolliert, inkrementell Hoch Grosse Datenmengen, teure Berechnung, klare Invalidierungslogik
Cron Job mit eigener Tabelle Fixe Intervalle Mittel Verzoegerung von Minuten akzeptabel, keine Mview Trigger noetig
Cache Tag im FPC Bei jedem Request neu berechnet Niedrig Kleine, guenstige Berechnungen ohne Datenbankzugriff
Observer + Save Event Sofort bei Aenderung Niedrig bis mittel Einzelne Entitaeten, keine Batchverarbeitung noetig

Der entscheidende Vorteil eines eigenen Indexers gegenueber einem simplen Cron Job ist die Anbindung an Mview: Aenderungen werden ereignisbasiert erfasst, statt in festen Intervallen zu pruefen, ob sich ueberhaupt etwas geaendert hat. Das reduziert unnoetige Reindex Laeufe erheblich, insbesondere bei Katalogen, in denen sich nur ein kleiner Teil der Produkte pro Tag aendert. Ein eigener Indexer ist damit fast immer die richtige Wahl, sobald die Datenmenge oder die Berechnungskosten ein einfaches "auf jeder Anfrage neu rechnen" ausschliessen.

Mironsoft

Magento 2 Architektur, Indexer und Performance Engineering

Braucht euer Datenmodell einen eigenen Indexer?

Wir entwerfen, implementieren und testen massgeschneiderte Indexer fuer Magento 2, inklusive Mview Anbindung, Tabellendesign und Monitoring, damit teure Berechnungen nie mehr im Frontend passieren.

Indexer Design

Tabellenstruktur, Invalidierungslogik und Abhaengigkeiten sauber planen

Implementierung

indexer.xml, Action Klassen und Mview Konfiguration produktionsreif umsetzen

Monitoring

Reindex Laufzeiten, Fehlerraten und Invalidierungen dauerhaft im Blick

10. Zusammenfassung

Ein eigener Indexer in Magento 2 besteht aus einer klaren Deklaration in indexer.xml, einer Action Klasse mit den vier Methoden executeFull, executeList, executeRow und execute, einer flachen Zieltabelle mit atomarem Austausch per RENAME TABLE, sowie einer Mview Registrierung fuer inkrementellen Reindex. Wer diese Bausteine sauber trennt, bekommt einen Indexer, der sich nahtlos in die vorhandene CLI, das Admin Grid und den Cron Betrieb einfuegt.

Der groesste Fehler beim Bau eines eigenen Indexers ist, Mview zu ignorieren und stattdessen ausschliesslich auf vollstaendige Reindex Laeufe zu setzen. Das funktioniert bei kleinen Katalogen, skaliert aber nicht, sobald die Datenmenge waechst. Eine saubere Trennung zwischen Berechnungslogik und Framework Integration macht den eigenen Indexer ausserdem testbar und langfristig wartbar, auch wenn sich die zugrunde liegenden Geschaeftsregeln aendern.

Eigene Indexer in Magento 2 schreiben, das Wichtigste auf einen Blick

Deklaration

indexer.xml mit eindeutigem Code, Action Klasse und optionalen Abhaengigkeiten zu anderen Indexern.

Action Klasse

executeFull, executeList und executeRow trennen vollstaendigen und partiellen Reindex sauber.

Mview

Changelog Tabellen erfassen Aenderungen ereignisbasiert und ermoeglichen echten Partial Reindex.

Tabellendesign

Flache Zieltabelle, atomarer Austausch per RENAME TABLE, keine Joins zur Laufzeit.

11. FAQ: Eigene Indexer in Magento 2 schreiben

1Eigener Indexer oder Cron Job?
Bei grosser Datenmenge und ereignisbasierter Invalidierung ist ein eigener Indexer mit Mview klar im Vorteil gegenueber festen Cron Intervallen.
2Welche Interfaces sind Pflicht?
ActionInterface fuer executeFull, executeList, executeRow. Fuer inkrementellen Reindex zusaetzlich das Mview ActionInterface.
3Indexer erscheint nicht in indexer:info?
Deklaration in indexer.xml pruefen, danach cache:flush und setup:upgrade ausfuehren.
4executeList vs. executeRow?
executeRow verarbeitet eine einzelne ID, executeList eine ganze Liste fuer effiziente Batchverarbeitung.
5Wie funktioniert Mview?
Datenbank Trigger schreiben geaenderte IDs in eine Changelog Tabelle, die der Reindex Lauf abarbeitet.
6Zieltabelle jedes Mal leeren?
Nein, temporaere Tabelle nutzen und per atomarem RENAME TABLE austauschen, um Downtime zu vermeiden.
7Abhaengigkeit zu anderem Indexer?
Ja, ueber den dependencies Knoten in indexer.xml, damit die Ausfuehrungsreihenfolge garantiert stimmt.
8Wie testen?
Berechnungslogik isoliert per Unit Test, Integration mit echten Fixtures fuer executeFull und executeList.
9Changelog Tabelle bleibt leer?
Meist fehlt eine Tabelle im subscriptions Block von mview.xml oder der Modus steht noch auf Update on Save.
10Auch fuer kleine Shops sinnvoll?
Selten, bei wenigen hundert Produkten reicht meist ein einfacher Cron Job oder ein Cache Tag im Full Page Cache.