Full Page Cache: Understanding Tag Invalidation in Magento
AI generated
SET
TTL
Redis · Magento · Performance · Caching
Full Page Cache: Understanding Tag Invalidation in Magento
cache:clean, cache:flush and X-Magento-Tags in detail

Working tag invalidation is the basic prerequisite for the Magento full page cache to stay usable at all, without serving stale prices or stock levels. Understanding how X-Magento-Tags gets set, how Redis internally manages these tags, and why cache:clean does something fundamentally different from cache:flush lets you fix invalidation problems precisely instead of resorting to a blanket cache wipe.

15 min read X-Magento-Tags · cache:clean · cache:flush · debugging Redis 7.x · Magento 2.4.8 · PHP 8.4

1. How the Magento full page cache works

The full page cache (FPC) stores the fully rendered HTML output of a page so subsequent requests can be served directly from cache without re-executing layout, blocks and database queries. For static, rarely changing pages such as CMS pages or category overviews this brings massive speed gains. The problem: a product page sitting in cache must not stay valid forever once price, stock, or content changes, otherwise the shop shows outdated information.

This is exactly where tag invalidation comes in. Instead of clearing the entire FPC on every change, which would defeat the purpose of caching, Magento marks each cached page with a set of tags describing which entities the page depends on. When one of those entities changes, Magento triggers a targeted invalidation of exactly those pages while leaving every other cache entry untouched.

With Redis as the full page cache backend, this tag invalidation happens through the same Cm_Cache_Backend_Redis class used for the regular cache, typically with its own database index. The interplay of HTTP response headers, Redis sets and Magento's cache observer events forms the technical foundation explained step by step throughout the rest of this article.

2. Cache tags: concept and purpose of tag invalidation

A cache tag is a string expressing a relationship between a cache entry and a business entity, for example cat_p_123 for product id 123 or cat_c_45 for category id 45. Every cached page can carry multiple tags: a product page typically carries the product tag, tags for all assigned categories, and a generic FPC tag. This combination ensures both product specific and category related changes reach the correct pages.

Tag invalidation is triggered by Magento's indexer and observer system. When an admin saves a product, an event fires that removes exactly the affected cache entries through Magento\Framework\App\CacheInterface::clean() using the MATCHING_TAG mode. The decisive advantage over time based expiry: pages stay in cache as long as nothing in their data changes, but can react immediately once a relevant change happens.

3. How tags are stored in Redis

Internally, the Redis backend implements tag invalidation using sets: for every tag there is a Redis set under the key pattern zc:ta:TAG_NAME, containing the ids of every cache entry associated with that tag. When storing a page with several tags, Magento adds the cache id to each of the associated tag sets. When calling clean(MATCHING_TAG, ['cat_p_123']), Redis reads the corresponding set and deletes every cache entry it contains, one by one.

This set based structure is the reason tag invalidation in Redis is so much faster than a file based cache with directory scans: finding affected entries happens through a direct set lookup with complexity constant to linear in the number of tag members, instead of a full scan across every cache file. For large catalogs with hundreds of thousands of products, this difference becomes the decisive performance factor.


# Inspect full page cache tag sets directly in Redis (database index for page_cache)
redis-cli -n 1 SMEMBERS "zc:ta:cat_p_123"
redis-cli -n 1 SCARD "zc:ta:cat_p_123"

# List all cache entries associated with a category tag
redis-cli -n 1 SMEMBERS "zc:ta:cat_c_45"

# Check whether a specific cache entry still exists after invalidation
redis-cli -n 1 EXISTS "zc:k:CACHE_ID_HERE"

4. cache:clean versus cache:flush: the crucial difference

bin/magento cache:clean deletes only cache entries marked invalid, or, with a given tag filter, exactly the matching entries. This is the fine grained variant of tag invalidation and matches exactly what Magento triggers automatically on a product change. After cache:clean, every unaffected cache entry remains fully intact.

