Update on Save and Update by Schedule

Update on Save reindexes during the save; Update by Schedule logs ids to a changelog table and lets cron catch up.

Syntaxbin/magento indexer:set-mode {realtime|schedule} [indexer ...]

Every indexer runs in one of two modes, and the mode is set per indexer rather than globally.

Update on Save

The CLI calls this mode realtime. Saving a product reindexes the affected rows before the save returns. The storefront is correct immediately, and the person who clicked Save waits for it. That cost is paid again for every row.

Update by Schedule

The CLI calls this schedule. Magento puts MySQL triggers on the tables the indexer depends on. Each write appends the changed entity id to a changelog table named after the view id plus _cl, so catalog_product_price_cl, with columns version_id and entity_id. The machinery is called mview, short for materialised view. The cron group index runs the job indexer_update_all_views every minute; it reads the ids newer than the version_id stored in the mview_state table and hands them to the indexer, then records the new version.

Why an import must not run on save

In Update on Save, importing 20,000 products runs the price, category and stock indexers 20,000 times. In Update by Schedule the triggers write changelog rows and cron rebuilds them in batches. Switch to schedule before the import.

Switching mode is also what creates or drops the triggers, and it takes effect at once with no cache command. In 2.4.8 and later customer_grid supports both modes; earlier versions supported Update on Save only.

Example

Example · xml
<?xml version="1.0"?>
<!-- vendor/magento/module-indexer/etc/crontab.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="index">
        <job name="indexer_reindex_all_invalid"
             instance="Magento\Indexer\Cron\ReindexAllInvalid" method="execute">
            <schedule>* * * * *</schedule>
        </job>
        <job name="indexer_update_all_views"
             instance="Magento\Indexer\Cron\UpdateMview" method="execute">
            <schedule>* * * * *</schedule>
        </job>
        <job name="indexer_clean_all_changelogs"
             instance="Magento\Indexer\Cron\ClearChangelog" method="execute">
            <schedule>*/5 * * * *</schedule>
        </job>
    </group>
</config>

When to use it

  • Preparing a store for a nightly product feed of 50,000 SKUs.
  • Working out why a price saved in the admin appears a minute later instead of at once.
  • Deciding which indexers may stay on save on a small store whose cron is unreliable.
  • Diagnosing a changelog table that has grown to millions of rows.

More examples

Switching a store to Update by Schedule

With no indexer argument, set-mode applies to every indexer, which is the usual setting for a production store. show-mode is the quick confirmation; indexer:status is the fuller one, and only shows the Schedule Status and Schedule Updated columns for indexers that are in schedule mode. A backlog that never falls to 0 means cron is not running.

Example · bash
$ bin/magento indexer:set-mode schedule
Index mode for Indexer Design Config Grid was changed from 'Update on Save' to 'Update by Schedule'
Index mode for Indexer Customer Grid was changed from 'Update on Save' to 'Update by Schedule'
Index mode for Indexer Category Products was changed from 'Update on Save' to 'Update by Schedule'
...

$ bin/magento indexer:show-mode catalog_product_price
Product Price:                                    Update by Schedule

$ bin/magento indexer:status catalog_product_price
+-----------------------+---------------+--------+-----------+---------------------+---------------------+
| ID                    | Title         | Status | Update On | Schedule Status     | Schedule Updated    |
+-----------------------+---------------+--------+-----------+---------------------+---------------------+
| catalog_product_price | Product Price | Ready  | Schedule  | idle (0 in backlog) | 2026-10-05 08:14:02 |
+-----------------------+---------------+--------+-----------+---------------------+---------------------+

Looking at the changelog by hand

When a saved change does not reach the storefront, these three queries settle who is at fault. A changelog row with a version_id above the one in mview_state means the change was recorded and cron has not processed it yet. No new changelog row at all means the triggers are missing or you wrote to the database in a way that bypassed them. SHOW TRIGGERS confirms which it is.

Example · sql
-- the triggers write one row per changed entity
SELECT * FROM catalog_product_price_cl ORDER BY version_id DESC LIMIT 5;
-- version_id | entity_id
--      90412 |      1455
--      90411 |      1454
--      90410 |      1453

-- mview_state records how far the indexer has consumed the changelog
SELECT view_id, mode, status, version_id, updated
FROM mview_state WHERE view_id = 'catalog_product_price';
-- catalog_product_price | enabled | idle | 90412 | 2026-10-05 08:14:02

-- the triggers themselves, created when the mode was set to schedule
SHOW TRIGGERS LIKE 'catalog_product_entity';

A bulk import, in the right order

Step 3 is the one people miss on a development machine, where cron is often not installed at all. cron:run --group=index runs the index group once in the foreground, which processes the changelog immediately. On a real server the system crontab does this every minute and step 3 is unnecessary.

Example · bash
# 1. make sure nothing reindexes inside each row's save
bin/magento indexer:set-mode schedule catalog_product_price \
    catalog_category_product catalog_product_category \
    cataloginventory_stock catalogsearch_fulltext

# 2. run the import (admin grid, or your own CLI command)
bin/magento vendor:feed:import /var/feeds/products.csv

# 3. let mview catch up now rather than waiting for the cron tick
bin/magento cron:run --group=index

# 4. check the backlog is draining
bin/magento indexer:status

Discussion

  • Be the first to comment on this lesson.