← index

Redis Client I/O Buffers

Modelled on src/networking.c and src/server.c, Redis 7.2.14. Defaults read from that release's src/config.c; buffer constants from src/server.h.

A client costs Redis three buffers: one sds for what it sent, one flat reply buffer, and a list of heap blocks for whatever did not fit. Only the third is policed by client-output-buffer-limit, and for a normal client that limit is 0 0 0 — off (config.c:170). The page most people remember says 256mb/64mb/60; that is the replica preset. Pick a class, run a scenario, and watch which checks actually fire.

Client

networking.c:2641 readQueryFromClient
flags=N

The last control is this page's own knob, not a Redis constant: a short connWritev is what leaves sentlen pointing into a half-sent block (networking.c:1864, 1880–1900).

c->querybuf sds

server.h:1163

One sds per client. Each readable event copies at most PROTO_IOBUF_LEN = 16,384 bytes into it (networking.c:2653), then processInputBuffer parses and trims with sdsrange (networking.c:2626–2629).

qbufsdslen(c->querybuf) — bytes held. This is qbuf in CLIENT LIST.0
qbuf-freesdsavail(c->querybuf) — allocated but unused. Reclaimed by clientsCronResizeQueryBuffer (server.c:731), not permanent.0
qb_posHow far the parser has read. Processed bytes are not deleted immediately; the trim happens once, at the end of processInputBuffer (networking.c:2626).0
qbuf-peakserver.h:1165 calls it a “Recent (100ms or more) peak”. clientsCron resets it to sdslen every pass (server.c:757), so it is not a lifetime high-water mark.0
bulklenLength of the bulk argument being read, or -1. At or above PROTO_MBULK_BIG_ARG (32 KB) the read is sized to the exact remainder so the argument lands alone in the buffer (networking.c:2660–2668).-1
idleserver.unixtime − c->lastinteraction. Past 2 seconds the cron reclaims free space outright (server.c:739).0s

Growth: sdsMakeRoomFor, greedy — doubles below SDS_MAX_PREALLOC (sds.c:263, sds.h:36).

Input limits

config.c:3227, config.c:3247
limitdefaulton breach
client-query-buffer-limit1 GB WARNING log Closing client that reached max query buffer length, then freeClientAsync. No error reply. Checked after every read; master clients exempt. networking.c:2725–2734
proto-max-bulk-len512 MB -ERR Protocol error: invalid bulk length to the client, then CLIENT_CLOSE_AFTER_REPLY. networking.c:2356–2360, 2264
PROTO_IOBUF_LEN16 KB Not a limit — the per-event read size. server.h:176
pre-AUTH bulk length16 KB On a password-protected server, before AUTH: -ERR Protocol error: unauthenticated bulk length. networking.c:2361–2364

c->buf char *

server.h:1273

Not char[16384]. Since 7.0 it is a heap pointer sized by buf_usable_size, allocated once in createClient (networking.c:133) and resized per client between 1 KB and 16 KB by clientsCronResizeOutputBuffer (server.c:771–812).

bufposBytes written into c->buf. _addReplyToBuffer fills up to buf_usable_size - bufpos and returns a short count, so one reply can straddle buffer and list (networking.c:337–339).0
buf_usable_sizeThe live size. _addReplyToBuffer measures against this field, never against the constant (networking.c:331).16,384
buf_peakDrives the resize. Below half the buffer, cron shrinks to max(PROTO_REPLY_MIN_BYTES, buf_peak+1) (server.c:782–786); at exactly the buffer size it doubles, capped at PROTO_REPLY_CHUNK_BYTES (server.c:787–790).16,384
sentlenOffset into the first not-yet-drained buffer. A write that stops early leaves this non-zero and the rest is sent on a later event (networking.c:1943).0

Counted by the output buffer limit: no — getClientOutputBufferMemoryUsage ignores bufpos (networking.c:3799–3801).

c->reply list of clientReplyBlock

networking.c:348

Sticky: once the list is non-empty, _addReplyToBuffer returns 0 immediately and every later reply goes here, to keep order (networking.c:335). A new block is only allocated after the tail's free space is used up (networking.c:357–366).

list_lenNumber of blocks. New blocks are max(len, PROTO_REPLY_CHUNK_BYTES) rounded up to the allocator's usable size (networking.c:371–374); this page models the request size, not the allocator's rounding.0
reply_bytesSum of block size, i.e. allocated bytes, not used bytes (networking.c:378).0
used_memWhat the limit is compared against: reply_bytes plus a fixed per-node overhead of sizeof(listNode)+sizeof(clientReplyBlock), which this page omits (networking.c:3800–3801).0
soft sinceobuf_soft_limit_reached_time. The first crossing only records the time; the close needs a later check to find it still over after soft_limit_seconds (networking.c:3909–3921).—

client-output-buffer-limit

config.c:169–173
classhardsoftseconds
normal000
pubsub32mb8mb60
replica256mb64mb60

Zero means unconfigured: networking.c:3900–3905 only sets the hard or soft flag when the configured byte count is non-zero, so a stock normal client has no output buffer limit at all. A replica's usage is not c->reply at all — it reads the shared replication buffer through ref_repl_buf_node (networking.c:3788–3798), so it is out of scope here.

Before AUTH on a password-protected server the class is bypassed: over 1 KB of output is an immediate close (networking.c:3882–3883).

maxmemory-clients (default 0, disabled — config.c:3248) is the other cap: a server-wide ceiling on the combined memory of all normal and pubsub clients, which evicts the largest clients first (networking.c:4589–4590, 4583). It is the only limit that counts querybuf, reply buffer and reply list in one number (networking.c:3808–3814).

Event log

t = simulated server.unixtime

How a client is actually dropped

networking.c:3940

Two steps, not one. closeClientOnOutputBufferLimitReached only detects; it never closes the socket itself.

detect   _addReplyProtoToList allocates a new block      networking.c:380
         closeClientOnOutputBufferLimitReached(c, 1)
           reply_bytes == 0 ? return 0                     networking.c:3945
           checkClientOutputBufferLimits(c)                networking.c:3947
           freeClientAsync(c)                              networking.c:3951
             c->flags |= CLIENT_CLOSE_ASAP                 networking.c:1736
             listAddNodeTail(server.clients_to_close, c)   networking.c:1742
           log "Client %s scheduled to be closed ASAP for
                overcoming of output buffer limits."       networking.c:3953

reap     beforeSleep                                      server.c:1745
           freeClientsInAsyncFreeQueue()                  networking.c:1797

The check runs where a block is allocated — networking.c:380, 833 and 1217 — so an append that fits in the tail triggers no check at all. Separately, clientsCron calls it with async = 0 (server.c:1042), which takes the freeClient branch and closes the client on the spot (networking.c:3956). The same cron pass also resizes the query buffer (server.c:1028) and the reply buffer — different work, same tick.