Magento 2 MSI i tabela inventory_stock_2 – cichy killer wydajności indeksacji

Bardzo długa indeksacja w Magento 2

2 minutes, 48 seconds

Magento 2 MSI i tabela inventory_stock_2 – cichy killer wydajności indeksacji

Magento 2 MSI i tabela inventory_stock_2 – cichy killer wydajności indeksacji

W jednym z ostatnich wdrożeń Magento 2 (ok. 200 000 produktów, Multi Source Inventory włączone) zauważyliśmy bardzo długą indeksację modułów:

  • Inventory
  • Product Price
  • Category Products

Wszystkie klasyczne indeksy były poprawne. MariaDB miała 256 GB RAM, buffer pool był sensowny, indeksy EAV dopięte, a mimo to w logach wciąż pojawiały się ciężkie zapytania związane z:

inventory_stock_2

Po głębszej analizie okazało się, że problemem nie była sama ilość danych – tylko sposób, w jaki Magento MSI tworzy tabelę inventory_stock_*.


Skąd bierze się tabela inventory_stock_2?

Tabela inventory_stock_2 pojawia się w Magento po włączeniu Multi Source Inventory (MSI). Dla każdego stocku Magento tworzy osobną tabelę:

  • inventory_stock_1
  • inventory_stock_2
  • inventory_stock_3 ...

Tabela jest tworzona dynamicznie przez klasę:

Magento\InventoryIndexer\Indexer\IndexStructure

i wygląda mniej więcej tak:

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;

Gdzie leży problem?

Są dwa kluczowe problemy:

1️⃣ Brak utf8mb4

Kolumna sku często tworzona jest w utf8_general_ci, podczas gdy w catalog_product_entity mamy:

utf8mb4_general_ci

To powoduje:

  • konwersję znaków przy JOIN
  • użycie join buffer
  • gorsze plany zapytań

2️⃣ Brak indeksu zaczynającego się od is_salable

Magento bardzo często wykonuje zapytania typu:

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

A ponieważ jedyny indeks to:

(sku, quantity)

MariaDB nie może efektywnie filtrować po is_salable.

Efekt?

  • pełne skany tabeli
  • długie DELETE podczas price index
  • lock wait timeout 1205
  • inventory indexer „processing” bez końca

Dlaczego ręczne ALTER TABLE nie działa?

Bo Magento przy każdej indeksacji:

  • DROP TABLE inventory_stock_X
  • CREATE TABLE inventory_stock_X

Każda ręczna zmiana znika.

Dlatego rozwiązaniem musi być: automatyczna modyfikacja tabeli po jej utworzeniu.


Rozwiązanie: moduł Kowal_MsiStockFix

Zamiast modyfikować core Magento, tworzymy plugin, który po utworzeniu tabeli:

  • ustawi utf8mb4_general_ci
  • doda indeks (is_salable, sku)

Struktura modułu

app/code/Kowal/MsiStockFix/
├── registration.php
├── etc/
│   ├── module.xml
│   └── di.xml
└── Plugin/
    └── IndexStructurePlugin.php

registration.php

<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Kowal_MsiStockFix',
    __DIR__
);

etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Kowal_MsiStockFix" setup_version="1.0.0"/>
</config>

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">

    <type name="Magento\InventoryIndexer\Indexer\IndexStructure">
        <plugin name="kowal_msi_stock_fix_index_structure"
                type="Kowal\MsiStockFix\Plugin\IndexStructurePlugin"
                sortOrder="10"/>
    </type>

</config>

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;
        }

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

        // 2. Dodaj indeks (is_salable, sku)
        $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;
    }
}

Instalacja modułu

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

Efekty po wdrożeniu

  • Brak znikającej collation
  • Szybsze DELETE z price index
  • Brak lock wait timeout
  • Inventory index kończy się poprawnie
  • Mniej join buffer i full scanów

Podsumowanie

Problem z tabelą inventory_stock_2 jest mało oczywisty, bo wszystko wygląda „poprawnie”, a indeksy są zielone.

Dopiero analiza:

  • planów zapytań
  • collation
  • mechanizmu recreate tabel MSI

pozwala znaleźć prawdziwe źródło problemu.

Jeżeli masz sklep z dużą liczbą produktów i korzystasz z MSI – warto wdrożyć podobne rozwiązanie.

Magento często jest szybkie. Trzeba mu tylko trochę pomóc 😉

Previous Next