Why you probably don't need Redis yet
·3 min read ·Architecture · Databases · Performance
When scaling a web application, the knee-jerk reaction to performance issues is often 'let's grab Redis'. While Redis is an incredible piece of technology, introducing it adds an entirely new moving part to your infrastructure. You now have to worry about cache invalidation, memory management, and network latency between your app and the caching layer.
The Cost of Complexity
Every piece of infrastructure you add is another thing that can break at 3 AM. It’s another service to monitor, another connection pool to manage, and another potential bottleneck. When you introduce a distributed cache, you also introduce the hardest problem in computer science: cache invalidation.
Suddenly, your pristine, synchronous business logic needs to look like this:
$user = Cache::remember('user.{$id}', 3600, function () use ($id) {
return User::with('preferences', 'recentOrders')->find($id);
});
This seems simple enough, but what happens when a background job updates the user's preferences? If you forget to invalidate user.{$id}, your application serves stale data. Multiply this across hundreds of models and relations, and you have a recipe for subtle, impossible-to-reproduce bugs.
The Alternative: Use What You Have
Before reaching for an in-memory datastore, have you exhausted what your RDBMS (like PostgreSQL or MySQL) can do?
1. Proper Indexing
It sounds obvious, but a massive percentage of 'slow database' problems are just missing indices. An unindexed query scanning a 10-million-row table will bring your app to its knees. Adding a compound index can turn a 500ms query into a 2ms query.
2. Materialized Views
If you have heavy analytical queries (e.g., calculating the total sales per region over the last month), don't compute this on the fly and don't cache it in Redis. Use a Materialized View in PostgreSQL.
CREATE MATERIALIZED VIEW monthly_regional_sales AS
SELECT region_id, SUM(amount) as total
FROM sales
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY region_id;
You can refresh this view asynchronously via a cron job (REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_regional_sales;). You get lightning-fast reads without the complexity of a separate caching service.
3. Database-Level Caching
Modern databases are incredibly smart about keeping frequently accessed data in memory. PostgreSQL's shared_buffers does exactly this. If you have enough RAM on your database server, your 'disk reads' are often just memory reads anyway.
When do you need Redis?
I'm not saying Redis is bad. You absolutely need it when:
- High-velocity data: You're tracking real-time unread message counts for a massive chat app.
- Rate limiting: You need distributed, atomic counters that evaporate quickly.
- Pub/Sub: You need to fan out WebSocket events to multiple application servers.
- Queueing: You need a robust backend for Laravel Horizon or similar background job workers.
But for caching the result of SELECT * FROM settings? Your database can handle that. Keep it simple until you can't.