Redis für Indexer-Lock-Koordination in Magento nutzen
AI generated
SET
TTL
Redis / Magento Indexer
Redis für Indexer-Lock-Koordination in Magento
verteilte Locks für Multi-Server-Cron-Setups

Sobald ein Magento-Shop auf mehrere Applikationsserver skaliert und Cron-Jobs auf jedem dieser Server laufen, kann derselbe Indexer theoretisch gleichzeitig von zwei Prozessen angestossen werden. Magentos eingebautes, datenbankbasiertes Locking verhindert das innerhalb einer einzelnen MySQL-Instanz zuverlässig, stößt aber an praktische Grenzen, sobald Koordination über mehrere unabhängige Cron-Worker mit eigener Prozessverwaltung hinweg nötig ist, etwa bei containerisierten Deployments mit mehreren gleichwertigen Worker-Pods. Redis bietet sich als schnelle, bereits vorhandene Infrastrukturkomponente an, um verteilte Locks nach dem Prinzip des Redlock-Algorithmus umzusetzen und damit Indexer-Kollisionen zuverlässig zu vermeiden.

12 Min. Lesezeit Verteilte Locks Redlock-Prinzip Multi-Server-Cron

1. Wie parallel laufende Indexer-Prozesse sich gegenseitig blockieren können

Magentos Indexer arbeiten typischerweise mit einer Versionstabelle und einer Changelog-Tabelle je Indexer, aus denen ein Reindex-Lauf seine zu verarbeitenden Datensätze ermittelt. Starten zwei Prozesse denselben Indexer nahezu gleichzeitig, etwa weil ein manuell ausgelöster bin/magento indexer:reindex mit einem regulären Cron-Lauf kollidiert, können beide Prozesse denselben Datenbestand parallel verarbeiten. Im günstigen Fall bedeutet das nur doppelte Arbeit und verschwendete CPU-Zeit, im ungünstigen Fall können sich beide Prozesse gegenseitig bei Schreiblocks auf denselben Zeilen blockieren und Deadlocks in der Datenbank auslösen.

Besonders kritisch wird es bei Full-Reindex-Läufen, die temporäre Tabellen anlegen und am Ende per atomarem Tabellentausch die produktive Indextabelle ersetzen. Laufen hier zwei Prozesse parallel, kann der zeitlich zweite Tabellentausch das Ergebnis des ersten überschreiben oder mit noch laufenden Lesezugriffen kollidieren, was im schlimmsten Fall zu inkonsistenten oder unvollständigen Indexdaten führt, die erst bei der nächsten erfolgreichen Reindexierung wieder korrigiert werden.

2. Magentos bestehender Locking-Mechanismus und seine Grenzen

Seit Magento 2.3.4 sichert der Reindex-Prozess sich selbst gegen parallele Ausführung ab, indem er vor dem eigentlichen Lauf über LockManagerInterface einen Lock mit einem Namen wie indexer_reindex_<indexerId> setzt und diesen erst nach Abschluss wieder freigibt. Die Standardimplementierung dieses Interfaces nutzt MySQLs GET_LOCK()-Funktion, die einen benannten Lock innerhalb derselben Datenbankverbindung hält und automatisch freigibt, sobald die Verbindung endet.

Dieser Mechanismus funktioniert zuverlässig, solange alle beteiligten Prozesse gegen dieselbe MySQL-Instanz sprechen, was in Magento-Setups praktisch immer der Fall ist. Die eigentliche Grenze liegt nicht in der Datenbank selbst, sondern in der Prozesskoordination drumherum: Bei containerisierten oder horizontal skalierten Deployments mit mehreren, unabhängig laufenden Cron-Workern kann es sinnvoll sein, die Lock-Logik unabhängig von einer dauerhaft gehaltenen MySQL-Session zu betreiben, etwa um Verbindungs-Timeouts oder Connection-Pooling-Eigenheiten aus dem Spiel zu nehmen.

3. Redis als verteilter Lock-Provider: das SET NX PX Muster