bin/magento cache:flush, by contrast, unconditionally clears the entire cache storage of the given cache types, regardless of tags. With Redis as the backend, this essentially corresponds to a FLUSHDB on the affected database. The difference is fundamental: cache:flush is a reset command, while cache:clean represents the precise tag invalidation that production operation should rely on. Anyone who routinely uses cache:flush instead of cache:clean throws away the entire performance benefit of the full page cache with every cleanup.


# Targeted cleanup: removes only entries matching the given tags (fast, safe)
bin/magento cache:clean full_page

# Full reset: unconditionally clears the entire cache database (use sparingly)
bin/magento cache:flush full_page

# Clean by explicit tag via the Redis CLI, mirroring what Magento does internally
redis-cli -n 1 SMEMBERS "zc:ta:cat_p_123" | xargs -I{} redis-cli -n 1 DEL "zc:k:{}"

# Verify how many keys remain in the full page cache database afterward
redis-cli -n 1 DBSIZE

When Varnish or a similar HTTP reverse proxy sits in front of Magento, Magento communicates a response's relevant tags via the HTTP header X-Magento-Tags. Varnish reads this header when serving a page and internally maintains its own mapping of tags to cached objects, independent of the Redis backend. On a change, Magento sends an HTTP PURGE request with the same tags to Varnish, which then applies its own ban rules.

Important to understand: without Varnish, tag invalidation operates exclusively within Redis via the sets described in the previous section, and the X-Magento-Tags header no longer has any practical effect. With Varnish, two parallel invalidation mechanisms exist: one in Redis for the actual full page cache storage, and one in Varnish for the upstream HTTP cache. Both need to work in sync, otherwise situations arise where Varnish serves a stale page even though the Redis cache has already been correctly invalidated.


# Inspect the X-Magento-Tags header on a response (only meaningful with Varnish upstream)
curl -sI https://shop.example.com/product-page.html | grep -i "x-magento-tags"

# Confirm cache origin: HIT means a cached version was served
curl -sI https://shop.example.com/product-page.html | grep -i "x-magento-cache-debug"

6. Debugging: why a page failed to invalidate

The most common support case around tag invalidation: a product was changed, but the frontend page still shows old data. The first debugging step is to check the HTTP header X-Magento-Cache-Debug in the browser developer tools, which shows HIT or MISS. On HIT, a cached version is actually being served, confirming that invalidation failed rather than this being an unrelated data problem.

The second step is to check directly in Redis whether the expected tag exists at all and whether the affected cache entry is still listed in the corresponding tag set. If the tag is missing from the set despite the page originally having been stored with that tag, it points to a problem setting the tags, usually caused by a custom block that does not correctly implement getCacheKeyInfo() or fails to pass the relevant tags through to getIdentities().


<?php
declare(strict_types=1);

namespace Mironsoft\Catalog\Block;

use Magento\Catalog\Block\Product\View as ProductView;

/**
 * Example block that must correctly expose cache identities
 * so tag-based invalidation can find and clear it.
 */
class CustomProductBlock extends ProductView
{
    /**
     * Returns cache identities used by Magento's tag-based invalidation.
     *
     * @return string[]
     */
    public function getIdentities(): array
    {
        $product = $this->getProduct();

        // Missing this merge is the most common cause of stale FPC pages
        return array_merge(
            parent::getIdentities(),
            $product ? [\Magento\Catalog\Model\Product::CACHE_TAG . '_' . $product->getId()] : []
        );
    }
}

7. Common causes of broken invalidation

By far the most common cause of broken tag invalidation is a custom block or a plugin that overrides getIdentities() but forgets to merge in parent::getIdentities() via array_merge. This causes the block to lose every default tag Magento normally sets, and the page no longer gets invalidated on regular product changes even though it remains in the cache.

A second common cause is asynchronous processing via message queues: when product updates are processed through async.operations.all, the actual tag invalidation only happens once the consumer has processed the message. If the consumer is not running or has fallen behind, the cache remains unchanged despite the saved change. A third case involves third party modules that implement their own, non standard caching strategies and ignore the core system's tag conventions in the process.

8. redis-cli commands for tag analysis

