A service writes 10,000 keys to Redis by issuing 10,000 separate SET commands in a loop, and the loop takes seconds even though Redis reports each command as sub-millisecond. Explain what Redis pipelining is and how it changes this picture.
answer
- RTT dominates, not Redis CPU
- write many, read many, order preserved
- client-side only, server unchanged
- no atomicity, no isolation
- batch hundreds-thousands, not millions
basics
~20 sThe cost is 10,000 network round trips, not Redis work. Pipelining means writing many commands to the socket without waiting for each reply, then reading the replies in order. Same server work, far fewer round trips, often 5-50x faster.
solid answer
~50 sThe bottleneck is the request/response round trip (RTT), not Redis. A naive loop is strictly lockstep: send SET, block, read `+OK`, send the next one. At 0.5 ms RTT, 10,000 commands cost about 5 seconds of pure waiting while Redis sits idle. Pipelining breaks the lockstep: the client writes many commands into the socket back-to-back without waiting, then reads the replies. RESP guarantees replies come back on that connection in the same order the commands were sent, so the client matches reply *i* to command *i* by position. The server does exactly the same per-command work; what disappears is the waiting. In practice you batch in chunks of a few hundred to a few thousand commands, because both sides must buffer outstanding commands and their replies. Pipelining gives ordering on one connection; it does **not** give atomicity or isolation, so other clients' commands may run between yours. For bulk loads, `redis-cli --pipe` does the batching for you.
code
text · 13 lines# lockstep: one round trip per command
redis-benchmark -n 100000 -t set -P 1
# ~30,000 requests per second on a 0.3 ms RTT link
# pipelined 16 deep
redis-benchmark -n 100000 -t set -P 16
# ~400,000 requests per second - same server, fewer round trips
# bulk load a file of commands using the built-in pipe mode
cat commands.txt | redis-cli --pipe
# All data transferred. Waiting for the last reply...
# Last reply received from server.
# errors: 0, replies: 100000go deeper
Say clearly that the loop pays one network round trip per command, and that pipelining sends many commands before reading replies. Mention that replies come back in order.
Add the mechanism: client-side batching over RESP, server unchanged, ordering per connection, and the explicit caveat that there is no atomicity or isolation.
Quantify it (RTT x N), talk about batch sizing against buffers and fairness to other clients, partial application on connection loss, and when a variadic command or Lua is the better tool.
Frame it as a latency-budget decision: where the RTT sits in the call graph, how batch depth trades tail latency for throughput, and how the client library and pool topology should encode that rather than each caller hand-rolling batches.
## The problem is round trips, not Redis throughput Every command in a naive client loop is a full request/response cycle: the client writes the command bytes to the TCP socket, then **blocks** until the reply arrives. Elapsed time per command is dominated by the network round-trip time (RTT), not by Redis's execution time. Redis typically executes a `SET` in a few microseconds, but an in-datacenter RTT is 0.1-0.5 ms and a cross-AZ or VPN hop can be several milliseconds. So 10,000 sequential commands cost `10,000 x RTT`, which is seconds, while the server is idle for almost all of it. This ceiling is per connection and is unaffected by how fast Redis is; buying a bigger Redis instance changes nothing. ## What pipelining actually is Pipelining is a **client-side** technique, not a Redis command or mode. The client serializes several commands into the outgoing socket buffer without waiting for a reply between them, then reads the replies afterwards. The server is untouched: it reads whatever bytes have arrived, parses complete commands from its per-client query buffer, executes them one at a time on its single command-processing loop, and appends each reply to that client's output buffer. The correctness rule that makes this safe is RESP's ordering guarantee: on a single connection, replies are emitted in the exact order the commands were received. The client therefore does not need request IDs; it keeps a queue of the commands it sent and pops one entry per reply parsed. That is also why a client must not lose track of how many replies are outstanding: a mismatch desynchronizes the connection permanently. ## What changes and what does not - **Changes:** the number of round trips, hence wall-clock latency of the batch, and syscall overhead (fewer, larger `read`/`write` calls on both sides). - **Does not change:** the work Redis performs per command, memory used by the values, key expiry, or command semantics. Ten pipelined `INCR`s do exactly what ten sequential ones do. - **Does not add:** atomicity, isolation, or a transaction. Between two commands of your pipeline, Redis may execute commands from other clients. If you need the batch to run as one uninterrupted unit, wrap it in `MULTI`/`EXEC` or run a Lua script; pipelining is only about transport. - **Failure mode differs:** if the connection dies mid-pipeline, an arbitrary prefix of the commands may already have been executed. Pipelined batches must therefore be idempotent or recoverable, exactly like any sequence of independent commands. ## Choosing a batch size Bigger is not monotonically better. Every command in flight consumes memory in the server's query buffer, and every reply consumes memory in that client's output buffer until the client reads it. A single enormous pipeline also occupies the server for a long stretch, delaying other clients. Typical guidance is batches of roughly 100-10,000 commands, tuned so a batch is on the order of a few hundred kilobytes to a few megabytes of request plus reply bytes. Read replies as you go rather than deferring a million of them. ## Alternatives and how they relate - **Variadic commands** (`MSET`, `MGET`, `HMSET`, `SADD` with many members, `DEL` with many keys) are a single command, so they are one round trip *and* one unit of execution. Prefer them when they express what you need; pipeline when the commands are heterogeneous. - **Lua scripts** move logic server-side, giving one round trip plus atomic execution, at the cost of blocking the server for the script's duration. - **Connection pooling / concurrency** raises aggregate throughput across many clients but does not fix per-request latency for one logical batch. ## Measuring it `redis-benchmark -P <n>` runs the benchmark with a pipeline depth of `n` and makes the effect obvious: throughput commonly rises by an order of magnitude between `-P 1` and `-P 16` on a real network, then flattens once CPU or bandwidth, rather than RTT, becomes the limit. For one-off bulk loading, `redis-cli --pipe` streams raw RESP from stdin and reports the number of replies and errors at the end. ## What an interviewer is listening for Name RTT as the cost, describe pipelining as batching writes and deferring reads, cite RESP reply ordering as the mechanism that keeps replies matched, and volunteer the two limits: no atomicity, and memory/latency pressure from oversized batches.
- Why not put a million commands into one pipeline and be done with it?Because the outstanding requests sit in the server's per-client query buffer and every reply accumulates in that client's output buffer until you read it, so a huge batch can add hundreds of megabytes of server memory pressure. It also occupies the single command loop for a long stretch, delaying every other client, and it makes crash recovery coarser. Batching a few hundred to a few thousand commands per flush captures nearly all the RTT saving with none of that.
- How is pipelining different from using a variadic command such as MSET?A variadic command is one command: one round trip and one indivisible unit of execution, so nothing from another client can interleave inside it. A pipeline is N independent commands that merely travel together, so other clients can be served between them and each has its own reply. Prefer the variadic form when it expresses the operation; pipeline when the commands differ or target different data types.
Ordering ten coffees one at a time, walking back to your desk after each, versus writing the whole order on one slip and collecting all ten cups together. The barista's work is identical; you just stop pacing back and forth.
saying these in an interview costs you the question
- Saying pipelining makes the batch atomic or transactional
- Claiming Redis executes the pipelined commands in parallel
- Confusing pipelining with MULTI/EXEC, or thinking one implies the other
- Believing pipelining reduces Redis CPU or memory work per command
- Assuming unlimited batch size is free because 'it is just network'