When you switch a Magento 2 indexer to "Update by Schedule" mode, Magento creates a companion changelog table — the Mview changelog — that tracks every entity change between reindex runs. This is the mechanism that enables incremental indexing: instead of rebuilding the entire index, the indexer only processes the rows that changed.
But there's a catch nobody talks about: these changelog tables grow forever if not managed properly. And as they grow, your indexer performance degrades, your disk usage creeps up, and in extreme cases, your database replication starts lagging.
Here's how to keep Mview changelogs under control.
How Mview Changelogs Work
Each indexer that supports Mview (Materialized View) has a corresponding changelog table. For example:
-
catalog_product_flat_cl— tracks changes to products for the flat catalog -
catalog_category_flat_cl— same for categories -
cataloginventory_stock_status_cl— stock status changes -
catalog_product_index_price_cl— price changes -
catalogsearch_fulltext_cl— search index changes -
catalogrule_product_cl— catalog rule changes
The naming convention is <indexer_table>_cl. Each table has a simple structure:
entity_id INT
operation VARCHAR (INSERT, UPDATE, DELETE)
When a product is saved, Magento inserts a row into every relevant _cl table. When the indexer runs (via cron or bin/magento indexer:reindex), it reads the changelog, processes only those entities, and then truncates the changelog.
The Problem: Unbounded Growth
The truncation only happens when the indexer completes successfully. If your indexer fails, times out, or you're running in "Update on Save" mode (which bypasses Mview entirely), the changelog tables keep accumulating rows.
This creates a vicious cycle:
- Changelog grows → more rows to process per reindex
- Reindex takes longer → more likely to time out
- Timeout kills reindex → changelog isn't truncated
- Go to step 1
On a busy store with frequent product saves, I've seen changelog tables with millions of rows. A catalog_product_flat_cl with 500,000 rows means the indexer has to process 500,000 entities even if most of those changes are already reflected in the index.
Diagnosing Changelog Bloat
Run this SQL query to check the size of all Mview changelog tables:
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name LIKE '%_cl'
ORDER BY table_rows DESC;
If you see tables with more than 100,000 rows, you should investigate. Tables with millions of rows are a red flag.
You can also check the Mview state:
SELECT * FROM mview_state;
This table shows the current version ID for each indexer's changelog. If version_id is significantly behind the max entity_id in the changelog table, the indexer hasn't processed recent changes.
Cleaning Up Changelogs
After a Failed Reindex
If an indexer failed and left a large changelog, you can safely truncate it only if you run a full reindex afterward:
# Truncate the changelog
mysql -e "TRUNCATE TABLE catalog_product_flat_cl;"
# Run a full reindex to rebuild from scratch
bin/magento indexer:reindex catalog_product_flat
⚠️ Never truncate a changelog without reindexing. The indexer won't know about the skipped changes, and your index will be out of sync with the database.
Scheduled Cleanup
For stores that frequently switch between "Update on Save" and "Update by Schedule", or that have unreliable indexers, add a weekly cleanup cron:
# Weekly full reindex + changelog cleanup (off-peak)
0 3 * * 0 cd /var/www/magento && bin/magento indexer:reindex && \
mysql -e "TRUNCATE TABLE catalog_product_flat_cl; TRUNCATE TABLE catalog_category_flat_cl;"
This is aggressive but effective. After a full reindex, the changelogs are empty and the index is fresh.
Configuring Mview Batch Sizes
Magento 2 doesn't expose Mview batch size configuration by default, but you can control how many entities the indexer processes per cycle by adjusting the batch_size parameter in the indexer's configuration.
For example, in etc/indexer.xml:
<indexer id="catalog_product_flat" view="catalog_product_flat" class="Magento\Catalog\Model\Indexer\Product\Flat">
<structure>
<field name="batch_size" value="1000"/>
</structure>
</indexer>
Smaller batch sizes reduce memory usage but increase the number of database round trips. Larger batch sizes are faster but can cause memory exhaustion on large catalogs.
A good starting point:
-
Small catalogs (<10K products):
batch_size = 500 -
Medium catalogs (10K-100K):
batch_size = 1000 -
Large catalogs (100K+):
batch_size = 5000
Monitoring Changelog Health
Add this check to your monitoring stack (Nagios, Zabbix, Prometheus, or a simple cron with alerts):
#!/bin/bash
# Check Mview changelog sizes and alert if any exceed threshold
THRESHOLD=50000
MYSQL_USER="magento"
MYSQL_PASS="password"
MYSQL_DB="magento"
QUERY="SELECT table_name, table_rows FROM information_schema.tables \
WHERE table_schema='${MYSQL_DB}' AND table_name LIKE '%_cl' AND table_rows > ${THRESHOLD}"
RESULTS=$(mysql -u${MYSQL_USER} -p${MYSQL_PASS} -e "${QUERY}" -s 2>/dev/null)
if [ -n "$RESULTS" ]; then
echo "ALERT: Mview changelog tables exceeding threshold:"
echo "$RESULTS"
# Send to monitoring system or email
fi
Set the threshold based on your catalog size and indexer frequency. For a store that reindexes every 5 minutes, 50,000 rows is a lot. For a store that reindexes hourly, 100,000 might be acceptable.
The Mview State Table
The mview_state table is the source of truth for indexer progress. Key columns:
-
state_id— indexer identifier -
view_id— the Mview view name -
mode—enabledordisabled -
version_id— the last processed changelog version -
updated— timestamp of last update
If version_id is stuck (not advancing) while the changelog keeps growing, your indexer is failing silently. Check the cron logs:
grep "indexer" var/log/cron.log | tail -50
Or run the indexer manually to see the error:
bin/magento indexer:reindex catalog_product_flat
Common Pitfalls
1. Switching from "Update on Save" to "Update by Schedule"
When you switch modes, Magento starts writing to the changelog. But any changes made while in "Update on Save" mode are not in the changelog. Always run a full reindex after switching modes:
bin/magento indexer:set-mode schedule catalog_product_flat catalog_category_flat
bin/magento indexer:reindex
2. Long-Running Cron Jobs
If your reindex cron runs every 5 minutes but the reindex takes 7 minutes, you'll have overlapping processes. The second cron starts before the first finishes, leading to deadlocks and changelog corruption.
Fix: Use a lock file or a single-process queue:
# In crontab
*/5 * * * * cd /var/www/magento && \
flock -n /tmp/magento-indexer.lock bin/magento indexer:reindex catalog_product_flat
3. Third-Party Indexers Not Cleaning Up
Custom third-party indexers that use Mview but don't properly truncate their changelogs after processing are a common source of bloat. Audit your custom indexers:
# List all registered indexers and their modes
bin/magento indexer:status
Check if any custom _cl tables are growing without being truncated. If a third-party module's changelog table keeps growing even after successful reindexes, the module may have a bug in its changelog cleanup logic. Report it to the vendor or patch it locally.
4. MySQL Replication Lag
If you're running a primary-replica setup, large changelog tables can cause replication lag in two ways:
-
Replicating the changelog writes — every product save generates INSERT statements into multiple
_cltables, all of which replicate to the replica. - Replicating the truncation — when the indexer truncates a 500K-row changelog, that DDL statement replicates too.
Keep changelogs small to minimize replication overhead.
Changelog Growth vs. Indexer Frequency
There's a direct relationship between how often you reindex and how large your changelogs get. The formula is simple:
changelog_size ≈ (product_saves_per_hour) × (hours_between_reindexes)
If your team saves 200 products per hour and you reindex every 6 hours, your changelog will hover around 1,200 rows per cycle — manageable. If you reindex once a day, it'll be 4,800 rows — still fine. But if a reindex fails and you don't notice for three days, you're at 14,400 rows, and the next reindex will be significantly slower.
Recommendation: Schedule reindex crons at a frequency that keeps changelogs under 10,000 rows between runs. For most stores, every 15-30 minutes is sufficient. For high-volume stores with frequent product updates, every 5 minutes may be necessary.
Recovery Procedure: When Things Go Wrong
If you inherit a store with massively bloated changelogs (millions of rows), follow this recovery procedure:
- Put the store in maintenance mode (if high-traffic):
bin/magento maintenance:enable
- Switch all indexers to "Update on Save" temporarily:
bin/magento indexer:set-mode realtime
- Truncate all changelog tables:
mysql -e "$(mysql -e "SELECT CONCAT('TRUNCATE TABLE ', table_name, ';') FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name LIKE '%_cl'" -s)"
- Run a full reindex:
bin/magento indexer:reindex
- Switch back to "Update by Schedule":
bin/magento indexer:set-mode schedule
- Disable maintenance mode:
bin/magento maintenance:disable
This gives you a clean slate: fresh indexes, empty changelogs, and the indexer ready to track new changes incrementally.
Wrapping Up
Mview changelogs are a powerful feature that enables efficient incremental indexing, but they require active management. The key takeaways:
- Monitor changelog table sizes — set up alerts for tables exceeding 50K rows
- Truncate only after full reindex — never skip the reindex step
- Fix failing indexers immediately — a stuck indexer causes bloat that compounds over time
-
Use
flockfor cron-based reindex — prevent overlapping processes - Audit third-party indexers — they may not clean up their changelogs properly
- Match reindex frequency to your change volume — keep changelogs under 10K rows between runs
By keeping your Mview changelogs lean, you'll keep your indexer runs fast, your disk usage predictable, and your database healthy — even as your catalog grows.
Top comments (0)