Ein verteilter Lock in Redis lässt sich mit dem Befehl SET key value NX PX ttl in einer einzigen atomaren Operation umsetzen: NX sorgt dafür, dass der Schlüssel nur gesetzt wird, wenn er noch nicht existiert, und PX vergibt eine Ablaufzeit in Millisekunden, damit ein abgestürzter Prozess den Lock nicht auf ewig blockiert. Der gesetzte Wert sollte eine eindeutige Prozess- oder Request-ID enthalten, damit beim Freigeben zweifelsfrei geprüft werden kann, dass tatsächlich der eigene Lock gelöscht wird und nicht versehentlich der eines anderen, inzwischen neu gestarteten Prozesses.

Das Freigeben des Locks muss deshalb atomar Wert prüfen und löschen, was sich am zuverlässigsten über ein kleines Lua-Skript mit EVAL umsetzen lässt, da eine separate GET-dann-DEL-Sequenz in PHP eine Race Condition zwischen Prüfung und Löschung offen liesse.


<?php
declare(strict_types=1);

namespace Mironsoft\IndexerLock\Model;

use Redis;

/**
 * Setzt und entfernt verteilte Indexer-Locks in Redis nach dem
 * SET NX PX Muster mit atomarer, wertgeprüfte Freigabe.
 */
class RedisIndexerLock
{
    private const LOCK_TTL_MS = 300000;

    /**
     * @param Redis $redis Verbundener phpredis-Client.
     */
    public function __construct(private readonly Redis $redis)
    {
    }

    /**
     * Versucht, einen Lock für den angegebenen Indexer zu setzen.
     *
     * @param string $indexerId ID des zu sperrenden Indexers.
     * @param string $ownerToken Eindeutige Kennung des sperrenden Prozesses.
     * @return bool True, wenn der Lock erfolgreich gesetzt wurde.
     */
    public function acquire(string $indexerId, string $ownerToken): bool
    {
        $key = sprintf('indexer_lock:%s', $indexerId);

        return (bool) $this->redis->set($key, $ownerToken, ['NX', 'PX' => self::LOCK_TTL_MS]);
    }

    /**
     * Gibt einen Lock nur frei, wenn der Aufrufer noch dessen Eigentümer ist.
     *
     * @param string $indexerId ID des zu entsperrenden Indexers.
     * @param string $ownerToken Eindeutige Kennung des sperrenden Prozesses.
     * @return bool True, wenn der Lock tatsächlich entfernt wurde.
     */
    public function release(string $indexerId, string $ownerToken): bool
    {
        $key = sprintf('indexer_lock:%s', $indexerId);
        $script = <<<'LUA'
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
end
return 0
LUA;

        return (bool) $this->redis->eval($script, [$key, $ownerToken], 1);
    }
}

4. Integration als LockManagerInterface-Preference statt Wrapper-Skript

Statt den Redis-Lock nur in einem externen Bash-Wrapper vor dem eigentlichen Reindex-Aufruf zu prüfen, lässt sich die eigene Implementierung auch direkt als Ersatz für Magentos LockManagerInterface registrieren. Dazu wird die Standard-Preference in di.xml auf die eigene Redis-Klasse umgebogen, wodurch nicht nur der manuell aufgerufene Reindex, sondern jeder interne Aufruf, der intern bereits auf das Interface setzt, automatisch über Redis statt über MySQLs GET_LOCK() koordiniert wird, ohne dass an anderer Stelle im Code etwas geändert werden muss.

Dieser Ansatz ist saubererer als ein separates Wrapper-Skript, weil er innerhalb der bestehenden Magento-Architektur bleibt und automatisch für alle Aufrufer greift, die gegen das Interface programmieren, nicht nur für manuell aufgerufene CLI-Befehle. Wichtig ist dabei, dass die eigene Implementierung exakt die Methoden lock(), unlock() und isLocked() des Interfaces bedient, damit bestehender Magento-Kerncode ohne Anpassung weiterläuft.


<!-- app/code/Mironsoft/IndexerLock/etc/di.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Framework\Lock\LockManagerInterface"
                type="Mironsoft\IndexerLock\Model\RedisLockManager" />
