How to Implement Redis Caching for Web Applications
Redis is an in-memory data structure store that provides sub-millisecond response times, making it the most popular caching layer for web applications. Deploying Redis on your Breeze server can dramatically reduce database load and improve response times for frequently accessed data.
Installing Redis
sudo apt update
sudo apt install -y redis-server
Configure Redis for production use:
sudo nano /etc/redis/redis.conf
# Bind to localhost only (security)
bind 127.0.0.1 -::1
# Set a strong password
requirepass YourStrongRedisPassword
# Set max memory and eviction policy
maxmemory 512mb
maxmemory-policy allkeys-lru
# Enable persistence (optional - disable for pure cache)
save 900 1
save 300 10
save 60 10000
# Disable dangerous commands
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""
sudo systemctl restart redis-server
sudo systemctl enable redis-server
Installing the PHP Redis Extension
sudo apt install -y php-redis
sudo systemctl restart php8.2-fpm
Verify the extension is loaded:
php -m | grep redis
Basic Caching Patterns
The most common pattern is cache-aside (lazy loading):
$redis = new Redis();
$redis->connect('127.0.0.1');
$redis->auth('YourStrongRedisPassword');
function getCached(Redis $redis, PDO $db, string $key, callable $fetcher, int $ttl = 300) {
$cached = $redis->get($key);
if ($cached !== false) {
return json_decode($cached, true);
}
$data = $fetcher($db);
$redis->setex($key, $ttl, json_encode($data));
return $data;
}
// Usage
$products = getCached($redis, $db, 'products:featured', function($db) {
return $db->query("SELECT * FROM products WHERE featured = 1 ORDER BY name")->fetchAll();
}, 600); // Cache for 10 minutes
Cache Invalidation Strategies
The hardest part of caching is knowing when to invalidate:
// 1. Time-based expiration (simplest)
$redis->setex('key', 300, $value); // Expires in 5 minutes
// 2. Explicit invalidation on write
function updateProduct($db, $redis, $id, $data) {
$db->update('products', $data, ['id' => $id]);
// Delete specific cache entries
$redis->del("product:{$id}");
$redis->del("products:featured");
$redis->del("products:category:{$data['category_id']}");
}
// 3. Tag-based invalidation using sets
function cacheWithTags(Redis $redis, string $key, $value, array $tags, int $ttl) {
$redis->setex($key, $ttl, json_encode($value));
foreach ($tags as $tag) {
$redis->sAdd("tag:{$tag}", $key);
}
}
function invalidateTag(Redis $redis, string $tag) {
$keys = $redis->sMembers("tag:{$tag}");
if (!empty($keys)) {
$redis->del(...$keys);
}
$redis->del("tag:{$tag}");
}
// Cache a product list with tags
cacheWithTags($redis, 'products:page:1', $products, ['products', 'catalog'], 600);
// When any product changes, invalidate all product caches
invalidateTag($redis, 'products');
Session Storage with Redis
Store PHP sessions in Redis for faster access and easy scaling:
; In php.ini or pool config
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=YourStrongRedisPassword"
Rate Limiting with Redis
Implement sliding window rate limiting:
function checkRateLimit(Redis $redis, string $identifier, int $maxRequests, int $windowSeconds): bool {
$key = "ratelimit:{$identifier}";
$now = microtime(true);
$windowStart = $now - $windowSeconds;
$pipe = $redis->multi(Redis::PIPELINE);
$pipe->zRemRangeByScore($key, '-inf', $windowStart);
$pipe->zCard($key);
$pipe->zAdd($key, $now, $now . ':' . mt_rand());
$pipe->expire($key, $windowSeconds);
$results = $pipe->exec();
$currentCount = $results[1];
return $currentCount < $maxRequests;
}
Monitoring Redis Performance
# Connect to Redis CLI
redis-cli -a YourStrongRedisPassword
# Check memory usage
INFO memory
# View hit/miss ratio
INFO stats
# Look for keyspace_hits and keyspace_misses
# Monitor commands in real time
MONITOR
# Check slowlog
SLOWLOG GET 10
# View all keys matching a pattern (use sparingly)
SCAN 0 MATCH "products:*" COUNT 100
Best Practices
- Set TTLs on all keys — prevent unbounded memory growth
- Use meaningful key naming — follow a convention like
entity:id:attribute - Monitor hit rates — a cache with a low hit rate is wasting memory; tune TTLs and warming strategies
- Use pipelining — batch multiple Redis commands to reduce round trips
- Serialize efficiently — use
json_encodefor interoperability origbinaryfor smaller payloads - Plan for cache failure — your application should degrade gracefully if Redis is unavailable