Magento 2 MSI y la tabla inventory_stock_2 – el asesino silencioso del rendimiento de indexación

Indexación muy larga en Magento 2

3 minutos, 0 segundos

Magento 2 MSI y la tabla inventory_stock_2 – el asesino silencioso del rendimiento de indexación

Magento 2 MSI y la tabla inventory_stock_2 – el asesino silencioso del rendimiento de indexación

En una de nuestras implementaciones recientes de Magento 2 (aprox. 200 000 productos, Multi Source Inventory activado) detectamos tiempos de indexación muy largos en los módulos:

  • Inventory
  • Product Price
  • Category Products

Todos los índices clásicos parecían correctos. MariaDB tenía 256 GB de RAM, el buffer pool era razonable, los índices EAV estaban bien configurados y, aun así, seguían apareciendo consultas pesadas relacionadas con:

inventory_stock_2

Después de un análisis más profundo vimos que el problema no era el volumen de datos en sí, sino la forma en que Magento MSI crea la tabla inventory_stock_*.


¿De dónde sale la tabla inventory_stock_2?

La tabla inventory_stock_2 aparece en Magento después de activar Multi Source Inventory (MSI). Magento crea una tabla separada para cada stock:

  • inventory_stock_1
  • inventory_stock_2
  • inventory_stock_3 ...

La tabla se crea dinámicamente mediante la clase:

Magento\InventoryIndexer\Indexer\IndexStructure

y tiene un aspecto similar a este:

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;

¿Dónde está el problema?

Hay dos problemas clave:

1. Falta de utf8mb4

La columna sku suele crearse con utf8_general_ci, mientras que en catalog_product_entity tenemos:

utf8mb4_general_ci

Esto provoca:

  • conversión de caracteres en los JOIN
  • uso de join buffer
  • planes de consulta peores

2. Falta de un índice que empiece por is_salable

Magento ejecuta con mucha frecuencia consultas del tipo:

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

Y como el único índice existente es:

(sku, quantity)

MariaDB no puede filtrar eficazmente por is_salable.

Resultado:

  • escaneos completos de tabla
  • DELETE largos durante el índice de precio
  • lock wait timeout 1205
  • inventory indexer en estado "processing" indefinidamente

¿Por qué no funciona un ALTER TABLE manual?

Porque Magento hace esto en cada indexación:

  • DROP TABLE inventory_stock_X
  • CREATE TABLE inventory_stock_X

Cualquier cambio manual desaparece.

Por eso la solución debe ser: modificar la tabla automáticamente después de crearla.


Solución: módulo Kowal_MsiStockFix

En lugar de modificar el core de Magento, creamos un plugin que, después de crear la tabla:

  • establece utf8mb4_general_ci
  • añade el índice (is_salable, sku)

Estructura del módulo

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

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

Instalación del módulo

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

Efectos después de la implementación

  • La collation deja de desaparecer
  • DELETE más rápidos en el índice de precio
  • Sin lock wait timeout
  • El índice de inventory finaliza correctamente
  • Menos join buffer y menos full scans

Resumen

El problema con la tabla inventory_stock_2 no es obvio, porque todo parece "correcto" y los índices se ven en verde.

Solo el análisis de:

  • los planes de consulta
  • la collation
  • el mecanismo de recreación de tablas MSI

permite encontrar la causa real.

Si tienes una tienda con muchos productos y utilizas MSI, merece la pena aplicar una solución similar.

Magento a menudo es rápido. Solo hay que darle una pequeña ayuda.

Anterior Siguiente