Next time a client asks "why is my reindex slow?", the answer is almost always the price index. It's the indexer that scales worst with catalog size, the one that chokes on webshops, B2B stores, configurable-heavy catalogs and (counter-intuitively) gets more painful the more you try to "fix" it with raw SQL. Yet most developers treat it like a black box.
In this guide I'll pull back the curtain on how Magento actually stores and computes prices, why the price index behaves the way it does, and — crucially — the strategies that actually move the needle.
How Magento stores prices (the part nobody reads)
Prices do not live on the product table. When you save a product, Magento stores the raw price on catalog_product_entity_decimal — one row per product, attribute, store and scope.
But the moment you add any of the following, the "true" price stops being a simple column lookup:
-
Tier prices (
catalog_product_entity_tier_price) -
Special price scheduling (
special_from_date/special_to_date) -
Catalog price rules (
catalogrule, applied via rules engine) - Group prices (customer groups)
- Bundle/grouped product composite pricing
- Configurable products with per-option price adjustments
- Staging updates (Content Staging, Magento Commerce)
Because the effective price depends on time, customer group and rules, Magento has to precompute it. That precomputation is the price index. When it works, page requests just read a flat, pre-joined set of rows instead of re-evaluating every rule on every request. When it's stale or under-built, you either serve wrong prices or you trigger expensive on-the-fly calculation.
What the price index actually is
The price indexer writes to the catalog_product_index_price table (plus _idx/_tmp variants and the catalog_product_index_price_final_tmp intermediate tables). A reindex runs in phases:
-
Reindex all products into the
_tmptable with their base price. - Apply tax, tier, special, group and rule adjustments.
- Handle composite products (bundle's min/max, grouped's sums).
- Merge into the final
catalog_product_index_price(and customer-group / website / store dimension rows).
This is why the table balloons: one row per product × website × customer group. A 50k SKU store across 2 websites and 4 customer groups produces 400,000 price-index rows — and that's before configurable option permutations and staged versions multiply it further.
Why it's slow (the real reasons)
1. It re-evaluates every product, not just changed ones
The full reindex (bin/magento indexer:reindex catalog_product_price) rebuilds the entire dimension space. Unlike the URL-rewrite indexer, the price indexer historically had weak partial-reindex support, so a single product save could kick off a large portion of the rebuild. Mview (the changelog-based indexer) helps, but on heavy write stores it can still fall behind — and the moment it falls too far behind, Magento falls back to a synchronous rebuild mid-request.
2. Configurable products are a multiplier
Every configurable product with 50 options effectively fans out into 50 price rows (minus on-demand fallbacks). Storefronts with huge configurable catalogs see the price indexer take orders of magnitude longer than other indexers for the same SKU count.
3. Catalog price rules force full re-evaluation
Unlike tier prices (stored per row), catalog price rules are computed by a rule engine that must evaluate each product against each rule's conditions. That's not a column read — it's a rules evaluation over the whole catalog. The MySQL-based rules engine (catalogrule) is notoriously slow, and the indexer runs it on every reindex. This is the single most common reason a price reindex takes minutes to hours on real stores.
4. It's CPU-, I/O- and memory-bound all at once
The price indexer builds large temporary tables, sorts them, and merges them. On shared hosting or under-provisioned MySQL (tiny innodb_buffer_pool_size, no SSD), this is brutal. And because Magento can run indexers in a limited number of ways by default, you can't easily parallelize it without care.
Measure before you touch anything
Never optimize blind. Establish a baseline first:
bin/magento indexer:status
# Look at the "Schedule Status" for catalog_product_price — is it in Update by Schedule? Behind?
# Time a full build
time bin/magento indexer:reindex catalog_product_price
# See row counts (the big picture and its dimensions)
mysql -e "SELECT COUNT(*) FROM catalog_product_index_price;"
mysql -e "SELECT COUNT(*) FROM catalog_product_index_price_final_tmp;"
Also check catalog_product_index_price row count against your SKU count. If it's 5–10× your product count, you have a multiplication problem (websites × groups × option permutations) that no "tune the server" advice will fix.
Strategies that actually help
Strategy 1 — Question whether the price indexer should exist at all
This sounds heretical but it's the highest-leverage move: the more you can move price logic out of the indexer, the faster it gets.
- Replace catalog price rules with tier prices or per-product special price where feasible. A rule that applies "10% off category X" is convenient, but if it covers a large category it forces full rule re-evaluation on every reindex. Tier prices are stored per row and are dramatically cheaper to index. Before refactoring, check whether your rules are static enough to be converted.
-
Avoid time-scheduled rules with tight windows. Every
special_from/toand scheduled rule means the previous/next value differs, forcing the indexer to track time dimensions.
If you must keep rules, at least reduce rule condition complexity. Each condition touches an attribute — complex conditions (category membership blends, multiple AND/OR groups) multiply evaluation cost.
Strategy 2 — Feed the database properly
The price indexer is a database workload, so it responds to database provisioning:
- Put MySQL on NVMe SSD. This is often the single biggest win for indexers — the difference between a 20-minute and a 3-minute reindex on big catalogs.
- Raise
innodb_buffer_pool_sizeto 70–80% of available RAM (on a dedicated DB server) so the_tmptables and sort buffers live in memory. - Increase
tmp_table_sizeandmax_heap_sizeso intermediate price tables stay as in-memoryMEMORYtables instead of spilling to disk. - Watch
innodb_io_capacityandinnodb_io_capacity_max— matching them to your actual device prevents InnoDB from under-using the disk during large writes.
Strategy 3 — Run it at the right time, the right way
- Schedule the price indexer to run off-peak via cron, and keep
indexer:statusin "Update by Schedule" for it. - Prevent concurrent indexers from fighting each other. Multiple heavy indexers (price + search + URL rewrites) running at once on one DB share the same InnoDB buffer pool and I/O. Stagger them in your crontab.
- For very large catalogs, run the indexer on a staging/standby DB replica and promote it, rather than hammering your live DB mid-day.
Strategy 4 — Reduce the multiplication
- Audit customer groups. If you have dozens of groups that all resolve to identical pricing, you're paying for N× the rows for no benefit. Consolidate groups whose pricing is actually the same — the price indexer is one of the biggest hidden costs of group sprawl.
- Audit websites/store views. Same logic: each website adds a dimension. If an extra website doesn't have distinct pricing, you may be indexing duplicate data.
Strategy 5 — Don't hand-edit the price tables (stop the nightmare)
A recurring anti-pattern from novice "performance fixes": someone truncates or hand-updates catalog_product_index_price "to make the site faster." This immediately produces wrong storefront prices (the storefront reads the index, not the base table) and corrupts the changelog, causing the indexer to fall back to full rebuilds. Never touch *_index_price tables directly. If you need a cached price override, use the proper entity/rule mechanisms and reindex cleanly.
A note on partial reindexing and Mview
catalog_product_price uses Mview (change-log based updating) so that incremental saves update only the affected products. This works well when:
- Your store is write-light (few saves per second) — the changelog stays tiny and watchers keep up.
- You don't run heavy catalog price rules (rules still trigger broad re-evaluation).
On write-heavy dynamic-pricing stores (frequent price imports, B2B negotiated pricing via staged updates), Mview can fall behind and you end up with a runaway backlog. In those cases, controlled off-peak full reindexes are often more predictable than trusting incremental updates.
The practical checklist
To summarize, here's what to audit when price reindexing is slow:
- Row-count ratio — is the price index 5–10× your SKU count? Attack the dimensions (groups/websites/options), not the server.
- Catalog price rules — are big static rules forcing full rule-engine evaluation every reindex? Migrate to tier/special prices where possible.
-
Storage — NVMe +
innodb_buffer_pool_size+ in-memory temp tables. - Scheduling — off-peak, staggered, non-overlapping with other heavy indexers.
-
Never hand-edit
*_index_pricetables.
Final word
The price index is where "it's just a cache" thinking fails. It's a real computational pipeline that re-derives the effective price for every product across every dimension. If you respect what it's doing — precomputing complex pricing so the storefront can serve pages fast — you can make informed choices: shrink the dimensions, simplify the rules, feed the DB, and schedule intelligently. Do that, and price reindexing goes from a feared maintenance window to a quiet background job.
Want me to cover a specific aspect of Magento pricing next — like catalog price rule internals or composite-product price calculation? Drop it in the comments.
Top comments (0)