Yii2 Cache Architecture: Matching Backend Choice to Deployment Reality
A practical architecture note on configuring Yii2's caching component, choosing between file, APCu, Redis, and Memcached backends, and verifying invalidation under real deployment constraints.
17 Dec 2025, 17:44 UTC

The problem with a one-line cache config
Yii2 makes caching trivial to enable: add a cache component to the config array and call Yii::$app->cache. The difficulty is not turning it on, but keeping cached data correct when requests run concurrently, when more than one web server serves traffic, or when the storage layer underneath a dependency behaves differently than expected. The useful takeaway is that backend selection is an architecture decision driven by your deployment topology and staleness tolerance, not just by speed benchmarks.
Requirements before choosing a backend
Before writing configuration, define what the cache must guarantee:
- Bounded staleness. How long can a user see outdated data? A TTL (time to live) of 60 seconds is a different contract from a TTL of 5 minutes.
- Invalidation on write. When a record changes, the cache must refresh. Yii expresses this through dependency objects rather than manual key deletion.
- Shared state. If two or more web nodes serve requests, they must see the same cached values. A local file cache breaks this.
- Operational simplicity. A backend that requires a separate daemon and monitoring may be justified, but only if the requirements above demand it.
The smallest suitable design
For a single web server, the smallest suitable design is yii\caching\FileCache or yii\caching\ApcCache. FileCache needs no extra services and survives restarts. ApcCache is faster but is local to the PHP process pool and is lost on restart.
For two or more web servers, the smallest correct design is a shared backend such as yii\redis\Cache or yii\memcache\Cache. Both provide a single source of truth for cached values across nodes.
Configuration lives in the application config array. A minimal definition for a shared Redis backend looks like this:
'components' => [
'cache' => [
'class' => 'yii\redis\Cache',
'redis' => [
'host' => 'cache-host.internal',
'port' => 6379,
'database' => 0,
],
],
],
Replace cache-host.internal with your actual Redis endpoint. If Redis runs on the same host, use 127.0.0.1. The same component definition propagates everywhere Yii::$app->cache is requested, so swapping backends later is a config change, not a code change.
Declarative invalidation with dependencies
Yii’s query cache accepts a duration and an optional dependency. A dependency is an object that answers one question: has the underlying data changed? If the answer is yes, the cached value is discarded.
A common pattern uses yii\caching\DbDependency to tie a cached query to a database value:
$posts = Post::getDb()->cache(function ($db) {
return Post::find()->where(['status' => 1])->all();
}, 60, new DbDependency([
'sql' => 'SELECT MAX(updated_at) FROM post',
]));
Here, the cache entry lives for 60 seconds or until MAX(updated_at) changes, whichever comes first. The SQL runs on every request that checks the cache, so keep it cheap and indexed.
Trust and data boundaries
The cache is never the source of truth; the database is. Every dependency encodes a trust assumption about how change is detected.
DbDependencytrusts that the SQL result reflects the latest committed writes. If you read from a lagging replica, the dependency may miss a recent update.FileDependencytrusts the filesystem modification time. On NFS or certain cloud storage systems, mtime propagation can be delayed, so the cache may serve stale data past the intended boundary.TagDependencytrusts that your application explicitly invalidates tags on write. It is precise but requires discipline in the write path.
When the trust anchor fails, the TTL is the only remaining safety net. Size TTLs so that even a broken dependency produces acceptable staleness.
Operational checks
Verify the cache behaves as configured before relying on it in production.
Confirm the backend class
Run the following in a console command, a temporary controller action, or a bootstrap script after the application is initialized:
echo get_class(Yii::$app->cache);
Expected check: the output matches the class in your config, for example yii\redis\Cache. If it prints yii\caching\FileCache on a multi-server deployment, stop and fix the config before proceeding.
Measure TTL expiry
Cache a known value with a short duration, then read it before and after expiry:
Yii::$app->cache->set('health-check', 'value-1', 5);
// Immediately: Yii::$app->cache->get('health-check') returns 'value-1'
// After 6 seconds: returns false
Run this from the same environment that serves traffic. A result that survives past the TTL indicates a backend or clock problem.
Test dependency invalidation
Modify a database record that feeds a DbDependency, then confirm the next request rebuilds the cache. If the old value persists, check that your dependency SQL actually sees the new write and that you are not reading from a stale replica.
Risk: these checks write keys into the production cache. Use a namespaced key such as health-check:<unique-id> and delete it afterward.
Failure modes
- File cache contention. Multiple web servers writing to separate local file caches produce divergent data. Even with a shared network file system, locking and latency can cause contention.
- Delayed mtime. As noted above,
FileDependencycan fail silently under storage layers that delay metadata propagation. - Backend connection loss. If Redis or Memcached becomes unreachable, cache operations may throw exceptions. Wrap critical reads in try-catch blocks or configure a fallback if your application must degrade gracefully.
- Cache stampede. When a popular key expires, many concurrent requests may miss simultaneously and hammer the database. Mitigate with slight TTL jitter or a lock around rebuild logic.
- Unbounded growth. FileCache and APCu consume local disk or memory. Set eviction policies or monitor usage.
Conditions that would change the design
- Adding a second web server. Local file or APCu caches become incorrect. Migrate to Redis or Memcached before the second node serves traffic.
- Introducing read replicas.
DbDependencyqueries routed to a replica may lag. Either force dependency queries to the primary or accept longer staleness. - High write volume. If the underlying table changes faster than the TTL, caching may add overhead without benefit. Consider caching only derived or aggregated data.
- Cloud or NFS storage. Replace
FileDependencywith database-based dependencies or explicit tag invalidation. - Need for atomic operations. If you require atomic increments, locks, or pub/sub, Redis offers primitives that Memcached and FileCache do not.
Practical verification
After deployment, confirm the cache is active and correct by checking response times for a repeated query and by inspecting the backend directly. For Redis, use redis-cli on the cache host to list keys matching your application prefix. For FileCache, inspect the runtime/cache directory. A cache that is configured but empty under load usually means dependencies are invalidating too aggressively or TTLs are too short.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.