</config>

5. Das Redlock-Prinzip und wann es tatsächlich relevant wird

Der von Redis-Erfinder Salvatore Sanfilippo vorgeschlagene Redlock-Algorithmus adressiert ein Problem, das bei einem einzelnen Redis-Knoten nicht auftritt, aber relevant wird, sobald mehrere unabhängige Redis-Instanzen für Hochverfügbarkeit im Einsatz sind: Ein Lock gilt erst dann als erfolgreich erworben, wenn er auf einer Mehrheit der Instanzen, etwa drei von fünf, innerhalb eines engen Zeitfensters gesetzt werden konnte. Das schützt davor, dass ein einzelner, isolierter oder failovernder Knoten fälschlich einen Lock als gültig ausgibt, obwohl ein anderer Prozess ihn auf den übrigen Knoten bereits hält.

Für die meisten Magento-Installationen mit einer einzelnen Redis-Instanz oder einem einfachen Primary-Replica-Setup ohne mehrfach unabhängige Schreibknoten ist der volle Redlock-Algorithmus überdimensioniert. Relevant wird er vor allem bei Redis Cluster oder Sentinel-Topologien mit mehreren potenziell gleichzeitig schreibbaren Knoten, bei denen ein Failover mitten in einer Lock-Operation sonst zu widersprüchlichen Lock-Zuständen führen könnte.

6. Praxisbeispiel: mehrere Cron-Worker auf verschiedenen Servern koordinieren

In einem typischen skalierten Magento-Deployment laufen mehrere Applikationsserver oder Container hinter einem Load Balancer, wobei aus historischen oder Verfügbarkeitsgründen auf jedem Knoten ein eigener Cron-Prozess aktiv ist, statt Cron zentral auf nur einem dedizierten Knoten zu betreiben. Ohne zusätzliche Koordination würde in diesem Szenario derselbe Indexer-Reindex-Job auf jedem Server parallel angestossen, sobald die Cron-Zeit erreicht ist.

Ein Redis-basierter Lock, der vor dem eigentlichen Reindex-Aufruf in einem Wrapper-Skript oder Custom Command geprüft wird, sorgt dafür, dass nur der Prozess, der den Lock zuerst erwirbt, den Indexer tatsächlich ausführt, während alle anderen Cron-Instanzen den Lauf für diesen Zyklus überspringen. Das reduziert nicht nur das Kollisionsrisiko, sondern spart auch unnötige, redundante Rechenzeit auf den übrigen Servern.


#!/bin/bash
# Wrapper-Skript: Reindex nur ausführen, wenn dieser Server den Lock erhält
LOCK_KEY="indexer_lock:catalog_product_price"
OWNER="$(hostname)-$$"

ACQUIRED=$(redis-cli SET "$LOCK_KEY" "$OWNER" NX PX 300000)

if [ "$ACQUIRED" = "OK" ]; then
    bin/magento indexer:reindex catalog_product_price
    redis-cli EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0" 1 "$LOCK_KEY" "$OWNER"
else
    echo "Reindex läuft bereits auf einem anderen Knoten, überspringe."
fi

7. Lock-Timeout und Deadlock-Vermeidung richtig dimensionieren

Die Wahl der Lock-TTL ist ein Balanceakt: Ist sie zu kurz gewählt, kann ein noch laufender, legitimer Reindex-Prozess seinen eigenen Lock verlieren, bevor er fertig ist, sodass ein zweiter Prozess parallel startet, obwohl der erste noch aktiv arbeitet. Ist sie dagegen zu lang gewählt, blockiert ein abgestürzter oder hängen gebliebener Prozess den Indexer für unnötig lange Zeit, selbst wenn er selbst längst nicht mehr existiert.

Eine robuste Lösung setzt die initiale TTL großzügig genug für die längste realistische Laufzeit des jeweiligen Indexers und verlängert den Lock zusätzlich periodisch während der Ausführung über einen separaten Heartbeat-Mechanismus, solange der Prozess nachweislich noch aktiv ist. Auf diese Weise läuft ein legitimer, länger dauernder Reindex nie in eine vorzeitige Lock-Freigabe, während ein tatsächlich abgestürzter Prozess trotzdem innerhalb einer klar definierten oberen Zeitgrenze automatisch freigegeben wird.

