keeping Redis data directly in application memory
Every Redis access costs a network round trip, even when the value has not changed since the last query. Client-side caching flips this principle around: frequently read values move into the application's local memory, and Redis actively reports through the tracking protocol as soon as a cached key changes. That eliminates the round trip for many reads entirely, without the application risking stale data. How the tracking protocol works technically, where broadcasting and default mode differ, and where the practical limits lie, is what this article covers.
Table of Contents
- 1. The baseline problem: every read costs a round trip
- 2. How the RESP3 tracking protocol sends invalidating messages
- 3. Default mode: server-side tracking per client
- 4. Broadcasting mode: invalidation by prefix instead of per key
- 5. Application integration using a PHP client as an example
- 6. Practical limit: memory usage on the client side
- 7. Suitability for hot keys: where tracking has the biggest impact
- 8. Error handling: connection drops and missed invalidations
- 9. Rollout strategy: migrating individual data classes step by step
- 10. Summary
- 11. FAQ
1. The baseline problem: every read costs a round trip
A classic Redis access always travels over the network, even when the requested value has sat unchanged in the Redis server's memory for milliseconds. On a product detail page that reads several cached fragments from Redis per call, such as pricing data, availability, and category assignment, these round trips quickly add up to noticeable latency, especially when the application and the Redis server are not on the same machine and every round trip carries network overhead and TLS handshake cost.
An obvious idea is to keep frequently read values directly in the application's own memory and only consult Redis on a cache miss. The catch is classic cache invalidation: how does the application know that a locally held value has since become stale because another process changed the underlying key in Redis? This is exactly where the tracking protocol comes in.
2. How the RESP3 tracking protocol sends invalidating messages
With CLIENT TRACKING ON, a connection enables server-side tracking for itself. From that point on, Redis remembers which keys this client requested through read commands like GET or MGET and internally builds a mapping from key to interested clients. When one of these keys changes, whether through SET, DEL, EXPIRE, or any other write command, Redis proactively sends an invalidation message to the affected client without the client having to ask.
Technically this relies on the RESP3 protocol, which, unlike RESP2, supports genuine push messages that can arrive outside the normal request-response cycle over the same connection. The application must use its client library so that it recognizes incoming invalidation messages, whether on a separate RESP3 push connection or directly on the main connection, and immediately removes the affected local cache entry.
# Enable tracking for the current connection (requires RESP3)
redis-cli -3
> CLIENT TRACKING ON
OK
> GET product:4711:price
"49.90"
# When another client changes the key, a push message arrives:
# >3
# $10
# invalidate
# *1
# $18
# product:4711:price
3. Default mode: server-side tracking per client
In default mode, Redis individually remembers, for each tracked client, which keys it last read. This mapping lives in an invalidation table in server memory and grows with the number of distinct keys that active clients query. The advantage is precision: a client only receives invalidations for keys it actually read itself, which keeps message traffic minimal.
The downside is memory usage on the Redis server itself, which grows with the number of concurrently tracked clients and the size of their distinct key sets. With very many active connections each reading a broad and varied set of keys, this table can create noticeable memory pressure, which is why default mode suits a manageable number of long-lived connections with a stable access pattern best, such as application server processes with a permanently open Redis connection.
4. Broadcasting mode: invalidation by prefix instead of per key
Broadcasting mode, enabled via CLIENT TRACKING ON BCAST PREFIX product:, skips individual per-client key tracking entirely. Instead, the client subscribes to one or more key prefixes, and Redis sends invalidation messages for every changed key within those prefixes to all clients subscribed to that prefix, regardless of whether the individual client ever actually read that particular key.
This drastically reduces server-side memory requirements, since no per-key-to-per-client mapping needs to be maintained, only a list of subscribed prefixes. The trade-off is potentially higher message volume: with a broad prefix like product:, clients also receive invalidations for products they never loaded into their local cache at all. For workloads with clearly scoped, topically matching prefixes and many concurrent clients, broadcasting is therefore often the more practical choice.
# Enable broadcasting mode with two prefixes
redis-cli -3
> CLIENT TRACKING ON BCAST PREFIX product: PREFIX category:
OK
# Optional: use a separate redirect connection for push messages
> CLIENT TRACKING ON REDIRECT 42 BCAST PREFIX product:
OK
5. Application integration using a PHP client as an example
In practice, tracking is usually wrapped in a small cache facade: a local array or a PSR-16-compatible in-memory cache holds values, while a dedicated Redis connection in RESP3 mode listens for invalidation messages in the background and removes the matching local entry on every incoming message. For PHP-based applications with a per-request process model, tracking pays off mainly when a long-lived worker process, for example under RoadRunner or Swoole, serves multiple requests in sequence using the same local cache, since classic PHP-FPM discards process memory after every request anyway.
For short-lived PHP-FPM processes, tracking therefore brings barely any benefit, because the local cache never outlives a single request in the first place. It looks different for daemon processes, such as a consumer that runs continuously and repeatedly reads the same reference data, like category trees or pricing rules that change rarely but unpredictably.
{
"pattern": "long-lived worker process",
"local_cache": "in-memory array keyed by redis key",
"tracking_connection": "separate RESP3 connection, BCAST mode",
"on_invalidate_push": "remove matching key from local_cache",
"on_cache_miss": "fetch from redis, populate local_cache"
}
6. Practical limit: memory usage on the client side
Client-side caching shifts memory demand from the central Redis server onto every single application process. In horizontally scaled applications with many parallel worker processes, this means the same cached value can potentially sit separately in every process's memory instead of centrally once in Redis. For small, frequently read reference data this is unproblematic, but for large objects or very many distinct keys, memory usage across all processes can add up quickly, trading the saved network round trip for increased memory pressure on the application servers.
A sensible limit is therefore a hard cap on the number of locally held entries, combined with an LRU-style eviction strategy in the local cache itself, so tracking only applies to actually repeatedly read, compact values rather than accidentally becoming an unbounded shadow copy of the entire Redis instance.
7. Suitability for hot keys: where tracking has the biggest impact
Client-side caching delivers the biggest benefit for classic hot keys: keys that very many clients read extremely often but write only rarely. Typical examples are global configuration values, feature flags, current exchange rates, or category trees needed on every page but perhaps only changing a few times a day. For such values, tracking massively reduces server load, because a large share of reads never reach the Redis server at all and are served directly from application memory instead.
For values with high write frequency, such as intermediate cart states or live stock levels, the effect reverses: frequent invalidations mean the local cache entry rarely stays valid long enough to genuinely save the round trip, while additional message traffic for the invalidation itself is generated at the same time. Tracking is therefore suited specifically to read-dominant, low-change data, not as a blanket replacement for every cache access.
8. Error handling: connection drops and missed invalidations
A central risk in client-side caching is connection loss: if the tracking connection drops before the client could receive an invalidation message, the application has no way of knowing whether its local cache is still accurate. Redis handles this case with a special message carrying no key information, signaling that invalidations may have been missed. A robust implementation must react to this message by flushing the entire local cache rather than continuing selectively.
After a reconnect, the application should explicitly re-enable tracking and, in broadcasting mode, re-register its subscribed prefixes, since these are not automatically preserved across a reconnection. Anyone neglecting this error handling risks stale data in the local cache that, in the worst case, persists considerably longer than it would have without client-side caching at all.
9. Rollout strategy: migrating individual data classes step by step
A blanket switch of the entire application to client-side caching rarely makes sense. A step-by-step approach has proven effective: first identify concrete hot-key candidates using access statistics, for instance through redis-cli --hotkeys or custom monitoring, enable tracking on a trial basis for exactly that data class, and measure the effect on latency and server load under realistic traffic before rolling out further data classes.
It is also worth checking the client library in use: not every Redis library supports RESP3 tracking equally well, some already wrap push message handling conveniently, others require manual handling of the redirect connection. This check should come first in any rollout, before tracking is used in production at scale.
| Criterion | Default mode | Broadcasting mode | Practical relevance |
|---|---|---|---|
| Tracking scope | Per client and key | Per subscribed prefix | Broadcasting saves server memory |
| Server memory | Grows with clients × keys | Only a prefix list per client | Default risky with many clients |
| Message volume | Only relevant invalidations | Can include noise | Broadcasting inefficient with broad prefixes |
| Suitability | Few long-lived connections | Many clients, clear prefixes | Choose mode based on topology |
| Setup effort | Simple to start | Requires prefix design | Broadcasting needs planning |
Mironsoft
Cache layer setup and Magento Redis integration
Magento cache that isn't quite working or is misconfigured?
We set up Redis as a cache and session backend for Magento cleanly, tune memory usage and eviction strategies, and make sure full page cache and session storage work together reliably.
Redis Setup
Configure the cache, session, and FPC backend production-ready for Magento.
Memory Tuning
Match memory usage and eviction policies to the shop's actual load.
High Availability Setup
Set up Redis Sentinel or Cluster for resilient Magento environments.
10. Summary
Client-Side Caching With Tracking: The Essentials at a Glance
Basic principle
RESP3 push messages actively inform clients about changes to previously read keys, so local copies are invalidated instead of blindly reused.
Two modes
Default mode tracks precisely per client and key, broadcasting mode tracks more efficiently per prefix at the cost of potentially extra messages.
Limits
Memory usage shifts to every application process individually, connection drops require robust cache flushing instead of selective continuation.
Best fit
Read-dominant hot keys with rare changes, like feature flags or category trees, benefit the most; write-heavy data barely benefits at all.