Why Use Multi-Layer Caching?
Consider a PHP application where each database query takes about 1 second—users experience slow page loads. Adding caching helps, but is a single cache layer sufficient?
- In-memory cache is fast but volatile—it disappears when the service restarts.
- Redis offers persistence but incurs network latency.
- File-based cache is durable but limited by disk I/O speed.
The multi-layer caching strategy prioritizes speed while ensuring reliability:
- Check in-memory cache first (fastest)
- Fall back to Redis if not found (moderate speed)
- Finally query file system or database (slowest but most reliable)
This reduces load on slower storage layers while maintaining data availability.
Why the Decorator Pattern?
To build a chain like: Memory → Redis → File → Database, traditional inheritance leads to rigid, deeply nested classes:
// Problematic inheritance approach
class MemoryRedisFileCache extends FileCache { /* ... */ }
The decerator pattern offers a composable alternative—like nesting dolls:
$dataSource = new DatabaseAdapter();
$cacheStack = new InMemoryCache(
new RedisCache(
new FilesystemCache($dataSource, '/tmp/cache', 3600),
['host' => '127.0.0.1', 'port' => 6379],
'app_',
true
),
300, // TTL in seconds
1024 // Max items
);
Benefits include:
- Flexible composition: reorder layers as needed
- Single-responsibility classes: each handles only its own logic
- Easy extension: add new cache types without modifying existing code
Usage Example
After installing via Cmoposer (composer require hejunjie/tools):
use Hejunjie\Tools\Cache\Decorators;
$source = new UserDatabaseGateway();
$multiCache = new Decorators\InMemoryCache(
new Decorators\RedisCache(
new Decorators\FilesystemCache(
$source,
'/var/cache/app',
7200
),
['host' => 'redis.local', 'port' => 6379],
'user_data:',
false
),
600,
500
);
// Transparent read-through
$user = $multiCache->get('profile_456');
// Write-through: updates all layers
$multiCache->set('profile_456', ['name' => 'Li Hua']);
The get() method traverses layers from fastest to slowest, returning immediately on hit and propagating the result back up the chain. The set() method writes to all layers simultaneously.
Performance Comparison
| Metric | No Cache | Single-Layer | Three-Layer |
|---|---|---|---|
| Read Latency | 1.2s | 0.3s | 0.05ms |
| DB Load | 100% | 30% | <5% |
| Post-Restart Availability | Full | Lost | File-backed recovery |
Key Advantages
Modular Design: Easily reconfigure layers:
// Skip Redis during maintenance
$cache = new Decorators\InMemoryCache(
new Decorators\FilesystemCache($db, '/cache', 3600),
600,
500
);
Robustness Features:
- File cache uses advisory locking to prevent race conditions
- Redis client includes automatic reconnection logic
- In-memory cache enforces maximum item limits to avoid OOM errors
Operational Visibility: Built-in metrics:
print_r($cache->getMetrics());
/* Output:
[
'hits' => 2953,
'misses' => 47,
'hit_ratio' => 0.984,
'stored_items' => 1024
]
*/
Ideal Use Cases
- Frequently accessed read-heavy data (e.g., product catalogs)
- Low-latency API endpoints
- Systems under high database load
- Applications requiring graceful degradation after restarts