Magento Indexing and Caching
Overview
Magento's performance architecture relies on two distinct layers: indexers that pre-compute denormalized data (product prices, search indexes, category paths) into flat tables, and the Full-Page Cache (FPC) that stores complete rendered HTML pages. Varnish is the recommended FPC backend for production, replacing Magento's built-in file-based cache. Redis is the recommended backend for application cache, session storage, and the default cache (block HTML, config, layout). Misconfigured or stale indexes are one of the most common causes of incorrect pricing and product visibility issues.
When to Use This Skill
- When products appear with wrong prices or are invisible after catalog updates
- When implementing Varnish for the first time on a Magento production server
- When diagnosing slow category page loads caused by on-the-fly price calculation
- When configuring Redis for Magento's session and block cache storage
- When setting up cache invalidation after catalog or CMS updates via Magento's cache tags
- When managing indexer schedules on large catalogs (> 50,000 products) to prevent full reindex locking
Core Instructions
Understand Magento's indexers and their modes
# List all indexers and their status
bin/magento indexer:info
bin/magento indexer:status
# Example output:
# catalog_category_product Category Products valid Update on Save
# catalog_product_category Product Categories valid Update on Save
# catalog_product_price Product Price invalid Update by Schedule
# catalogsearch_fulltext Catalog Search valid Update by Schedule
# catalogrule_product Catalog Rule Product invalid Update on Save
Set all production indexers to Update by Schedule mode to prevent checkout blocking:
bin/magento indexer:set-mode schedule catalog_category_product
bin/magento indexer:set-mode schedule catalog_product_price
bin/magento indexer:set-mode schedule catalogsearch_fulltext
bin/magento indexer:set-mode schedule catalog_product_flat
bin/magento indexer:set-mode schedule catalog_category_flat
# Verify modes
bin/magento indexer:show-mode
Manage indexers and partial reindexing
# Reindex a single indexer (less disruptive than full reindex)
bin/magento indexer:reindex catalog_product_price
# Reindex all (avoid on production during business hours)
bin/magento indexer:reindex
# Reset indexer to "invalid" to force next scheduled run
bin/magento indexer:reset catalog_product_price
# Check mview (materialized view) changelog tables — lists pending rows to process
bin/magento indexer:show-changelog
For very large catalogs, use parallel indexing:
# app/etc/env.php — enable parallel processing
# In Admin → System → Index Management → Indexers → Configure
# Or via config:
bin/magento config:set dev/grid/async_indexing 1
<?php
// app/etc/env.php
return [
// ...
'indexer' => [
'batch_size' => [
'catalog_product_price' => ['simple' => 200, 'configurable' => 50],
'catalogsearch_fulltext' => ['simple' => 500],
],
],
];
Configure Varnish as Full-Page Cache
# In Admin → System → Configuration → Advanced → System → Full Page Cache
# Set Caching Application: Varnish Cache
# Export Varnish configuration:
bin/magento varnish:vcl:generate --export-version=6 --output-file=/etc/varnish/magento.vcl
Key sections of the generated VCL to understand:
# /etc/varnish/magento.vcl (excerpts from generated config)
sub vcl_recv {
# Do not cache if private cookie is set (logged-in customer)
if (req.http.cookie ~ "X-Magento-Vary=") {
# Magento sets this cookie to vary cache by context (customer group, currency)
# The cookie value changes only when the context changes — not per session
}
# Strip _ga, fbclid, utm_* params from cache key to prevent cache fragmentation
set req.url = regsuball(req.url, "(\?|&)(utm_[^&]+|fbclid|gclid|mc_[^&]+)(&|$)", "\1");
}
sub vcl_hash {
# Include Magento's context cookie in the cache hash
if (req.http.cookie ~ "X-Magento-Vary=") {
hash_data(regsub(req.http.cookie, ".*X-Magento-Vary=([^;]+).*", "\1"));
}
}
sub vcl_backend_response {
# Respect Magento's Cache-Control headers
if (beresp.http.Cache-Control ~ "no-store") {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
}
# Strip Set-Cookie on cacheable responses
if (beresp.ttl > 0s) {
unset beresp.http.set-cookie;
}
}
Configure Redis for application cache and sessions
<?php
// app/etc/env.php — Redis cache and session configuration
return [
'cache' => [
'frontend' => [
'default' => [
'id_prefix' => 'b0c_',
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => '127.0.0.1',
'database' => '0',
'port' => '6379',
'password' => '',
'compress_data' => '1',
'compression_lib' => 'gzip',
'persistent' => 'mgto_cache',
],
],
'page_cache' => [
'id_prefix' => 'b0c_',
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => '127.0.0.1',
'database' => '1', // Separate DB for FPC
'port' => '6379',
'compress_data' => '0', // FPC data is already compressed HTML
],
],
],
],
'session' => [
'save' => 'redis',
'redis' => [
'host' => '127.0.0.1',
'port' => '6379',
'password' => '',
'timeout' => '2.5',
'persistent_identifier' => 'mgto_sessions',
'database' => '2', // Separate DB for sessions
'compression_threshold' => '2048',
'compression_library' => 'gzip',
'log_level' => '1',
'max_concurrency' => '6',
'break_after_frontend' => '5',
'max_lifetime' => '7200',
'disable_locking' => '1', // Improves performance but reduces session safety
],
],
];
Implement cache tag invalidation for custom modules
Magento uses cache tags to selectively invalidate cached pages when data changes. Custom modules should tag and clean caches properly:
<?php
// Declare cache tags in your block/model
namespace MyVendor\CustomModule\Block;
use Magento\Framework\View\Element\Template;
use Magento\Framework\DataObject\IdentityInterface;
class CustomProductWidget extends Template implements IdentityInterface
{
private array $productIds = [];
public function getIdentities(): array
{
// Return cache tags so Magento invalidates this block when any of these products change
$tags = [\Magento\Catalog\Model\Product::CACHE_TAG];
foreach ($this->productIds as $id) {
$tags[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $id;
}
return $tags;
}
}
<?php
// In your observer or command — flush only relevant pages after product update
namespace MyVendor\CustomModule\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\PageCache\Model\Cache\Type as PageCacheType;
class ProductSaveObserver implements ObserverInterface
{
public function __construct(
private readonly TypeListInterface $cacheTypeList,
private readonly \Magento\Framework\App\Cache\Tag\Resolver $tagResolver
) {}
public function execute(\Magento\Framework\Event\Observer $observer): void
{
$product = $observer->getEvent()->getProduct();
// Invalidate only pages tagged with this product's cache tag
// Varnish will purge via PURGE request using X-Magento-Tags-Pattern header
$this->cacheTypeList->invalidate(PageCacheType::TYPE_IDENTIFIER);
}
}
Examples
Diagnose index lag causing wrong prices
# Check if mview changelog has pending rows
bin/magento indexer:show-changelog
# If catalog_product_price has > 0 pending rows, prices are stale
# Run the indexer to catch up
bin/magento indexer:reindex catalog_product_price
# Check cron_schedule table for missed cron jobs (should run indexers every minute)
bin/magento cron:run --group index &
# Or manually via:
mysql -u root -e "SELECT job_code, status, finished_at FROM magento.cron_schedule WHERE job_code LIKE '%index%' ORDER BY finished_at DESC LIMIT 20;"
Warm FPC after deployment
#!/bin/bash
# scripts/warm-cache.sh — warm the FPC after deploy
set -e
MAGENTO_BASE_URL="https://www.mystore.com"
SITEMAP_URL="$MAGENTO_BASE_URL/sitemap.xml"
echo "Flushing Magento caches..."
bin/magento cache:flush
echo "Warming FPC via sitemap..."
# Download sitemap and curl each URL with a warm-cache header
curl -s "$SITEMAP_URL" | \
grep -oP '<loc>\K[^<]+' | \
head -500 | \
xargs -P 10 -I {} curl -s -o /dev/null -w "%{http_code} {}\n" \
-H "X-Warm-Cache: 1" {}
echo "Cache warming complete."
Best Practices
- Always use
Update by Schedule mode in production — Update on Save reindexes synchronously during admin saves, causing timeouts on large catalogs
- Separate Redis databases for cache, FPC, and sessions — using database 0 for everything causes key conflicts and makes it impossible to flush only one type
- Tag custom blocks with
IdentityInterface — this ensures Varnish and Magento's FPC invalidate the right pages when your data changes
- Monitor indexer changelog table sizes — a
catalog_product_price_cl table with millions of rows indicates cron is not running; fix cron before the table size causes reindex timeouts
- Use
bin/magento cache:clean vs cache:flush — clean removes only invalid cache entries (safe); flush nukes everything including other framework caches sharing the same Redis
- Set distinct
id_prefix per environment — staging and production can share a Redis server if each has a unique prefix, preventing cache poisoning
- Test Varnish with
curl -I and X-Cache header — a HIT response confirms Varnish is serving from cache; MISS means it's hitting Magento for every request
Common Pitfalls
| Problem |
Solution |
| Products invisible or wrong price after import |
Run bin/magento indexer:reindex after bulk imports — programmatic product saves don't always trigger indexer if using ResourceModel\Product::save directly |
| Varnish serving stale pages for logged-in customers |
Ensure X-Magento-Vary cookie is in the Varnish VCL hash; logged-in customers get a different vary value that bypasses the shared cache |
| Redis running out of memory |
Set maxmemory-policy allkeys-lru in redis.conf for cache databases; for session database use noeviction to prevent silent session loss |
| FPC not invalidating after product save |
Check that Varnish's PURGE ACL allows requests from the Magento server's IP; test with curl -X PURGE http://varnish-ip/ |
| Full reindex takes hours and locks tables |
Enable indexer batch sizes in env.php and run with --mode realtime for partial reindexing; schedule during off-peak windows |
| Cron indexers not running |
Check var/log/magento.cron.log for errors; verify the system crontab entry runs bin/magento cron:run every minute as the web server user |
Related Skills
- @magento-module-development
- @magento-graphql
- @magento-multi-store
- @caching-strategies
- @infrastructure-performance
1---2name: magento-indexing-caching3description: Speed up Magento by managing indexers correctly, configuring Varnish full-page cache, and using Redis for session and object caching4---56# Magento Indexing and Caching78## Overview910Magento's performance architecture relies on two distinct layers: indexers that pre-compute denormalized data (product prices, search indexes, category paths) into flat tables, and the Full-Page Cache (FPC) that stores complete rendered HTML pages. Varnish is the recommended FPC backend for production, replacing Magento's built-in file-based cache. Redis is the recommended backend for application cache, session storage, and the default cache (block HTML, config, layout). Misconfigured or stale indexes are one of the most common causes of incorrect pricing and product visibility issues.1112## When to Use This Skill1314- When products appear with wrong prices or are invisible after catalog updates15- When implementing Varnish for the first time on a Magento production server16- When diagnosing slow category page loads caused by on-the-fly price calculation17- When configuring Redis for Magento's session and block cache storage18- When setting up cache invalidation after catalog or CMS updates via Magento's cache tags19- When managing indexer schedules on large catalogs (> 50,000 products) to prevent full reindex locking2021## Core Instructions22231. **Understand Magento's indexers and their modes**2425 ```bash26 # List all indexers and their status27 bin/magento indexer:info28 bin/magento indexer:status2930 # Example output:31 # catalog_category_product Category Products valid Update on Save32 # catalog_product_category Product Categories valid Update on Save33 # catalog_product_price Product Price invalid Update by Schedule34 # catalogsearch_fulltext Catalog Search valid Update by Schedule35 # catalogrule_product Catalog Rule Product invalid Update on Save36 ```3738 Set all production indexers to `Update by Schedule` mode to prevent checkout blocking:3940 ```bash41 bin/magento indexer:set-mode schedule catalog_category_product42 bin/magento indexer:set-mode schedule catalog_product_price43 bin/magento indexer:set-mode schedule catalogsearch_fulltext44 bin/magento indexer:set-mode schedule catalog_product_flat45 bin/magento indexer:set-mode schedule catalog_category_flat4647 # Verify modes48 bin/magento indexer:show-mode49 ```50512. **Manage indexers and partial reindexing**5253 ```bash54 # Reindex a single indexer (less disruptive than full reindex)55 bin/magento indexer:reindex catalog_product_price5657 # Reindex all (avoid on production during business hours)58 bin/magento indexer:reindex5960 # Reset indexer to "invalid" to force next scheduled run61 bin/magento indexer:reset catalog_product_price6263 # Check mview (materialized view) changelog tables — lists pending rows to process64 bin/magento indexer:show-changelog65 ```6667 For very large catalogs, use parallel indexing:6869 ```bash70 # app/etc/env.php — enable parallel processing71 # In Admin → System → Index Management → Indexers → Configure72 # Or via config:73 bin/magento config:set dev/grid/async_indexing 174 ```7576 ```php77 <?php78 // app/etc/env.php79 return [80 // ...81 'indexer' => [82 'batch_size' => [83 'catalog_product_price' => ['simple' => 200, 'configurable' => 50],84 'catalogsearch_fulltext' => ['simple' => 500],85 ],86 ],87 ];88 ```89903. **Configure Varnish as Full-Page Cache**9192 ```bash93 # In Admin → System → Configuration → Advanced → System → Full Page Cache94 # Set Caching Application: Varnish Cache95 # Export Varnish configuration:96 bin/magento varnish:vcl:generate --export-version=6 --output-file=/etc/varnish/magento.vcl97 ```9899 Key sections of the generated VCL to understand:100101 ```vcl102 # /etc/varnish/magento.vcl (excerpts from generated config)103104 sub vcl_recv {105 # Do not cache if private cookie is set (logged-in customer)106 if (req.http.cookie ~ "X-Magento-Vary=") {107 # Magento sets this cookie to vary cache by context (customer group, currency)108 # The cookie value changes only when the context changes — not per session109 }110111 # Strip _ga, fbclid, utm_* params from cache key to prevent cache fragmentation112 set req.url = regsuball(req.url, "(\?|&)(utm_[^&]+|fbclid|gclid|mc_[^&]+)(&|$)", "\1");113 }114115 sub vcl_hash {116 # Include Magento's context cookie in the cache hash117 if (req.http.cookie ~ "X-Magento-Vary=") {118 hash_data(regsub(req.http.cookie, ".*X-Magento-Vary=([^;]+).*", "\1"));119 }120 }121122 sub vcl_backend_response {123 # Respect Magento's Cache-Control headers124 if (beresp.http.Cache-Control ~ "no-store") {125 set beresp.uncacheable = true;126 set beresp.ttl = 120s;127 }128129 # Strip Set-Cookie on cacheable responses130 if (beresp.ttl > 0s) {131 unset beresp.http.set-cookie;132 }133 }134 ```1351364. **Configure Redis for application cache and sessions**137138 ```php139 <?php140 // app/etc/env.php — Redis cache and session configuration141142 return [143 'cache' => [144 'frontend' => [145 'default' => [146 'id_prefix' => 'b0c_',147 'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',148 'backend_options' => [149 'server' => '127.0.0.1',150 'database' => '0',151 'port' => '6379',152 'password' => '',153 'compress_data' => '1',154 'compression_lib' => 'gzip',155 'persistent' => 'mgto_cache',156 ],157 ],158 'page_cache' => [159 'id_prefix' => 'b0c_',160 'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',161 'backend_options' => [162 'server' => '127.0.0.1',163 'database' => '1', // Separate DB for FPC164 'port' => '6379',165 'compress_data' => '0', // FPC data is already compressed HTML166 ],167 ],168 ],169 ],170 'session' => [171 'save' => 'redis',172 'redis' => [173 'host' => '127.0.0.1',174 'port' => '6379',175 'password' => '',176 'timeout' => '2.5',177 'persistent_identifier' => 'mgto_sessions',178 'database' => '2', // Separate DB for sessions179 'compression_threshold' => '2048',180 'compression_library' => 'gzip',181 'log_level' => '1',182 'max_concurrency' => '6',183 'break_after_frontend' => '5',184 'max_lifetime' => '7200',185 'disable_locking' => '1', // Improves performance but reduces session safety186 ],187 ],188 ];189 ```1901915. **Implement cache tag invalidation for custom modules**192193 Magento uses cache tags to selectively invalidate cached pages when data changes. Custom modules should tag and clean caches properly:194195 ```php196 <?php197 // Declare cache tags in your block/model198 namespace MyVendor\CustomModule\Block;199200 use Magento\Framework\View\Element\Template;201 use Magento\Framework\DataObject\IdentityInterface;202203 class CustomProductWidget extends Template implements IdentityInterface204 {205 private array $productIds = [];206207 public function getIdentities(): array208 {209 // Return cache tags so Magento invalidates this block when any of these products change210 $tags = [\Magento\Catalog\Model\Product::CACHE_TAG];211 foreach ($this->productIds as $id) {212 $tags[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $id;213 }214 return $tags;215 }216 }217 ```218219 ```php220 <?php221 // In your observer or command — flush only relevant pages after product update222 namespace MyVendor\CustomModule\Observer;223224 use Magento\Framework\Event\ObserverInterface;225 use Magento\Framework\App\Cache\TypeListInterface;226 use Magento\PageCache\Model\Cache\Type as PageCacheType;227228 class ProductSaveObserver implements ObserverInterface229 {230 public function __construct(231 private readonly TypeListInterface $cacheTypeList,232 private readonly \Magento\Framework\App\Cache\Tag\Resolver $tagResolver233 ) {}234235 public function execute(\Magento\Framework\Event\Observer $observer): void236 {237 $product = $observer->getEvent()->getProduct();238239 // Invalidate only pages tagged with this product's cache tag240 // Varnish will purge via PURGE request using X-Magento-Tags-Pattern header241 $this->cacheTypeList->invalidate(PageCacheType::TYPE_IDENTIFIER);242 }243 }244 ```245246## Examples247248### Diagnose index lag causing wrong prices249250```bash251# Check if mview changelog has pending rows252bin/magento indexer:show-changelog253254# If catalog_product_price has > 0 pending rows, prices are stale255# Run the indexer to catch up256bin/magento indexer:reindex catalog_product_price257258# Check cron_schedule table for missed cron jobs (should run indexers every minute)259bin/magento cron:run --group index &260# Or manually via:261mysql -u root -e "SELECT job_code, status, finished_at FROM magento.cron_schedule WHERE job_code LIKE '%index%' ORDER BY finished_at DESC LIMIT 20;"262```263264### Warm FPC after deployment265266```bash267#!/bin/bash268# scripts/warm-cache.sh — warm the FPC after deploy269set -e270271MAGENTO_BASE_URL="https://www.mystore.com"272SITEMAP_URL="$MAGENTO_BASE_URL/sitemap.xml"273274echo "Flushing Magento caches..."275bin/magento cache:flush276277echo "Warming FPC via sitemap..."278# Download sitemap and curl each URL with a warm-cache header279curl -s "$SITEMAP_URL" | \280 grep -oP '<loc>\K[^<]+' | \281 head -500 | \282 xargs -P 10 -I {} curl -s -o /dev/null -w "%{http_code} {}\n" \283 -H "X-Warm-Cache: 1" {}284285echo "Cache warming complete."286```287288## Best Practices289290- **Always use `Update by Schedule` mode in production** — `Update on Save` reindexes synchronously during admin saves, causing timeouts on large catalogs291- **Separate Redis databases for cache, FPC, and sessions** — using database 0 for everything causes key conflicts and makes it impossible to flush only one type292- **Tag custom blocks with `IdentityInterface`** — this ensures Varnish and Magento's FPC invalidate the right pages when your data changes293- **Monitor indexer changelog table sizes** — a `catalog_product_price_cl` table with millions of rows indicates cron is not running; fix cron before the table size causes reindex timeouts294- **Use `bin/magento cache:clean` vs `cache:flush`** — `clean` removes only invalid cache entries (safe); `flush` nukes everything including other framework caches sharing the same Redis295- **Set distinct `id_prefix` per environment** — staging and production can share a Redis server if each has a unique prefix, preventing cache poisoning296- **Test Varnish with `curl -I` and `X-Cache` header** — a `HIT` response confirms Varnish is serving from cache; `MISS` means it's hitting Magento for every request297298## Common Pitfalls299300| Problem | Solution |301|---------|----------|302| Products invisible or wrong price after import | Run `bin/magento indexer:reindex` after bulk imports — programmatic product saves don't always trigger indexer if using `ResourceModel\Product::save` directly |303| Varnish serving stale pages for logged-in customers | Ensure `X-Magento-Vary` cookie is in the Varnish VCL hash; logged-in customers get a different vary value that bypasses the shared cache |304| Redis running out of memory | Set `maxmemory-policy allkeys-lru` in `redis.conf` for cache databases; for session database use `noeviction` to prevent silent session loss |305| FPC not invalidating after product save | Check that Varnish's PURGE ACL allows requests from the Magento server's IP; test with `curl -X PURGE http://varnish-ip/` |306| Full reindex takes hours and locks tables | Enable indexer batch sizes in `env.php` and run with `--mode realtime` for partial reindexing; schedule during off-peak windows |307| Cron indexers not running | Check `var/log/magento.cron.log` for errors; verify the system crontab entry runs `bin/magento cron:run` every minute as the web server user |308309## Related Skills310311- @magento-module-development312- @magento-graphql313- @magento-multi-store314- @caching-strategies315- @infrastructure-performance