Magento 2 MSI and the inventory_stock_2 table – the silent killer of indexing performance

Very long indexing in Magento 2

3 minutes, 1 second

Magento 2 MSI and the inventory_stock_2 table – the silent killer of indexing performance

Magento 2 MSI and the inventory_stock_2 table – the silent killer of indexing performance

In one of our recent Magento 2 implementations (approx. 200,000 products, Multi Source Inventory enabled) we noticed very long module indexing times:

  • Inventory
  • Product Price
  • Category Products

All classic indexes were correct. MariaDB had 256 GB of RAM, the buffer pool was reasonable, EAV indexes were attached, and yet heavy queries related to:

inventory_stock_2

After a deeper analysis, it turned out that the problem was not the amount of data itself, but the way Magento MSI creates the inventory_stock_* table.


Where does the inventory_stock_2 table come from?

The inventory_stock_2 table appears in Magento after enabling Multi Source Inventory (MSI). Magento creates a separate table for each stock:

  • inventory_stock_1
  • inventory_stock_2
  • inventory_stock_3 ...

The table is created dynamically by the class:

Magento\InventoryIndexer\Indexer\IndexStructure

and looks something like this:

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;

What is the problem?

There are two key problems:

1️⃣ Lack of utf8mb4

The sku column is often created in utf8_general_ci, while in catalog_product_entity we have:

utf8mb4_general_ci

This causes:

  • character conversion during JOIN
  • use of join buffer
  • worse query plans

2️⃣ No index starting with is_salable

Magento very often performs queries such as:

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

And since the only index is:

(sku, quantity)

MariaDB cannot effectively filter by is_salable.

The result?

  • full table scans
  • long DELETE during price index
  • lock wait timeout 1205
  • inventory indexer "processing" endlessly

Why does manual ALTER TABLE not work?

Because Magento does the following during each indexing:

  • DROP TABLE inventory_stock_X
  • CREATE TABLE inventory_stock_X

Every manual change disappears.

Therefore, the solution must be: automatic modification of the table after its creation.


Solution: Kowal_MsiStockFix module

Instead of modifying the Magento core, we create a plugin that, after creating the table:

  • sets utf8mb4_general_ci
  • adds an index (is_salable, sku)

Module structure

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

Module installation

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

Effects after implementation

  • No disappearing collation
  • Faster DELETE with price index
  • No lock wait timeout
  • Inventory index ends correctly
  • Fewer join buffers and full scans

Summary

The problem with the inventory_stock_2 table is not obvious, because everything looks "correct" and the indexes are green.

Only analysis of:

  • query plans
  • collation
  • the MSI table recreate mechanism

allows you to find the real source of the problem.

If you have a store with a large number of products and use MSI, it is worth implementing a similar solution.

Magento is often fast. It just needs a little help 😉

Previous Next