A handful of redis-cli commands are enough for systematic analysis of tag invalidation in production. SMEMBERS shows the members of a tag set, SCARD returns just the count, which is more performant for very large sets to estimate up front how extensive an invalidation will be. TTL on a cache key shows whether an entry is still active at all or has already been removed by regular TTL based expiry.

For deeper analysis, redis-cli --scan --pattern "zc:ta:cat_p_*" helps find every product related tag set without using the blocking KEYS command in production. SCAN iterates incrementally using a cursor, which matters when there is a large number of keys, so as not to slow down the Redis server with a single, long blocking command.


# Safe, non-blocking way to find product-related tag sets in production
redis-cli --scan --pattern "zc:ta:cat_p_*" | head -20

# Count members of a specific tag set without loading all of them
redis-cli SCARD "zc:ta:cat_p_123"

# Check whether a cache entry has already expired via TTL
redis-cli TTL "zc:k:CACHE_ID_HERE"

9. clean versus flush versus Varnish ban compared

Depending on the situation, a different invalidation method is appropriate. The table below ranks the options by precision and use case.

Method Scope Performance impact Use case
cache:clean (tag) Only affected entries Minimal, cache stays mostly warm Automatic invalidation in normal operation
cache:flush Entire cache type High, full recomputation required Only after deployments or emergencies
Varnish PURGE/ban Only affected pages in Varnish Minimal, works alongside Redis When Varnish sits upstream
Manual FLUSHDB Entire Redis database Very high, affects other roles if database separation is wrong Only as an absolute exception

In practice, cache:clean with a tag filter should be the absolute rule, while cache:flush stays limited to rare cases such as large deployments or inconsistent cache states. Anyone reaching for cache:flush regularly because tag invalidation is not working reliably should look for the root cause in custom code instead of masking the symptom with a full reset.

10. Summary

Tag invalidation is the heart of a working full page cache: it allows pages to stay in cache for a long time while still reacting immediately to relevant data changes. In Redis this is implemented through tag sets under the prefix zc:ta:, enabling efficient, targeted removal of affected entries without clearing the entire cache.

The difference between cache:clean and cache:flush is crucial for production operation: the former is precise tag invalidation, the latter an unconditional reset. Anyone debugging invalidation problems should first check the X-Magento-Cache-Debug header, then look directly in Redis to see whether the expected tag is in the right set, and finally check custom blocks for a correctly implemented getIdentities() method.

Tag Invalidation in the Magento Full Page Cache - The Essentials at a Glance

Tag sets in Redis

Prefix zc:ta:TAG_NAME stores every cache id linked to a tag.

clean, not flush

cache:clean is precise invalidation, cache:flush is an unconditional reset. Always use clean in normal operation.

Check getIdentities()

Custom blocks must merge in parent::getIdentities(), otherwise default tags get lost.

Use the debug header

X-Magento-Cache-Debug shows HIT or MISS and is the first step of any troubleshooting.

11. FAQ: Tag Invalidation in the Magento Full Page Cache

1Difference between cache:clean and cache:flush?
clean targets matching entries, flush clears everything unconditionally. Always use clean for normal operation.
2How does Redis store cache tags?
As sets under zc:ta:TAG_NAME with the ids of every linked cache entry.
3Why does a product page fail to invalidate?
Usually a missing parent::getIdentities() in a custom block, dropping default tags.
4What does X-Magento-Tags do?
Communicates relevant tags to Varnish. Without Varnish, invalidation runs only through Redis sets.
5Check a page's cache origin?
X-Magento-Cache-Debug header shows HIT or MISS, the first debugging step.
6Why not use flush routinely?
Destroys unaffected warm entries too, creates unnecessary load from recomputation.
7Role of message queues?
With async.operations.all, invalidation only happens after consumer processing. A delayed consumer delays invalidation.
8Find cache entries for a tag?
redis-cli SMEMBERS zc:ta:TAG_NAME, SCARD for just the count on large sets.
9KEYS or SCAN in production?
Always SCAN, cursor based and non blocking. Avoid KEYS in production.
10Redis invalidation versus Varnish ban?
Two independent, parallel mechanisms: Redis for the FPC storage, Varnish for the upstream HTTP cache.