8. Monitoring von Lock-Contention im laufenden Betrieb

Um zu erkennen, wie häufig verschiedene Cron-Worker tatsächlich um denselben Lock konkurrieren, lohnt sich ein einfaches Logging bei jedem fehlgeschlagenen Lock-Versuch, inklusive Zeitstempel, Hostname und betroffenem Indexer. Häuft sich diese Contention für einen bestimmten Indexer auffällig, ist das ein Hinweis darauf, dass entweder die Cron-Zeitpläne der beteiligten Server zu eng getaktet sind oder der Reindex-Lauf selbst zu lange dauert und die nächste geplante Ausführung bereits überlappt.

Zusätzlich lässt sich über redis-cli TTL indexer_lock:<name> jederzeit prüfen, ob ein Lock aktuell gehalten wird und wie lange er noch gültig ist, was bei der Fehlersuche im laufenden Betrieb schnelle Klarheit über den tatsächlichen Zustand verschafft, ohne Log-Dateien durchsuchen zu müssen.

9. Grenzen: Redis als Single Point of Failure und wann Redlock übertrieben ist

Wird die Lock-Koordination vollständig über Redis abgewickelt, wird die Redis-Instanz selbst zu einer kritischen Komponente: Fällt sie aus, können keine neuen Locks mehr erworben werden, und je nach Implementierung blockiert das im schlimmsten Fall auch reguläre Reindex-Läufe, die ohne funktionierende Lock-Koordination sicherheitshalber gar nicht erst starten sollten. Ein sinnvoller Fallback ist, bei nicht erreichbarem Redis auf das bestehende, datenbankbasierte Locking von Magento zurückzufallen, statt den gesamten Indexer-Betrieb von der Verfügbarkeit einer zusätzlichen Komponente abhängig zu machen.

Für die große Mehrheit der Magento-Installationen mit einer einzelnen produktiven Redis-Instanz reicht das einfache SET-NX-PX-Muster vollständig aus, und der vollständige Redlock-Algorithmus mit mehreren unabhängigen Instanzen ist unnötiger Mehraufwand. Er lohnt sich nur, wenn die Redis-Infrastruktur ohnehin bereits als Cluster mit mehreren, potenziell gleichzeitig schreibbaren Knoten betrieben wird und diese Komplexität nicht zusätzlich für das Locking eingeführt werden muss.

Mechanismus Magento LockManager (DB) Redis SET NX PX Redlock (Multi-Knoten)
Basis MySQL GET_LOCK() pro Verbindung Atomare Redis-Operation Mehrheit mehrerer Redis-Knoten
Skalierung Eine gemeinsame MySQL-Instanz Eine Redis-Instanz oder Replica-Set Mehrere unabhängige Knoten
Ausfallsicherheit An MySQL-Verfügbarkeit gebunden An Redis-Verfügbarkeit gebunden Toleriert Ausfall einzelner Knoten
Komplexität Bereits vorhanden, kein Zusatzaufwand Gering, ein Lua-Skript genügt Hoch, eigene Bibliothek empfohlen
Typischer Einsatz Standard für Single-DB-Setups Multi-Server-Cron mit einem Redis Redis Cluster mit mehreren Schreibknoten

Mironsoft

Cache-Layer-Setup und Magento-Redis-Integration

Magento-Cache, der nicht richtig greift oder falsch konfiguriert ist?

Wir richten Redis als Cache- und Session-Backend für Magento sauber ein, tunen Speicherverbrauch und Eviction-Strategien und sorgen dafür, dass Full Page Cache und Session-Storage zuverlässig zusammenspielen.

Redis-Setup

Cache-, Session- und FPC-Backend produktionsreif für Magento konfigurieren.

Memory-Tuning

Speicherverbrauch und Eviction-Policies auf die tatsächliche Shop-Last abstimmen.

High-Availability-Setup

