← Back to blog

Magento 2 Indexers: Update on Save vs Update by Schedule

Rodrigo Biazzi

Every Magento 2 indexer runs in one of two modes: Update on Save or Update by Schedule. The choice decides whether saving a product in the admin also rebuilds index data in the same request, or whether that work happens later in cron. On a small catalog with little traffic the difference is hardly visible. On a real store, the wrong mode causes slow admin saves, deadlocks during imports, or storefront data that is hours out of date. This article explains how both modes work in Magento Open Source and Adobe Commerce 2.4.8, how to switch, and how to keep schedule mode healthy.

Why Magento needs indexers

Magento stores catalog data in a normalized structure, with EAV tables per attribute type, separate tables for website and store values, and relations for categories, inventory and prices. Reading a product price for a customer group on a website from that structure takes many joins. Indexers precompute flat, query friendly tables such as category product relations, final prices per customer group, search data and stock status. The storefront reads from those index tables.

When the source data changes, the index tables need updating. The two modes decide when.

Update on Save

In Update on Save mode, which the CLI calls realtime, the indexer runs as part of the save. When an admin saves a product, the same request updates the affected rows in the index tables before returning.

The upside is that the storefront reflects the change right away. The downsides grow with the catalog:

  • Admin saves get slower, since each save also does index work for every affected indexer.
  • Mass actions and imports trigger index work for every row, which can turn a two minute import into an hour.
  • Index writes inside the save transaction hold locks longer. Under concurrent saves, imports and storefront traffic, that raises the chance of deadlocks and lock wait timeouts.

Update by Schedule

In Update by Schedule mode, called schedule in the CLI, Magento uses the mview framework. When you switch an indexer to schedule mode, Magento creates database triggers on the source tables that indexer depends on. Each insert, update or delete on those tables writes the entity ID into a changelog table, named after the view with a _cl suffix, for example catalog_product_price_cl.

The save itself only writes the source data plus a changelog row, which is fast. Later, the cron job indexer_update_all_views in the index cron group reads new changelog entries since the last processed version and reindexes only those entities. The current version for each view is tracked in the mview_state table.

The upside is fast saves and imports, fewer locks, and index work done in batches. The downside is a delay. A change appears on the storefront after the next index cron run, typically within a minute or two if cron runs every minute and keeps up.

Checking and switching modes

bin/magento indexer:show-mode
bin/magento indexer:status

bin/magento indexer:set-mode schedule
bin/magento indexer:set-mode schedule catalog_product_price catalogsearch_fulltext
bin/magento indexer:set-mode realtime customer_grid

Without indexer codes, set-mode applies to all indexers. Switching to schedule creates the triggers and changelog tables. Switching back to realtime drops the triggers.

Creating triggers when binary logging is enabled on MySQL requires either the SUPER privilege or log_bin_trust_function_creators = 1. If set-mode schedule fails with a privilege error, that is the cause. The MySQL tuning guide covers this setting.

Which mode to use

For production stores, use Update by Schedule for almost everything. It is the setup Adobe recommends for production and the one that scales with catalog size and traffic.

The common exception is the customer grid indexer. It is built for full reindexing and does not fully support schedule mode, so leave customer_grid on Update on Save. The admin customer grid then updates when customers are saved.

On a local environment or a tiny store, Update on Save is convenient because you see changes right away without cron. Just remember that local behaviour then differs from production, and bugs related to delayed indexing will not show up locally.

Cron must run

Schedule mode only works if cron runs, and specifically the index group. If cron is broken, triggers keep writing to changelog tables and nothing processes them. Product changes never reach the storefront, and the changelog tables grow without limit. Check:

SELECT * FROM mview_state;

SELECT COUNT(*) FROM catalog_product_price_cl;

SELECT job_code, status, scheduled_at, executed_at, finished_at
FROM cron_schedule
WHERE job_code LIKE 'indexer%'
ORDER BY scheduled_at DESC
LIMIT 20;

The version_id in mview_state should keep up with the latest IDs in the changelog tables. If the newest cron rows for indexer jobs are old, or many are stuck in running or error, fix cron first. The cron jobs guide covers how cron groups work and how to run the index group separately.

Changelog cleanup

Processed changelog entries are removed by the cron job indexer_clean_all_changelogs. As long as cron runs, the tables stay small. If they are large, entries are either not being processed or not being cleaned.

Invalid indexers and full reindexes

An indexer becomes invalid when Magento decides the index as a whole is no longer trustworthy, for example after certain configuration changes or attribute changes that affect the index structure. The admin shows a message, and indexer:status shows the indexer as requiring a reindex.

In schedule mode, the cron job indexer_reindex_all_invalid rebuilds invalid indexers automatically. You can also run it yourself:

bin/magento indexer:reindex catalog_product_price
bin/magento indexer:reset catalog_product_price

indexer:reset marks an indexer as invalid, which is useful when you want cron to pick it up for a full rebuild. Avoid running full reindexes of large indexers during peak traffic. They are heavy on the database and can lock tables the storefront reads.

Recent 2.4 releases also have bin/magento indexer:set-status, which can set an indexer to valid, invalid or suspended. Suspending stops cron from processing an indexer for a while, for example during a large import that you will follow with one full reindex. Check bin/magento list indexer on your installation to confirm the commands available.

Price indexer dimensions

The product price index can be split by dimensions, so each website and customer group combination gets its own table. On stores with many websites or customer groups this makes the price indexer faster and the tables smaller:

bin/magento indexer:set-dimensions-mode catalog_product_price website_and_customer_group
bin/magento indexer:show-dimensions-mode

Changing dimensions mode triggers a full reindex of prices. Plan it for a quiet time.

Custom indexers and mview

If you write your own indexer, you declare its mview subscriptions in etc/mview.xml, listing which tables and columns should create changelog entries. The same rules apply: schedule mode depends on triggers and cron, and your indexer's execute method receives the changed IDs in batches. The custom indexer guide shows a full example.

Imports and schedule mode

Large imports are where the mode choice shows most. In schedule mode, the import writes rows and the triggers add changelog entries. The import itself stays fast, and the index cron then works through the changes in batches. With Update on Save, every imported row triggers index work inline. If an import still takes too long in schedule mode, look at the import code itself, as covered in the product import best practices.

FAQ

Should Magento 2 indexers be Update on Save or Update by Schedule?

Use Update by Schedule for production stores, with customer_grid left on Update on Save. Update on Save is fine for local development and very small stores.

Why are my product changes not showing on the storefront?

With indexers in schedule mode, changes appear after the index cron runs. If they never appear, cron is probably not running the index group.

Why does indexer:set-mode schedule fail with a privilege error?

Schedule mode creates MySQL triggers. With binary logging on, that requires SUPER or log_bin_trust_function_creators set to 1.

Why are the _cl tables in my database so large?

Changelog entries are not being processed or cleaned, usually because cron is not running or the index group is stuck.

I am available for freelance Magento and Adobe Commerce work, including indexing and cron problems. If you need help with this, get in touch.

Related articles