Magento 2 MSI und die Tabelle inventory_stock_2 – der stille Killer der Indexierungsleistung

Sehr lange Indexierung in Magento 2

2 Minuten, 47 Sekunden

Magento 2 MSI und die Tabelle inventory_stock_2 – der stille Killer der Indexierungsleistung

Magento 2 MSI und die Tabelle inventory_stock_2 – der stille Killer der Indexierungsleistung

In einem unserer letzten Magento-2-Projekte (ca. 200.000 Produkte, Multi Source Inventory aktiviert) haben wir sehr lange Indexierungszeiten in den Modulen festgestellt:

  • Inventory
  • Product Price
  • Category Products

Alle klassischen Indizes sahen korrekt aus. MariaDB hatte 256 GB RAM, der Buffer Pool war sinnvoll dimensioniert, die EAV-Indizes waren vorhanden, und trotzdem tauchten weiterhin schwere Queries im Zusammenhang mit

inventory_stock_2

auf. Nach einer tieferen Analyse stellte sich heraus, dass nicht die Datenmenge selbst das Problem war, sondern die Art und Weise, wie Magento MSI die Tabelle inventory_stock_* erstellt.


Woher kommt die Tabelle inventory_stock_2?

Die Tabelle inventory_stock_2 erscheint in Magento nach der Aktivierung von Multi Source Inventory (MSI). Magento erstellt für jeden Stock eine separate Tabelle:

  • inventory_stock_1
  • inventory_stock_2
  • inventory_stock_3 ...

Die Tabelle wird dynamisch von der Klasse

Magento\InventoryIndexer\Indexer\IndexStructure

erstellt und sieht ungefähr so aus:

CREATE TABLE inventory_stock_2 (
  sku varchar(64) NOT NULL,
  quantity decimal(12,4) NOT NULL DEFAULT 0.0000,
  is_salable tinyint(1) NOT NULL,
  PRIMARY KEY (sku),
  KEY index_sku_qty (sku, quantity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

Wo liegt das Problem?

Es gibt zwei zentrale Probleme:

1. Fehlendes utf8mb4

Die Spalte sku wird häufig mit utf8_general_ci erstellt, während wir in catalog_product_entity folgendes haben:

utf8mb4_general_ci

Das verursacht:

  • Zeichenkonvertierung bei JOINs
  • Verwendung von Join Buffer
  • schlechtere Query-Pläne

2. Kein Index, der mit is_salable beginnt

Magento führt sehr häufig Abfragen wie diese aus:

WHERE stock_index.is_salable = 0
OR stock_index.is_salable IS NULL

Da der einzige Index jedoch

(sku, quantity)

ist, kann MariaDB nicht effizient nach is_salable filtern.

Ergebnis:

  • vollständige Tabellenscans
  • lange DELETEs beim Price Index
  • lock wait timeout 1205
  • Inventory Indexer bleibt endlos auf "processing"

Warum funktioniert ein manuelles ALTER TABLE nicht?

Weil Magento bei jeder Indexierung Folgendes macht:

  • DROP TABLE inventory_stock_X
  • CREATE TABLE inventory_stock_X

Jede manuelle Änderung verschwindet also wieder.

Deshalb muss die Lösung so aussehen: automatische Anpassung der Tabelle direkt nach ihrer Erstellung.


Lösung: das Modul Kowal_MsiStockFix

Anstatt den Magento-Core zu verändern, erstellen wir ein Plugin, das nach der Erstellung der Tabelle:

  • utf8mb4_general_ci setzt
  • einen Index (is_salable, sku) hinzufügt

Plugin: IndexStructurePlugin.php

<?php
declare(strict_types=1);

namespace Kowal\MsiStockFix\Plugin;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\InventoryIndexer\Indexer\IndexStructure;
use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName;
use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameResolverInterface;

class IndexStructurePlugin
{
    private const CHARSET = 'utf8mb4';
    private const COLLATION = 'utf8mb4_general_ci';

    public function __construct(
        private readonly ResourceConnection $resource,
        private readonly IndexNameResolverInterface $resolver
    ) {}

    public function afterCreate(IndexStructure $subject, $result, IndexName $indexName, string $connectionName)
    {
        $connection = $this->resource->getConnection($connectionName);
        $resolvedName = $this->resolver->resolveName($indexName);
        $table = $this->resource->getTableName($resolvedName);

        if (strpos($table, 'inventory_stock_') === false) {
            return $result;
        }

        if (!$connection->isTableExists($table)) {
            return $result;
        }

        $connection->query(
            sprintf(
                "ALTER TABLE `%s` CONVERT TO CHARACTER SET %s COLLATE %s",
                $table,
                self::CHARSET,
                self::COLLATION
            )
        );

        $indexes = $connection->getIndexList($table);
        if (!isset($indexes['IDX_STOCK_SALABLE_SKU'])) {
            $connection->addIndex(
                $table,
                'IDX_STOCK_SALABLE_SKU',
                ['is_salable', 'sku'],
                AdapterInterface::INDEX_TYPE_INDEX
            );
        }

        return $result;
    }
}

Installation des Moduls

bin/magento module:enable Kowal_MsiStockFix
bin/magento setup:upgrade
bin/magento cache:flush
bin/magento indexer:reindex inventory

Effekte nach der Implementierung

  • Die Collation verschwindet nicht mehr
  • Schnellere DELETEs beim Price Index
  • Kein lock wait timeout mehr
  • Der Inventory Index läuft sauber durch
  • Weniger Join Buffer und weniger Full Scans

Zusammenfassung

Das Problem mit der Tabelle inventory_stock_2 ist wenig offensichtlich, weil zunächst alles "korrekt" aussieht und die Indizes grün sind.

Erst die Analyse von

  • Query-Plänen
  • Collation
  • dem Mechanismus zur Neuerstellung von MSI-Tabellen

führt zur eigentlichen Ursache.

Wenn Sie einen Shop mit sehr vielen Produkten betreiben und MSI nutzen, lohnt sich eine ähnliche Lösung auf jeden Fall.

Magento ist oft schnell. Man muss ihm nur manchmal ein wenig helfen.

Vorherige Nächste