Redis Sentinel oder Cluster für ausfallsichere Magento-Umgebungen einrichten.

10. Zusammenfassung

Redis-Indexer-Locks in Magento: Das Wichtigste auf einen Blick

Problem

Parallele Indexer-Läufe auf mehreren Cron-Workern können sich blockieren oder inkonsistente Indexdaten erzeugen, besonders bei Multi-Server-Deployments.

Lösung

Ein Redis-basierter Lock nach dem SET NX PX Muster mit atomarer, wertgeprüfte Freigabe verhindert parallele Ausführung über Server-Grenzen hinweg.

Redlock-Einordnung

Der volle Redlock-Algorithmus lohnt sich nur bei mehreren unabhängigen Redis-Knoten, für eine einzelne Instanz reicht das einfache Muster.

Grenzen

Redis wird selbst zur kritischen Komponente; ein Fallback auf Magentos DB-Locking bei Redis-Ausfall verhindert einen zusätzlichen Single Point of Failure.

11. FAQ: Redis-Indexer-Locks in Magento: Das Wichtigste auf einen Blick

1Warum können sich parallele Indexer-Läufe in Magento gegenseitig blockieren?
Zwei gleichzeitig gestartete Prozesse für denselben Indexer können auf denselben Zeilen Schreiblocks anfordern und sich gegenseitig blockieren, im schlimmsten Fall inklusive Deadlocks in der Datenbank.
2Sichert Magento Indexer-Läufe nicht bereits selbst ab?
Seit Magento 2.3.4 setzt der Reindex-Prozess über LockManagerInterface einen datenbankbasierten Lock, der auf MySQLs GET_LOCK-Funktion aufbaut und innerhalb einer Instanz zuverlässig funktioniert.
3Wo stößt das bestehende DB-Locking an Grenzen?
Bei containerisierten oder horizontal skalierten Setups mit mehreren unabhängigen Cron-Workern kann es sinnvoll sein, die Lock-Logik unabhängig von einer dauerhaft gehaltenen MySQL-Session zu betreiben.
4Wie funktioniert ein verteilter Lock mit Redis grundsätzlich?
Über den atomaren Befehl SET key value NX PX ttl wird ein Schlüssel nur gesetzt, wenn er nicht bereits existiert, mit automatischem Ablauf nach der angegebenen Zeit in Millisekunden.
5Warum reicht eine einfache GET-dann-DEL-Sequenz zum Freigeben nicht aus?
Zwischen dem Prüfen und dem Löschen könnte ein anderer Prozess den Lock inzwischen neu erworben haben, was ohne atomare Prüfung zu einer Race Condition führt. Ein Lua-Skript per EVAL löst das.
6Was ist der Redlock-Algorithmus?
Ein von Salvatore Sanfilippo vorgeschlagenes Verfahren, das einen Lock erst als gültig betrachtet, wenn er auf einer Mehrheit mehrerer unabhängiger Redis-Knoten innerhalb eines engen Zeitfensters gesetzt werden konnte.
7Wann ist Redlock für Magento-Setups tatsächlich relevant?
Vor allem bei Redis Cluster oder Sentinel-Topologien mit mehreren potenziell gleichzeitig schreibbaren Knoten, nicht bei einer einzelnen produktiven Redis-Instanz.
8Wie sollte die Lock-TTL für Indexer-Locks gewählt werden?
Großzügig genug für die längste realistische Laufzeit, idealerweise ergänzt um einen periodischen Heartbeat, der den Lock während eines noch aktiven Laufs verlängert.
9Was passiert, wenn Redis für die Lock-Koordination ausfällt?
Ohne Fallback können keine neuen Locks erworben werden. Ein sinnvoller Fallback ist der Rücksprung auf Magentos bestehendes, datenbankbasiertes Locking bei nicht erreichbarem Redis.
10Lohnt sich der volle Redlock-Algorithmus für die meisten Magento-Shops?
Nein, für eine einzelne Redis-Instanz oder ein einfaches Primary-Replica-Setup reicht das einfache SET-NX-PX-Muster vollständig aus.