Operating Indexers

Reading indexer:status, choosing between reindex and reset, and rescuing an index left stuck in Processing.

Syntaxbin/magento indexer:reset [indexer ...]

Four commands cover nearly all day-to-day work. indexer:info lists the ids. indexer:status prints one row per indexer. indexer:reindex [id ...] rebuilds fully, or rebuilds everything when given no id. indexer:reset [id ...] marks an index invalid and builds nothing.

Reading the status

The Status column shows Ready for a valid index, Reindex required for an invalid one, Processing while a rebuild holds the lock, and Suspended. Update On shows Save or Schedule. In schedule mode the Schedule Status column adds the mview state and how many changelog rows are waiting.

The index that never recovers

A reindex killed partway through, by an out-of-memory kill, a deploy or a closed SSH session, leaves the status at Processing. indexer:reindex then refuses it with "Product Price index is locked by another reindex process. Skipping.", and the cron job indexer_reindex_all_invalid passes over it because that job only picks up indexers whose status is invalid. Nothing fixes itself. Run bin/magento indexer:reset catalog_product_price to mark it invalid, then reindex.

What the merchant sees

System > Tools > Index Management shows the same statuses. Its Actions menu only changes the mode; Open Source has no reindex button there. While an index is invalid the storefront keeps serving the previous index, so a merchant sees stale prices rather than an error, and will not report it as one.

Example

Example · php
<?php
// app/code/Vendor/Ops/Console/Command/AssertIndexersReady.php

declare(strict_types=1);

namespace Vendor\Ops\Console\Command;

use Magento\Framework\Console\Cli;
use Magento\Framework\Indexer\ConfigInterface;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\Indexer\StateInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class AssertIndexersReady extends Command
{
    public function __construct(
        private readonly ConfigInterface $indexerConfig,
        private readonly IndexerRegistry $indexerRegistry,
        ?string $name = null
    ) {
        parent::__construct($name);
    }

    protected function configure(): void
    {
        $this->setName('vendor:index:assert-ready')
            ->setDescription('Fails if any indexer is not valid');

        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $stale = [];

        foreach (array_keys($this->indexerConfig->getIndexers()) as $indexerId) {
            $status = $this->indexerRegistry->get($indexerId)->getStatus();

            if ($status !== StateInterface::STATUS_VALID) {
                $stale[] = $indexerId . ': ' . $status;
                $output->writeln($indexerId . ': ' . $status);
            }
        }

        return $stale === [] ? Cli::RETURN_SUCCESS : Cli::RETURN_FAILURE;
    }
}

// bin/magento vendor:index:assert-ready
// catalog_product_price: working
// catalogsearch_fulltext: invalid
// echo $?
// 1

When to use it

  • Blocking a deployment when any indexer is invalid.
  • Recovering a price index left in Processing by a reindex the kernel killed.
  • Answering a merchant who reports that Index Management is showing red.
  • Reindexing one indexer on a live store without rebuilding all of them.

More examples

Rescuing an index stuck in Processing

This is the whole fix, and it is worth recognising from those two lines of output: indexer:reindex catches the lock as an error, so it prints its generic "process error during indexation process:" header before the message itself and exits non-zero. Processing is a lock, not progress: if no reindex process is actually running, the lock is stale and reset clears it by moving the status to invalid. Check that no reindex is genuinely in flight first, because resetting during a real rebuild lets a second one start alongside it.

Example · bash
$ bin/magento indexer:status catalog_product_price
+-----------------------+---------------+------------+-----------+------------------------+---------------------+
| ID                    | Title         | Status     | Update On | Schedule Status        | Schedule Updated    |
+-----------------------+---------------+------------+-----------+------------------------+---------------------+
| catalog_product_price | Product Price | Processing | Schedule  | idle (4312 in backlog) | 2026-10-05 07:58:11 |
+-----------------------+---------------+------------+-----------+------------------------+---------------------+

$ bin/magento indexer:reindex catalog_product_price
process error during indexation process:
Product Price index is locked by another reindex process. Skipping.

$ bin/magento indexer:reset catalog_product_price
Product Price indexer has been invalidated.

$ bin/magento indexer:reindex catalog_product_price
Product Price index has been rebuilt successfully in 00:01:38

Registering the command so bin/magento sees it

A console command class on its own does nothing. CommandList takes an array argument of command objects, and each module adds its own items to that array. Without this file bin/magento list will not show vendor:index:assert-ready. After adding it, run bin/magento cache:flush, and bin/magento setup:di:compile as well if the install is in production mode.

Example · xml
<?xml version="1.0"?>
<!-- app/code/Vendor/Ops/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Console\CommandList">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="vendor_ops_assert_indexers_ready" xsi:type="object">
                    Vendor\Ops\Console\Command\AssertIndexersReady
                </item>
            </argument>
        </arguments>
    </type>
</config>

Suspending an indexer during a long job

indexer:set-status writes the status directly, and accepts invalid, suspended or valid. Suspended keeps both cron jobs away from the index in either mode, which is useful around a migration that will touch many rows. Setting a status to valid by hand claims the index is up to date without building anything, so keep that for the case where you know a rebuild already happened.

Example · bash
# stop cron picking this index up while a migration runs
bin/magento indexer:set-status suspended catalog_product_price

# ... run the migration ...

# hand it back and rebuild once
bin/magento indexer:set-status invalid catalog_product_price
bin/magento indexer:reindex catalog_product_price

Discussion

  • Be the first to comment on this lesson.