Redis Eviction Policy in Laravel: Strategy and Implementation
Introduction
Redis is an in-memory data store with finite memory capacity. When Redis reaches its maximum memory limit, it must decide which keys to remove to make space for new data. This process is called eviction, and understanding eviction policies is crucial for building reliable Laravel applications that depend on Redis for caching, sessions, and queuing.
This article explores Redis eviction policies, their patterns, strategies for implementation in Laravel, and best practices for production environments.
What is Redis Eviction Policy?
An eviction policy is a set of rules that determines which keys Redis should delete when it runs out of memory. Without an eviction policy, Redis would return "out of memory" errors and fail to store new data. The eviction policy ensures graceful degradation and continuous operation under memory pressure.
Redis Eviction Policy Types
Redis provides eight built-in eviction policies, grouped into three categories:
Expiration-Based Policies
These policies only evict keys that have a time-to-live (TTL) set:
- volatile-lru: Evicts least recently used keys among those with TTL
- volatile-lfu: Evicts least frequently used keys among those with TTL
- volatile-ttl: Evicts keys with TTL, prioritizing those closest to expiration
- volatile-random: Randomly evicts keys with TTL
Evict All Policies
These policies consider all keys, regardless of TTL:
- allkeys-lru: Evicts least recently used keys (most common choice)
- allkeys-lfu: Evicts least frequently used keys
- allkeys-random: Randomly evicts any key
No Eviction
- noeviction: Disables eviction entirely (default); Redis returns errors when memory is full
Understanding the Patterns
LRU (Least Recently Used)
LRU eviction tracks the last access time of each key. Keys accessed longest ago are evicted first. This works well when recently accessed data is more likely to be needed again.
Scenario: A Laravel application caching user session data. If a user hasn't been active in hours, their session data is less valuable and can be evicted.
LFU (Least Frequently Used)
LFU tracks how often each key is accessed. Keys used least frequently are evicted first. This is superior for workloads where frequently-accessed data should persist.
Scenario: A content delivery system where popular articles are cached and infrequently-viewed content can be cleared.
TTL-Based
This strategy prioritizes evicting data closest to its expiration time, minimizing waste of expired data consuming memory.
Scenario: Cache entries with varying TTLs; those about to expire anyway are evicted first.
Configuring Eviction Policy in Redis
The eviction policy is set in the Redis configuration file (redis.conf
) or via runtime commands:
# In redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru
Or at runtime:
CONFIG SET maxmemory 256000000
CONFIG SET maxmemory-policy allkeys-lru
View current settings:
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
Implementing Eviction Policy Strategy in Laravel
1. Basic Cache Configuration with Eviction Strategy
Set up your Laravel cache to work with Redis and an appropriate eviction policy:
// config/cache.php
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'ttl' => 3600, // 1 hour default TTL
],
// config/database.php
'redis' => [
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 1, // Separate database for cache
],
],
2. Implementing LRU Strategy in Laravel
<?php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class CacheStrategy
{
/**
* Store a value with LRU-friendly TTL
* Shorter TTLs encourage LRU to work effectively
*/
public function storeCacheablData($key, $value, $ttl = 3600)
{
Cache::put($key, $value, $ttl);
}
/**
* Get cache stats and memory usage
*/
public function getCacheStats()
{
$info = Redis::command('INFO', ['memory']);
return [
'memory_used' => $info['used_memory_human'],
'memory_peak' => $info['used_memory_peak_human'],
'memory_percent' => ($info['used_memory'] / $info['maxmemory']) * 100,
'evicted_keys' => $info['evicted_keys'] ?? 0,
];
}
/**
* Check eviction rate (indicates memory pressure)
*/
public function getEvictionRate()
{
$stats = Redis::command('INFO', ['stats']);
return [
'evicted_keys' => $stats['evicted_keys'] ?? 0,
'keyspace_hits' => $stats['keyspace_hits'],
'keyspace_misses' => $stats['keyspace_misses'],
'hit_rate' => $stats['keyspace_hits'] /
($stats['keyspace_hits'] + $stats['keyspace_misses']) * 100,
];
}
}
3. Tiered Caching with Different TTLs
Implement a strategy where different data types have different TTLs based on volatility:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class TieredCacheStrategy
{
// Short-lived: Frequently changing data
const SHORT_TTL = 300; // 5 minutes
// Medium-lived: Semi-stable data
const MEDIUM_TTL = 3600; // 1 hour
// Long-lived: Stable data
const LONG_TTL = 86400; // 1 day
/**
* Cache user session (frequently changing)
*/
public function cacheUserSession($userId, $sessionData)
{
$key = "user_session:{$userId}";
Cache::put($key, $sessionData, self::SHORT_TTL);
}
/**
* Cache user profile (semi-stable)
*/
public function cacheUserProfile($userId, $profileData)
{
$key = "user_profile:{$userId}";
Cache::put($key, $profileData, self::MEDIUM_TTL);
}
/**
* Cache configuration (stable)
*/
public function cacheConfig($configKey, $configData)
{
$key = "config:{$configKey}";
Cache::put($key, $configData, self::LONG_TTL);
}
/**
* Cache with adaptive TTL based on access frequency
*/
public function cacheWithAdaptiveTTL($key, $value, $accessCount = 0)
{
// More frequently accessed items get longer TTL
if ($accessCount > 100) {
$ttl = self::LONG_TTL;
} elseif ($accessCount > 50) {
$ttl = self::MEDIUM_TTL;
} else {
$ttl = self::SHORT_TTL;
}
Cache::put($key, $value, $ttl);
}
}
4. Monitoring and Reacting to Eviction
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Log;
class MonitorRedisEviction extends Command
{
protected $signature = 'redis:monitor-eviction';
protected $description = 'Monitor Redis eviction rate and alert on high eviction';
public function handle()
{
$previousEvicted = 0;
$threshold = 100; // Alert if more than 100 keys evicted per check
while (true) {
try {
$info = Redis::command('INFO', ['stats']);
$currentEvicted = $info['evicted_keys'] ?? 0;
$evictedSinceLastCheck = $currentEvicted - $previousEvicted;
// Get memory stats
$memory = Redis::command('INFO', ['memory']);
$memoryPercent = ($memory['used_memory'] / $memory['maxmemory']) * 100;
if ($evictedSinceLastCheck > $threshold) {
Log::warning('High Redis eviction detected', [
'evicted_since_last_check' => $evictedSinceLastCheck,
'total_evicted' => $currentEvicted,
'memory_usage_percent' => round($memoryPercent, 2),
'memory_used' => $memory['used_memory_human'],
]);
$this->warn("⚠️ High eviction: {$evictedSinceLastCheck} keys evicted");
$this->warn("Memory usage: {$memoryPercent}%");
} else {
$this->info("✓ Eviction normal: {$evictedSinceLastCheck} keys (Memory: {$memoryPercent}%)");
}
$previousEvicted = $currentEvicted;
sleep(60); // Check every minute
} catch (\Exception $e) {
Log::error('Redis monitoring error: ' . $e->getMessage());
$this->error('Error: ' . $e->getMessage());
sleep(30);
}
}
}
}
5. Graceful Degradation When Memory Pressure Occurs
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class CacheWithFallback
{
/**
* Try to get from cache, fall back to computation if evicted
*/
public function getOrCompute($key, callable $computation, $ttl = 3600)
{
try {
// Try to get from cache
$cached = Cache::get($key);
if ($cached !== null) {
return $cached;
}
// Compute fresh data
$result = $computation();
// Try to cache, but don't fail if memory is full
try {
Cache::put($key, $result, $ttl);
} catch (\Exception $e) {
Log::warning("Failed to cache {$key}: {$e->getMessage()}");
// Continue anyway - we have the result
}
return $result;
} catch (\Exception $e) {
Log::error("Cache computation failed for {$key}: {$e->getMessage()}");
return null;
}
}
/**
* Check memory pressure before caching large items
*/
public function getMemoryPressure()
{
try {
$info = Redis::command('INFO', ['memory']);
$used = $info['used_memory'];
$max = $info['maxmemory'];
if ($max === 0) return 'unlimited'; // No memory limit
$percent = ($used / $max) * 100;
if ($percent < 70) return 'low';
if ($percent < 85) return 'medium';
if ($percent < 95) return 'high';
return 'critical';
} catch (\Exception $e) {
return 'unknown';
}
}
/**
* Store data only if memory pressure is acceptable
*/
public function cacheIfNotUnderPressure($key, $value, $ttl = 3600)
{
$pressure = $this->getMemoryPressure();
if ($pressure === 'critical') {
Log::info("Skipping cache storage for {$key} due to critical memory pressure");
return false;
}
Cache::put($key, $value, $ttl);
return true;
}
}
Recommended Strategy for Laravel Applications
Best Practice Eviction Policy
For most Laravel applications, allkeys-lru
is recommended because:
- It evicts any key, not just those with TTL
- It's predictable and well-understood
- It keeps your most-used data in memory
- It's computationally efficient
Alternative: Use allkeys-lfu
if you have workloads where some data is accessed far more frequently than others.
Implementation Checklist
- Set maxmemory appropriately
maxmemory 2gb
maxmemory-policy allkeys-lru
- Use meaningful TTLs in Laravel
Cache::put('key', $value, 3600); // Set explicit TTL
-
Monitor eviction metrics
- Track
evicted_keys
from Redis INFO stats - Alert if eviction rate exceeds threshold
- Adjust maxmemory if constantly evicting
- Track
-
Implement separate Redis databases
- Use database 0 for sessions
- Use database 1 for cache
- Use database 2 for queues
-
Test under load
- Simulate memory pressure
- Verify application behavior during eviction
- Ensure critical data has appropriate TTLs
Conclusion
Redis eviction policies are essential for production Laravel applications. By understanding different eviction strategies, configuring appropriate policies, and implementing monitoring, you can ensure your Redis infrastructure remains stable and performant under memory constraints. The key is choosing a strategy aligned with your application's access patterns and consistently monitoring eviction rates to detect problems before they impact users.
Top comments (0)