How to Debug Slow Redis Queries in Production Systems
Learn a practical, production-safe approach to debugging slow Redis queries using the slow log, command profiling, and data structure analysis. This guide covers common culprits like KEYS, large hashes, and network latency, with real-world examples from PythonSkillset.
Advertisement
You’re running a production system, everything seems fine, but then users start complaining about lag. You check your logs, and Redis is the culprit. But how do you pinpoint exactly which query is causing the slowdown? Let me walk you through a practical approach that I’ve used at PythonSkillset to debug slow Redis queries without taking down the system.
The First Step: Enable Slow Log
Redis has a built-in feature called the slow log that records queries exceeding a certain execution time. This is your starting point. By default, it’s often disabled or set to a high threshold. Here’s how to configure it:
# Set threshold to 10 milliseconds (adjust based on your needs)
CONFIG SET slowlog-log-slower-than 10000
# Keep the last 1000 slow queries in memory
CONFIG SET slowlog-max-len 1000
Now you can view the slow queries with:
SLOWLOG GET 100
This gives you the exact command, execution time, and timestamp. At PythonSkillset, we once found a KEYS * command that was taking 2 seconds because someone forgot to use SCAN instead. The slow log caught it immediately.
Understanding What Makes Redis Slow
Before diving into debugging, you need to know what typically causes slow queries in Redis:
- O(N) commands like
KEYS,SMEMBERS,HGETALLon large datasets - Blocking operations such as
BLPOPwith long timeouts - Large value sizes that take time to serialize/deserialize
- Frequent reconnections due to connection pool exhaustion
- Memory fragmentation causing slower access patterns
Using the Slow Log Effectively
The slow log is your best friend, but you need to interpret it correctly. Each entry shows:
- The timestamp when the command was executed
- How long it took (in microseconds)
- The actual command and its arguments
- The client IP and port
Here’s a real example from PythonSkillset’s production environment:
1) 1) (integer) 14
2) (integer) 1623456789
3) (integer) 45000
4) 1) "HGETALL"
2) "user:session:12345"
5) "127.0.0.1:54321"
This tells us a HGETALL command on a user session hash took 45 milliseconds. That’s slow for a simple hash lookup. The problem? That hash had grown to contain 10,000 fields because of a bug in our session management code.
Profiling Without Impacting Production
You can’t just run MONITOR on a production Redis instance—it’ll flood your logs and slow things down further. Instead, use the slow log as your first filter. Once you identify problematic commands, you can dig deeper.
For Python applications, I recommend using the redis-py client’s built-in connection pooling and monitoring. Here’s a simple way to log slow queries from your application side:
import redis
import time
class SlowQueryLogger:
def __init__(self, threshold_ms=100):
self.threshold = threshold_ms / 1000.0
self.client = redis.Redis(connection_pool=redis.ConnectionPool())
def execute(self, command, *args):
start = time.time()
result = self.client.execute_command(command, *args)
duration = time.time() - start
if duration > self.threshold:
print(f"SLOW QUERY: {command} {args} took {duration*1000:.2f}ms")
return result
This wrapper logs any command that takes longer than your threshold. At PythonSkillset, we set ours to 50ms initially and then tightened it as we optimized.
Common Culprits and How to Fix Them
1. The KEYS Command
This is the most common offender. KEYS * scans every key in the database, blocking everything else. In production, this is a disaster waiting to happen.
Fix: Use SCAN instead, which returns results incrementally:
import redis
r = redis.Redis()
cursor = 0
pattern = "user:*"
while cursor != 0:
cursor, keys = r.scan(cursor=cursor, match=pattern, count=100)
for key in keys:
# Process each key
pass
2. Large Hashes and Sets
When you have a hash with thousands of fields, HGETALL becomes expensive. At PythonSkillset, we had a user profile hash that stored everything from preferences to purchase history. A single HGETALL was taking 200ms.
Solution: Break it into smaller hashes or use HSCAN for iteration:
def get_user_data_partial(user_id, fields):
r = redis.Redis()
pipeline = r.pipeline()
for field in fields:
pipeline.hget(f"user:{user_id}", field)
return pipeline.execute()
3. The Pipeline Problem
Many developers use pipelines to batch commands, but they forget that pipelines block the Redis server until all commands are executed. If you have a pipeline with 10,000 commands, Redis will process them all before returning anything.
Fix: Keep pipelines small—under 100 commands at a time. Also, consider using transaction=False if you don’t need atomicity:
pipeline = r.pipeline(transaction=False)
for i in range(100):
pipeline.get(f"key:{i}")
results = pipeline.execute()
Monitoring in Real-Time
The slow log is retrospective. For real-time monitoring, use Redis’s INFO COMMANDSTATS to see which commands are consuming the most CPU time:
INFO commandstats
This gives you a breakdown like:
cmdstat_get:calls=12345,usec=456789,usec_per_call=37.02
cmdstat_hgetall:calls=567,usec=234567,usec_per_call=413.52
Notice the HGETALL command has a high average time per call. That’s your target.
The Hidden Problem: Network Latency
Sometimes the issue isn’t Redis itself but the network between your application and Redis. At PythonSkillset, we once spent hours debugging a slow GET command only to find out the Redis server was in a different AWS region.
How to check: Use redis-cli --latency from your application server:
redis-cli -h your-redis-host -p 6379 --latency
If you see latency above 1ms, you have a network problem. Consider moving Redis closer to your application or using a connection pool with keepalive settings.
The Memory Fragmentation Trap
Redis uses jemalloc for memory allocation, but over time, memory can become fragmented. This doesn’t show up in the slow log directly, but it makes every operation slower.
Check memory fragmentation with:
INFO memory
Look for mem_fragmentation_ratio. If it’s above 1.5, you have significant fragmentation. The fix? Restart Redis during maintenance windows or use MEMORY PURGE to reclaim memory.
Real-World Example: The Session Store Nightmare
At PythonSkillset, we had a session store using Redis hashes. Each user session was stored as a hash with fields like user_id, last_activity, cart_items, and preferences. Over time, some sessions grew to contain hundreds of fields.
The slow log showed HGETALL commands taking 300-500ms. The fix was simple: we split the session into two hashes—one for frequently accessed fields and one for rarely used data. The HGETALL on the small hash took under 1ms.
Using Redis’s Built-in Profiling Tools
Redis 6.0 introduced the ACL LOG and better logging, but the most useful tool for debugging slow queries is LATENCY DOCTOR:
LATENCY DOCTOR
This gives you a human-readable report of latency issues. It’s like having a Redis expert looking over your shoulder.
The Connection Pool Bottleneck
Sometimes the slowness isn’t a single query but the connection overhead. If your application creates a new Redis connection for every request, you’ll see high latency from connection setup.
Fix: Use a connection pool with proper sizing:
import redis
pool = redis.ConnectionPool(
host='localhost',
port=6379,
max_connections=50,
socket_connect_timeout=2,
socket_timeout=5
)
r = redis.Redis(connection_pool=pool)
At PythonSkillset, we found that increasing the pool from 10 to 50 connections reduced average query time by 40% because we weren’t waiting for connections to be established.
The Silent Killer: Blocked Clients
Sometimes a slow query isn’t slow itself—it’s blocked by another operation. Use CLIENT LIST to see all connected clients and their states:
CLIENT LIST
Look for clients in the blocked state. If you see many blocked clients, something upstream is holding a lock or running a long transaction.
Practical Debugging Workflow
Here’s the workflow I follow at PythonSkillset when debugging slow Redis queries:
- Check the slow log – Identify the top 10 slowest commands
- Analyze the pattern – Are they all the same command? Same key pattern?
- Check memory fragmentation – Run
INFO memoryand look atmem_fragmentation_ratio - Monitor network latency – Use
redis-cli --latencyfrom your application server - Review your data model – Are you storing too much in a single key?
- Test with a staging environment – Reproduce the slow query with production-like data
The One Thing Most People Miss
When debugging slow Redis queries, everyone focuses on the commands themselves. But often the real issue is data structure design. At PythonSkillset, we had a sorted set with 2 million members. A ZRANGEBYSCORE operation was taking 800ms. The fix wasn’t optimizing the query—it was splitting the sorted set into multiple smaller ones based on date ranges.
Tools That Help
- redis-rdb-tools – Analyze your RDB file offline to see key sizes
- redis-stat – Real-time monitoring dashboard
- redis-cli --bigkeys – Find keys with large values
- Your application logs – Correlate slow Redis queries with user-facing latency
When All Else Fails
If you’ve optimized everything and Redis is still slow, consider:
- Upgrading hardware – More RAM, faster CPU, or SSD storage
- Using Redis Cluster – Distribute data across multiple nodes
- Adding read replicas – Offload read-heavy workloads
- Caching at the application level – Use a local cache like
cachetoolsfor frequently accessed data
The Bottom Line
Debugging slow Redis queries isn’t about guessing—it’s about using the right tools and understanding your data patterns. Start with the slow log, profile your commands, and always question your data model. At PythonSkillset, we’ve seen teams spend weeks optimizing queries when the real fix was simply restructuring their data.
Remember: Redis is fast, but it’s not magic. Treat it like any other database—profile it, monitor it, and design your data structures with performance in mind.
Advertisement
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.