Magento 2's full page cache is one of its strongest performance features — when it works. But every week, we see stores where a simple product save triggers a 30-second Varnish flush and subsequent cache stampede. The culprit is almost never Varnish itself. It's cache tags.
This post covers how Magento 2 cache tags work, why broad tags destroy performance, and exactly how to audit and fix them.
How Cache Tags Work in Magento 2
Every cached page, block, and data fragment in Magento is tagged with identifiers. When a product changes, Magento invalidates all cache entries tagged with that product's ID. The tag system is hierarchical:
-
cat_p_123— specific product -
cat_p— all products -
cat_c_5— specific category -
cat_c— all categories -
cms_b_about_us— a CMS block -
cms_p— all CMS pages
These tags are stored alongside cached content and used during invalidation. When you call $cache->clean(["cat_p_123"]), every cache entry tagged with cat_p_123 is removed. This is elegant until someone tags a global block with cat_p, and saving any product flushes half your store.
The Invalidation Storm Problem
Here's what happens during a storm:
- Admin saves a simple product update (price change)
- Magento generates the invalidation list:
cat_p_456,cat_c(because the product is in categories),cat_p(from a badly written block) -
cat_pis too broad — it matches the product list page, layered navigation, homepage widgets, and every product detail page - Varnish receives 50,000
BANrequests - Store goes from sub-100ms response times to 2-5 seconds for the next 10 minutes while the cache rebuilds
We've seen this on a store with 80,000 SKUs. A single product save dropped cache hit rate from 94% to 12%.
Diagnosing Bad Cache Tags
Check Your Current Tags
Add this to any block template to inspect what tags are being applied:
$block->getCacheKeyInfo();
// Or for the full page:
$block->getIdentities();
For a full audit, intercept cache writes in development:
// In di.xml:
<type name="Magento\Framework\App\Cache\Type\Layout">
<plugin name="tag_audit" type="Vendor\Module\Plugin\CacheTagAudit"/>
</type>
// In CacheTagAudit.php:
public function beforeSave($subject, $data, $id, $tags = [], $lifeTime = null)
{
if (in_array('cat_p', $tags) || in_array('cat_c', $tags)) {
$this->logger->warning("Broad cache tag detected", [
'tags' => $tags,
'id' => $id
]);
}
return [$data, $id, $tags, $lifeTime];
}
Monitor Varnish BANs
Watch your Varnish logs during a product save. If you see hundreds of BAN requests for a single save, you have a tag granularity problem:
varnishlog -g request -q 'ReqMethod == "BAN"' | head -50
Profile with Blackfire
Use Blackfire's cache invalidation metric. A healthy store shows 5-20 invalidated entries per product save. If you see 5,000+, investigate immediately.
Common Sources of Broad Tags
1. Custom Blocks Without getIdentities()
When a block doesn't define getIdentities(), Magento falls back to broad defaults. Always implement it:
class CustomProductList extends \Magento\Framework\View\Element\Template
{
public function getIdentities()
{
$identities = [\Magento\Catalog\Model\Product::CACHE_TAG];
// ^ THIS IS THE PROBLEM — it adds 'cat_p' for every product
foreach ($this->getProducts() as $product) {
$identities[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId();
}
return array_unique($identities);
}
}
The fix: only tag with specific product IDs, never the global cat_p tag unless you genuinely want every product cache cleared.
2. CMS Blocks with Dynamic Content
A CMS block containing a product widget often gets tagged with cms_b only. When the underlying products change, the block doesn't invalidate. The knee-jerk fix is adding cat_p to the block's identities — which then causes storms. The correct approach is granular tagging:
public function getIdentities()
{
$identities = [\Magento\Cms\Model\Block::CACHE_TAG . '_' . $this->getBlockId()];
foreach ($this->getWidgetProducts() as $product) {
$identities[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId();
}
return array_unique($identities);
}
3. Third-Party Extensions
Many extensions add global tags carelessly. We've seen a popular review extension tag every page with cat_p because "products might have reviews." Audit every third-party module that touches caching:
- Search for
CACHE_TAGin the vendor directory - Look for blocks that return
[$this->_cacheTag]without product-specific IDs - Check observers that call
cleanTypeorcleanwith broad tags
4. Layered Navigation and Category Pages
Category pages are the most commonly over-tagged. A category with 500 products should be tagged with cat_c_5 plus each individual cat_p_XXX. But some implementations tag the entire page with just cat_p, meaning any product save anywhere invalidates every category page.
The correct tagging for a category page:
public function getIdentities()
{
$identities = [\Magento\Catalog\Model\Category::CACHE_TAG . '_' . $this->getCategoryId()];
foreach ($this->getLoadedProductCollection() as $product) {
$identities[] = \Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId();
}
return array_unique($identities);
}
This means saving product 456 only invalidates category pages where product 456 appears — not every category page in the store.
Fixing Tag Granularity: A Systematic Approach
Step 1: Map Your Cache Surface
Document what should invalidate what:
| Cache Entry | Should Invalidate On |
|---|---|
| Product detail page | That product's save |
| Category page | Category save + products in that category |
| Homepage | CMS/homepage config changes |
| Cart/Checkout | Quote changes |
| Search results | Indexer run |
If a product save invalidates your homepage, that's a bug.
Step 2: Fix Custom Modules
Go through your custom code and replace broad tags with specific ones:
// BAD — invalidates everything
return [\Magento\Catalog\Model\Product::CACHE_TAG];
// GOOD — only invalidates when this specific product changes
return [\Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId()];
// BEST — also include category-specific tags for list pages
return [
\Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId(),
\Magento\Catalog\Model\Category::CACHE_TAG . '_' . $categoryId
];
Step 3: Patch or Replace Bad Extensions
If a third-party extension uses broad tags, you have three options:
-
Override via plugin — intercept
getIdentities()and filter the tags - Contact the vendor — legitimate performance bugs deserve patches
- Replace the extension — some are beyond saving
A plugin to strip dangerous tags:
public function afterGetIdentities($subject, $result)
{
$forbidden = ['cat_p', 'cat_c', 'cms_p'];
return array_diff($result, $forbidden);
}
Use this carefully — only strip tags you know are wrong. Don't break legitimate invalidation.
Step 4: Add Cache Warming After Fixes
Once your tags are clean, you'll see fewer invalidations — but the ones that do happen will be more targeted. Set up cache warming for critical pages so that targeted invalidations don't leave cold cache entries:
# Warm the homepage and top categories after any product save
n98-magerun2.phar cache:warm --urls="/" --urls="/women.html" --urls="/men.html"
Varnish-Specific Considerations
Varnish's BAN-based invalidation is extremely fast for small tag sets, but degrades with large ones. Each BAN request checks every cached object's tags. If you're sending 10,000 BANs, Varnish spends more time processing invalidations than serving requests.
Use Purge Instead of BAN When Possible
For specific object invalidation (single URL), use PURGE:
curl -X PURGE https://your-store.com/product-123.html \
-H "X-Magento-Tags-Pattern: cat_p_123"
This is more efficient than BAN for single-object invalidation.
Increase Varnish's Ban Lurker Speed
In varnish.vcl, tune the ban lurker:
sub vcl_init {
new ban_lurker = debug.ban_lurker(
interval = 0.1s,
batch_size = 1000
);
}
This helps Varnish process pending BANs faster between requests.
Consider Soft Purge for Non-Critical Content
If you have content where serving stale data for 5 minutes is acceptable, use soft purge:
curl -X SOFTBAN https://your-store.com/
This marks content as stale but doesn't immediately remove it — new requests trigger a background refresh.
Measuring the Impact
After fixing tags, measure these metrics over a week:
- Cache hit rate — should increase (target 90%+ for product pages)
- Varnish BAN count per product save — should drop from thousands to <50
- 90th percentile response time after product updates — should drop dramatically
- CPU usage during peak traffic — should decrease due to fewer cache rebuilds
Use this query to check your current tag distribution:
# Check how many unique tags Varnish is tracking
varnishstat -1 | grep n_ban
# Look for n_ban_add and n_ban_obj_test — high numbers indicate tag bloat
Summary
Cache tag granularity is the silent performance killer in Magento 2. A single broad tag can turn a routine product update into a site-wide performance incident. The fix is systematic:
- Audit your cache tags in development
- Fix custom blocks to use specific product/category IDs
- Patch or replace third-party extensions with bad tag hygiene
- Monitor Varnish BAN counts in production
- Add cache warming for critical paths
Stores that fix their tag strategy typically see 40-60% reduction in post-save response time spikes and a 15-20% increase in overall cache hit rate. It's low-hanging fruit — but only if you know to look for it.
Top comments (0)