Optimize PHP EasyCache for maximum performance.
- Tier Selection
- Compression Strategy
- Serialization Performance
- TTL Optimization
- SWR Best Practices
- File Storage Optimization
- Redis Optimization
- Benchmarks
High-traffic web applications:
$cache = new MultiTierCache([
new ApcuStorage('app:'), // L1: ~0.01ms
new RedisStorage($redis), // L2: ~1-2ms
new FileStorage('/cache') // L3: ~5-10ms
]);API servers with shared cache:
$cache = new MultiTierCache([
new RedisStorage($redis), // L1: Shared cache
new FileStorage('/cache') // L2: Fallback
]);Single server applications:
$cache = new MultiTierCache([
new ApcuStorage('app:'), // L1: Fast
new FileStorage('/cache') // L2: Persistent
]);Database-heavy applications:
$cache = new MultiTierCache([
new ApcuStorage('app:'), // L1: Fast
new PdoStorage($pdo) // L2: Same DB connection
]);| Backend | Read Speed | Write Speed | Shared | Persistent |
|---|---|---|---|---|
| APCu | Fastest | Fastest | Yes* | No |
| Redis | Fast | Fast | Yes | Optional |
| File | Medium | Medium | No | Yes |
| PDO | Slow | Slow | Yes | Yes |
*Shared between PHP-FPM workers on same server
Use compression when:
- Storing large payloads (>10KB)
- Network bandwidth is limited (Redis/PDO)
- Storage space is limited
- Data is compressible (text, JSON, HTML)
Skip compression when:
- Storing small values (<1KB)
- Data is already compressed (images, video)
- CPU is the bottleneck
- Using only APCu (memory is fast)
1KB payload:
None: 0.001ms
Gzip: 0.050ms (50x slower, 40% size)
Zstd: 0.030ms (30x slower, 45% size)
10KB payload:
None: 0.005ms
Gzip: 0.200ms (40x slower, 30% size)
Zstd: 0.120ms (24x slower, 35% size)
100KB payload:
None: 0.050ms
Gzip: 1.500ms (30x slower, 25% size)
Zstd: 0.800ms (16x slower, 28% size)
Gzip Levels:
// Fast compression (recommended for most cases)
new GzipCompressor(1); // 80% of max compression, 3x faster
// Balanced (default)
new GzipCompressor(5); // 95% of max compression, 1.5x faster
// Maximum compression
new GzipCompressor(9); // 100% compression, slowestRecommendation: Use level 3-5 for most applications.
// For text-heavy data (HTML, JSON)
$cache = new MultiTierCache(
[$apcu, $redis],
new JsonSerializer(),
new GzipCompressor(3) // 3x faster than level 9
);
// For mixed data
$cache = new MultiTierCache(
[$apcu, $redis],
new NativeSerializer(),
new NullCompressor() // No compression overhead
);| Serializer | Encode | Decode | Size | Objects |
|---|---|---|---|---|
| Native | Fast | Fast | Large | Yes |
| JSON | Faster | Faster | Small | No |
Simple array (1000 items):
Native: encode=0.15ms, decode=0.20ms, size=50KB
JSON: encode=0.08ms, decode=0.10ms, size=30KB
Complex nested structure:
Native: encode=0.50ms, decode=0.60ms, size=150KB
JSON: encode=0.30ms, decode=0.35ms, size=100KB
Use NativeSerializer when:
- You need to cache objects
- Data structure is complex
- You're only using PHP
Use JsonSerializer when:
- Data is simple (arrays, scalars)
- Size matters (with compression)
- Portability is important
Hot data (frequently accessed):
$cache->set('homepage_data', $data, 60); // 1 minuteWarm data (moderately accessed):
$cache->set('product_list', $data, 300); // 5 minutesCold data (rarely changes):
$cache->set('site_config', $data, 3600); // 1 hourStatic data:
$cache->set('country_list', $data, 86400); // 1 dayTTL Hit Rate Staleness
1min 99% Very fresh
5min 95% Fresh
15min 90% Acceptable
1hour 80% Potentially stale
1day 60% Likely stale
Formula: Optimal TTL ≈ Update Frequency × 2
Perfect for:
- External API calls
- Expensive database queries
- Complex calculations
- Frequently accessed, slowly changing data
Fast-changing data (news, prices):
$cache->getOrSetSWR(
'news_feed',
fn() => fetchNews(),
ttl: 60, // 1 minute fresh
swrSeconds: 30, // 30 seconds stale
staleIfErrorSeconds: 300 // 5 minutes if error
);Slow-changing data (user profiles):
$cache->getOrSetSWR(
'user_profile',
fn() => loadProfile(),
ttl: 3600, // 1 hour fresh
swrSeconds: 1800, // 30 minutes stale
staleIfErrorSeconds: 86400 // 1 day if error
);Use defer mode for better response times:
// Sync mode: ~100ms (includes refresh)
$data = $cache->getOrSetSWR(
'key',
fn() => expensiveCall(), // 50ms
300, 60, 300,
['mode' => 'sync']
);
// Defer mode: ~1ms (serves stale, refreshes after response)
$data = $cache->getOrSetSWR(
'key',
fn() => expensiveCall(), // Runs after response
300, 60, 300,
['mode' => 'defer'] // Requires fastcgi_finish_request()
);Impact on performance:
10,000 files:
Shards=0: ls=2000ms, find=1500ms
Shards=2: ls=50ms, find=30ms
Shards=3: ls=10ms, find=8ms
100,000 files:
Shards=0: Not practical
Shards=2: ls=500ms, find=300ms
Shards=3: ls=100ms, find=60ms
Recommendation:
- 0 shards: < 1,000 files
- 2 shards: 1,000 - 100,000 files (recommended)
- 3 shards: > 100,000 files
Performance comparison:
ext4: Read=100MB/s, Write=80MB/s (recommended)
xfs: Read=120MB/s, Write=90MB/s (best for large files)
btrfs: Read=90MB/s, Write=70MB/s
tmpfs: Read=2GB/s, Write=2GB/s (RAM disk - not persistent)
For cache directory:
# /etc/fstab
/dev/sdb1 /var/cache ext4 noatime,nodiratime 0 2Benefits:
noatime: Don't update access time (30% faster reads)nodiratime: Don't update directory access time
Use persistent connections:
$redis = new Redis();
$redis->pconnect('127.0.0.1', 6379); // Persistent
$storage = new RedisStorage($redis);In redis.conf:
# Memory management
maxmemory 2gb
maxmemory-policy allkeys-lru
# Performance
tcp-backlog 511
timeout 0
tcp-keepalive 300
# Persistence (optional for cache)
save "" # Disable RDB
appendonly no # Disable AOF
For bulk operations, use pipelining:
// Without pipeline: 100 operations = 100 network calls
for ($i = 0; $i < 100; $i++) {
$cache->set("key_{$i}", $data, 3600); // 100ms
}
// With pipeline: 100 operations = 1 network call
$cache->setMultiple($dataArray, 3600); // 10msAPCu: 0.01ms
Redis: 1.2ms (local)
Redis: 15ms (remote)
File (SSD): 5ms
File (HDD): 25ms
PDO (MySQL): 8ms
PDO (SQLite): 3ms
APCu: 0.01ms
Redis: 1.5ms
File (SSD): 8ms
File (HDD): 35ms
PDO: 12ms
Cache hit in tier:
L1 (APCu): 0.01ms
L2 (Redis): 1.2ms + backfill(0.01ms) = 1.21ms
L3 (File): 5ms + backfill(1.21ms) = 6.21ms
Miss: Producer time + 6.21ms
10KB payload:
No compression: 1.2ms (read) + 1.5ms (write)
Gzip level 3: 1.4ms (read) + 1.7ms (write)
Zstd level 3: 1.3ms (read) + 1.6ms (write)
Network savings (Redis): 70% less bandwidth
Storage savings (File): 65% less disk space
Before:
$data = $db->query("EXPENSIVE QUERY")->fetchAll(); // 250msAfter:
$cache = new MultiTierCache([
new ApcuStorage(),
new RedisStorage($redis)
], new JsonSerializer(), new GzipCompressor(3));
$data = $cache->getOrSetSWR(
'api_data',
fn() => $db->query("EXPENSIVE QUERY")->fetchAll(),
300, 60, 1800,
['mode' => 'defer']
);
// First request: 250ms
// Cached requests: 0.01ms (99.996% faster)
// Stale requests: 0.01ms + background refreshResult: 99.996% response time improvement
Before:
$products = getProducts(); // 500ms (joins, calculations)After:
$cache = new MultiTierCache([
new ApcuStorage(),
new FileStorage('/cache', '.cache', 2)
], new NativeSerializer(), new NullCompressor());
$products = $cache->get('products_catalog');
if (!$products) {
$products = getProducts();
$cache->set('products_catalog', $products, 600);
}
// Cached: 0.01ms (50,000x faster)use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('cache');
$logger->pushHandler(new StreamHandler('/var/log/cache.log'));
$cache = new MultiTierCache($tiers, $serializer, $compressor, 3600, $logger);- Hit rate per tier
- Average response time
- Cache size
- Eviction rate
- Error rate
class CacheMetrics
{
private int $hits = 0;
private int $misses = 0;
public function get($key)
{
$value = $this->cache->get($key);
if ($value !== null) {
$this->hits++;
} else {
$this->misses++;
}
return $value;
}
public function getHitRate(): float
{
$total = $this->hits + $this->misses;
return $total > 0 ? $this->hits / $total : 0;
}
}- Use APCu as first tier - Free 100x speed boost
- Enable compression for large data - Save bandwidth and storage
- Use appropriate TTLs - Balance freshness and hit rate
- Implement SWR - Eliminate cache stampedes
- Use defer mode - Improve response times
- Enable directory sharding - Better file system performance
- Use persistent Redis connections - Reduce connection overhead
- Batch operations - Use setMultiple/getMultiple
- Profile your cache - Measure before optimizing
- Monitor hit rates - Adjust TTLs based on data
Optimal cache configuration depends on your specific use case. Start with recommended defaults and adjust based on your metrics.
General recommendation for most applications:
$cache = new MultiTierCache(
[new ApcuStorage(), new RedisStorage($redis)],
new NativeSerializer(),
new GzipCompressor(3),
600
);For more help, see README.md and EXAMPLES.md.