Redis load testing: command latency, connection pools and hot keys
Redis usually answers a single command in under a millisecond, so Redis problems under load show in the tail, not the average. Because it processes commands one after another on a single thread, one slow command (a big key, a wide scan) holds up every command behind it. A Redis load test runs a realistic mix of commands at the target rate and asks: where is p99 latency at this rate, is the application's connection pool large enough, and how do memory and eviction behave under load.
What are we measuring?
- Command latency: p95, p99 and the maximum. The average says almost nothing here (the p95 and p99 guide).
- Throughput: commands processed per second. On a single-threaded Redis the limit is usually one CPU core; in a cluster the load spreads over the shards.
- Connections and the pool: waiting for a connection from the client's pool adds latency even when Redis itself is fast. The pool size is part of what you measure.
- Memory and eviction: memory fills as the key count and value sizes grow; at
maxmemoryRedis evicts keys by its eviction policy or refuses writes. See this behaviour in a long test (a soak test). - Hit ratio: for Redis used as a cache, how many reads find a value. As the hit ratio drops, the load moves to the database behind it.
Shaping the load
For Redis the target is almost always a rate ("4,000 commands a second"), so use a constant arrival rate. Take the command mix from production: the read/write ratio, the data structures used (string, hash, sorted set, list) and the value sizes. Pick keys from a realistic range: reading the same few keys keeps everything in cache, while the real distribution is usually a few hot keys and a long tail. To find the rate where Redis breaks, raise the rate in steps (breakpoint testing).
Common mistakes
- Small, identical values. A test with 10-byte values does not show the network and memory load of an application that stores 50 KB JSON documents. Make the value sizes resemble production.
- Slow commands.
KEYS,SMEMBERSorHGETALLon big collections,ZRANGEover wide ranges keep the single thread busy. If your application uses them, put them in the test and see their effect on the tail; if it does not, leave them out. - A distant load generator. When Redis answers in under a millisecond, the network round trip is most of the measurement. Generate the load from about as far from Redis as your application is.
- A shared key space. Write test data under a prefix that cannot collide with live keys (such as
loadtest:) and give it a TTL, so it cleans itself up after the test.
With Spitfire
First add a Redis connection under Connections: addresses (host:6379), the mode (standalone, cluster or sentinel; the master name for sentinel), an ACL username, password, database number, pool size (50 by default) and TLS if needed. The password is stored encrypted and the test names only the connection. The VUs of each runner share one connection pool per connection, like an application instance; N runners mean N pools. A failed command is not retried: a retry would hide the failure and add up latencies.
The Redis step runs one command: the command name and its arguments, a single line in the editor (SET "user:{{__VU}}" "{{$uuid}}" EX 60) and an array in JSON. The command name is a fixed word; each argument can contain {{$randInt 1 100000}}, {{$randString 512}} or variables from a CSV data file. The step's duration is the command's round trip and includes the wait for a pooled connection. The reply goes to checks and extraction as JSON: strings, numbers, arrays and (with RESP3) maps as they are. A missing key is not an error: the step's status is nil and the reply is null; for array replies the element count is reported as rows and can be tested with a rowCount check. Errors are told apart by kind: authentication and permission, read-only replica, syntax, refused connection and timeout.
Dangerous commands need approval. Ordinary writes such as SET, DEL or INCR are normal load test traffic. Commands that can break a shared server (FLUSHALL, FLUSHDB, KEYS, CONFIG, SHUTDOWN, SCRIPT, MONITOR and the like, including ones called inside an EVAL script) cannot be saved without an explicit approval on the step, and once approved the test asks for confirmation again every time it starts (security statement). A step is one command: pipelines and MULTI/EXEC transactions cannot be built across steps.
A session cache: 2,000 iterations a second, each reading a random session and writing a 512-character value to a random session with a 10-minute TTL (4,000 commands a second). The read check counts both a found and a missing key as success; the read's p99 must stay under 5 ms and the error rate under 0.1%. The test was checked with spitfire validate.
{
"name": "Redis: oturum önbelleği",
"scenarios": [
{ "name": "oturum",
"executor": { "type": "constant-arrival-rate", "rate": 2000, "timeUnit": "1s",
"duration": "10m", "preAllocatedVUs": 50, "maxVUs": 200 },
"steps": [
{ "id": "get", "name": "Oturum oku", "protocol": "redis", "connection": "cache",
"redis": { "command": ["GET", "loadtest:session:{{$randInt 1 100000}}"] },
"checks": [ { "type": "status", "op": "in", "value": ["ok", "nil"] } ] },
{ "id": "set", "name": "Oturum yaz", "protocol": "redis", "connection": "cache",
"redis": { "command": ["SET", "loadtest:session:{{$randInt 1 100000}}",
"{{$randString 512}}", "EX", "600"] } }
] }
],
"thresholds": [
{ "metric": "req_duration", "filter": { "step": "get" }, "expr": "p(99)<5" },
{ "metric": "req_failed", "expr": "rate<0.001" }
]
}During the run you watch per-step command rates, p95, p99, data sent and received, and error kinds live. If you collect Redis's own metrics (memory, evictions, connected clients, hit ratio) with a Prometheus exporter, add them to a Prometheus connection under Observability; the run page's Backend tab shows them aligned with the load stages. Redis usually sits in front of a database; to load the database in the same test, see the SQL load testing guide.
Spitfire installs on Docker or Kubernetes with one command; every testing feature and protocol is open in the free edition.