Most Redis latency spikes have nothing to do with Redis being "slow". Redis executes commands on one main thread, so anything that blocks that thread for 50 milliseconds makes every client wait 50 milliseconds. The job is to find what blocked it: one heavy command, a fork for a snapshot, a disk that stalled an fsync, the hypervisor, or the network between the app and the server.
First: is it Redis or the path to Redis?
Application timeouts measure the whole round trip. Before changing any Redis setting, check where the time goes:
redis-cli -h redis1 --latency # round-trip latency from this client
redis-cli -h redis1 --latency-history # the same, in time windows
redis-cli --intrinsic-latency 100 # run ON the server host, 100 seconds
redis-cli -h redis1 INFO | grep latest_fork_usec
The Redis latency guide explains that --intrinsic-latency does not talk to Redis at all. It measures how long the kernel or hypervisor keeps a process off the CPU, so it must run on the server host. If that number is already several milliseconds, no Redis tuning will get you below it. The same guide reports that virtualized hosts with noisy neighbours can be far worse than bare metal.
If --latency from the application host is high but low from the Redis host itself, look at the network, connection churn and the client. Opening a new connection for every request, or making hundreds of round trips instead of using MGET, pipelining or a script, adds latency that Redis never sees.
SLOWLOG: commands that blocked the server
CONFIG GET slowlog-log-slower-than # default 10000 (microseconds = 10 ms)
CONFIG GET slowlog-max-len # default 128 entries
SLOWLOG LEN
SLOWLOG GET 25 # newest entries: id, time, duration in us, args, client
SLOWLOG RESET
According to the SLOWLOG documentation, the slow log measures only command execution time, not network I/O or sending the reply, which is exactly the part where the main thread cannot serve anyone else. Each entry includes the duration in microseconds, the arguments and the client address, so you usually see straight away which service sent it.
Typical findings:
KEYSin production code or an admin script. The documentation calls it a very common source of latency; replace it withSCAN.- O(N) commands on big values:
HGETALL,SMEMBERS,LRANGE 0 -1,SORT,SUNION,ZRANGEover huge ranges. UseHSCAN/SSCAN/ZSCANor paginate. DELof a huge key. Freeing millions of elements takes time on the main thread.UNLINKfrees memory in the background. In Redis thelazyfree-lazy-user-deloption, which makesDELbehave likeUNLINK, defaults tono; Valkey switched the lazyfree options toyesby default in 8.0.- Long Lua scripts or
MULTIblocks that do too much work in one go.
The default threshold is 10 ms and only 128 entries are kept, so on a busy server old entries disappear quickly. Collect it regularly if you want history. In Valkey 8.1 and newer the slow log is part of COMMANDLOG, which also tracks large requests and large replies; the old slowlog-* parameters still work but are deprecated.
Latency monitor: spikes that are not commands
Some stalls never show up in SLOWLOG because they are not command execution. The latency monitor samples internal events such as fork, aof-write, aof-fsync-always, expire-cycle, eviction-cycle and active-defrag-cycle. It is off by default (threshold 0) and can be enabled at runtime:
CONFIG SET latency-monitor-threshold 100 # milliseconds, 0 = off (default)
LATENCY LATEST
LATENCY HISTORY fork
LATENCY DOCTOR
Set the threshold to what your application considers too slow. Each event keeps a time series of 160 samples. LATENCY DOCTOR prints a readable summary with hints. Since Redis 7.0 there is also per-command latency tracking, with p50, p99 and p99.9 exported in INFO latencystats; it is on by default.
The usual suspects outside commands
- Fork for RDB snapshots and AOF rewrites.
fork()runs on the main thread and must copy the page table, which grows with memory size. Checklatest_fork_usecinINFOand theforkevent. The documentation shows fork times differ by orders of magnitude between platforms, with some older Xen virtual machines being particularly slow. - Transparent huge pages. With THP enabled, copy-on-write after a fork can touch huge pages and cause both latency and extra memory use. The documentation says to disable it:
echo never > /sys/kernel/mm/transparent_hugepage/enabled, then restart Redis. - AOF and the disk. With
appendfsync alwaysevery write waits for fsync. Witheverysecfsync runs in a background thread, but if it takes too long, the main thread can still block onwrite(2). Other heavy I/O on the same disk makes this worse. Persistence trade-offs are covered in Redis persistence: RDB vs AOF. - Swapping. If the kernel swaps out Redis pages, touching those keys stalls the process. Check the
Swap:lines in/proc/<pid>/smapsand thesi/socolumns invmstat 1. - Many keys expiring at once. Active expiry normally removes keys in small steps, but if more than 25% of sampled keys with a TTL are already expired, the cycle repeats. Setting the same
EXPIREATon millions of keys can therefore block. Add jitter to TTLs. - Memory pressure and defragmentation. Eviction cycles and active defrag also run on the main thread. If
eviction-cycleoractive-defrag-cycleshows up inLATENCY LATEST, see Redis memory fragmentation ratio.
CONFIG SET watchdog-period 500) logs a stack trace when the server is blocked longer than the period. The documentation calls it experimental, for debugging only. Turn it off again with watchdog-period 0.What changes with large datasets
- Fork time grows with memory. The documentation's example: a 24 GB instance needs about 48 MB of page tables with 4 kB pages, and all of it is copied at every
BGSAVEor AOF rewrite. On a 100+ GB instance that stall can be clearly visible to clients. Schedule snapshots away from peaks, consider running persistence only on a replica, and measurelatest_fork_usecon the real instance size. - Copy-on-write needs spare RAM. During a snapshot every page modified by clients is duplicated. A write-heavy large instance needs memory headroom, or the host starts swapping, which is far worse than the fork itself.
- Big keys become big outages. A hash or sorted set with millions of members turns "harmless" commands into multi-second stalls, and moves those stalls into replication and migrations too. Find them with
redis-cli --bigkeysor--memkeys, ideally against a replica, and split them. - Full resynchronization hurts more. A replica that falls behind triggers a new RDB on the primary, with another fork and heavy network transfer. Size the replication backlog for your write rate so short disconnects do not force a full sync.
- Diagnostics need care too.
KEYS,DEBUGcommands or a largeSCANcount run from a laptop against a 100 GB primary can cause the very spike you are investigating. Use a replica for analysis where you can.
A practical order of checks
- Measure from both sides:
--latencyfrom the app host and from the Redis host, and--intrinsic-latencyon the server. - Read
SLOWLOG GETand match entries to the time of the timeouts. - Enable the latency monitor and check
LATENCY LATESTandLATENCY DOCTOR. - Correlate spikes with snapshots, AOF rewrites, expiry bursts and deploys.
- Check the OS: THP, swap, disk latency, CPU steal on VMs.
Key takeaways
- Redis latency spikes are usually something blocking the main thread, or something outside Redis.
--intrinsic-latencyon the host shows the floor that no Redis tuning can beat.- SLOWLOG catches slow commands; the latency monitor (off by default) catches fork, AOF, expiry and eviction stalls.
- Replace
KEYSwithSCAN,DELof big keys withUNLINK, and spread out TTLs. - On large instances plan for fork time, copy-on-write memory and big keys before they cause an incident.