Setting Up Redis for Web Application Data Caching
Redis as a cache works on a simple principle: reading from Redis is orders of magnitude faster than from PostgreSQL or recomputing. Redis latency — 0.1–1 ms vs 5–50 ms for simple SQL and 100–500 ms for heavy queries. The task isn't to cache everything, but to cache correctly: know what to cache, for how long, and how to invalidate.
Installation and Basic Configuration
apt install redis-server
/etc/redis/redis.conf — key parameters:
# Bind to localhost only if Redis on same server
bind 127.0.0.1
# Password (mandatory for production)
requirepass YourStrongRedisPassword
# Max memory
maxmemory 2gb
# Eviction policy when reaching limit
maxmemory-policy allkeys-lru
# Persistence: disable for pure cache
save ""
appendonly no
# Number of databases
databases 16
# Client timeout
timeout 300
# TCP keepalive
tcp-keepalive 300
maxmemory-policy:
-
allkeys-lru— evict rarely used keys from all DBs. Best for pure cache. -
volatile-lru— evict only TTL-expired keys. Problematic if keys without TTL. -
allkeys-lfu— Least Frequently Used — better LRU for Zipf distribution. -
noeviction— return error on memory exhaustion. For non-cache (queues, sessions).
Basic Caching Patterns
Cache-Aside (Lazy Loading) — application manages cache: read from Redis first, miss means read from DB, store in Redis.
PHP example:
class ProductRepository
{
private const CACHE_TTL = 3600; // 1 hour
public function findById(int $id): ?Product
{
$cacheKey = "product:{$id}";
$cached = $this->redis->get($cacheKey);
if ($cached !== false) {
return unserialize($cached);
}
$product = $this->db->find(Product::class, $id);
if ($product) {
$this->redis->setex($cacheKey, self::CACHE_TTL, serialize($product));
}
return $product;
}
public function save(Product $product): void
{
$this->db->persist($product);
$this->db->flush();
// Invalidate cache after save
$this->redis->del("product:{$product->getId()}");
}
}
Write-Through — write to DB and cache simultaneously. Plus: cache always fresh. Minus: writes slower, caching unused data.
Read-Through — cache fetches from DB on miss. Via Redis modules or libraries (RedisGears).
Laravel Cache
Laravel supports Redis as cache driver out of box:
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
],
config/database.php:
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'), // phpredis faster than predis
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', 'myapp_'),
],
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'), // Separate DB for cache
],
],
Usage:
// Cache with automatic computation on miss
$products = Cache::remember('products:featured', 3600, function () {
return Product::where('is_featured', true)->with('category')->get();
});
// Tags for group invalidation
$product = Cache::tags(['products', 'category:5'])->remember(
"product:{$id}",
3600,
fn() => Product::find($id)
);
// Invalidate group
Cache::tags(['products'])->flush();
Tags require redis or memcached driver — don't work with file/database cache.
Database Query Caching
For heavy aggregation queries rarely changing:
$stats = Cache::remember('dashboard:stats', 300, function () {
return DB::table('orders')
->selectRaw('
COUNT(*) as total_orders,
SUM(total_amount) as revenue,
AVG(total_amount) as avg_order
')
->whereDate('created_at', today())
->first();
});
5 minute TTL for dashboard stats is reasonable compromise between freshness and load.
Cache Monitoring
# Redis stats
redis-cli -a password INFO stats | grep -E "keyspace_hits|keyspace_misses|used_memory_human"
# Hit rate = hits / (hits + misses)
# Good: > 90%
# Active keys and TTL
redis-cli -a password SCAN 0 MATCH "product:*" COUNT 100
# Size of each group
redis-cli -a password --bigkeys
# Top commands
redis-cli -a password MONITOR # Warning: loads server, debug only
Production monitoring — Redis Exporter + Grafana:
docker run -d \
--name redis_exporter \
-p 9121:9121 \
oliver006/redis_exporter \
--redis.addr=redis://localhost:6379 \
--redis.password=YourPassword
Key Grafana metrics (ID 11835): redis_keyspace_hits_total, redis_keyspace_misses_total, redis_memory_used_bytes, redis_connected_clients.
Data Serialization
Serialized PHP objects use more space than JSON. For simple structs — JSON is faster and more readable:
// Slow and bulky
$this->redis->set($key, serialize($object));
$object = unserialize($this->redis->get($key));
// Faster for simple DTO
$this->redis->set($key, json_encode($data));
$data = json_decode($this->redis->get($key), true);
// For large objects — igbinary (PHP extension)
$this->redis->set($key, igbinary_serialize($object));
$object = igbinary_unserialize($this->redis->get($key));
igbinary gives ~50% compression vs serialize and works faster.
Timeline
Basic Redis installation with Laravel cache driver setup — 4–6 hours. Implementing caching in specific controllers/repositories with proper invalidation — 1–2 days depending on scope. Setting up monitoring — half day.







