Magento 2 MSI e la tabella inventory_stock_2 – il killer silenzioso delle prestazioni di indicizzazione

Indicizzazione molto lunga in Magento 2

2 minuti, 49 secondi

Magento 2 MSI e la tabella inventory_stock_2 – il killer silenzioso delle prestazioni di indicizzazione

Magento 2 MSI e la tabella inventory_stock_2 – il killer silenzioso delle prestazioni di indicizzazione

In una delle nostre recenti implementazioni Magento 2 (circa 200.000 prodotti, Multi Source Inventory abilitato) abbiamo notato tempi di indicizzazione molto lunghi nei moduli:

  • Inventory
  • Product Price
  • Category Products

Tutti gli indici classici sembravano corretti. MariaDB aveva 256 GB di RAM, il buffer pool era adeguato, gli indici EAV erano presenti eppure continuavano a comparire query pesanti legate a:

inventory_stock_2

Dopo un’analisi più approfondita si è scoperto che il problema non era la quantità di dati in sé, ma il modo in cui Magento MSI crea la tabella inventory_stock_*.


Da dove arriva la tabella inventory_stock_2?

La tabella inventory_stock_2 compare in Magento dopo l’attivazione di Multi Source Inventory (MSI). Magento crea una tabella separata per ogni stock:

  • inventory_stock_1
  • inventory_stock_2
  • inventory_stock_3 ...

La tabella viene creata dinamicamente dalla classe:

Magento\InventoryIndexer\Indexer\IndexStructure

e ha più o meno questa struttura:

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;

Dov’è il problema?

Ci sono due problemi chiave:

1. Mancanza di utf8mb4

La colonna sku viene spesso creata in utf8_general_ci, mentre in catalog_product_entity troviamo:

utf8mb4_general_ci

Questo provoca:

  • conversione di caratteri durante i JOIN
  • uso del join buffer
  • piani di query peggiori

2. Nessun indice che inizi con is_salable

Magento esegue molto spesso query del tipo:

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

E poiché l’unico indice è:

(sku, quantity)

MariaDB non riesce a filtrare in modo efficiente per is_salable.

Risultato:

  • full table scan
  • DELETE molto lunghi durante il price index
  • lock wait timeout 1205
  • inventory indexer in "processing" senza fine

Perché un ALTER TABLE manuale non funziona?

Perché Magento ad ogni indicizzazione esegue:

  • DROP TABLE inventory_stock_X
  • CREATE TABLE inventory_stock_X

Ogni modifica manuale scompare.

Per questo la soluzione deve essere: modificare automaticamente la tabella dopo la sua creazione.


Soluzione: modulo Kowal_MsiStockFix

Invece di modificare il core di Magento, creiamo un plugin che, dopo la creazione della tabella:

  • imposta utf8mb4_general_ci
  • aggiunge un indice (is_salable, sku)

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

Installazione del modulo

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

Effetti dopo l’implementazione

  • La collation non scompare più
  • DELETE più rapidi nel price index
  • Niente più lock wait timeout
  • L’inventory index termina correttamente
  • Meno join buffer e meno full scan

Riepilogo

Il problema con la tabella inventory_stock_2 non è immediato da vedere, perché tutto sembra "corretto" e gli indici risultano verdi.

Solo l’analisi di:

  • query plan
  • collation
  • meccanismo di ricreazione delle tabelle MSI

permette di trovare la vera causa del problema.

Se hai uno store con molti prodotti e usi MSI, vale la pena implementare una soluzione simile.

Magento spesso è veloce. Bisogna solo dargli una piccola mano.

Previous Next