skip to content

In the RESP protocol, most frames a client reads are replies to commands it sent. Explain what an out-of-band push frame is, why the older protocol version could not express one, and what capabilities it unlocked.

level: seniorimportance: should knowfreq 28%

answer

  1. > prefix = out-of-band, not a reply (RESP3 only)
  2. RESP2 subscribe mode locks the connection to subscription commands
  3. push kinds: message, pmessage, subscribe, invalidate
  4. enables CLIENT TRACKING invalidation + pub/sub on one connection
  5. reader must type-check every frame; pushes can interleave

basics

~20 s

A push frame (prefix >, RESP3 only) is a server-initiated message that is not a reply to any command. RESP2 had no such marker, so subscription messages looked like ordinary array replies and a subscribed connection had to be restricted to subscription commands. Push frames let a client demultiplex, so one connection can carry both commands and server notifications.

solid answer

~60 s

In RESP2 every frame a client reads is assumed to answer the next outstanding request, because replies arrive in request order. There is no type marker for "this is unsolicited". Pub/Sub messages were therefore delivered as ordinary `*` arrays, indistinguishable in shape from a reply — so Redis had to forbid a subscribed RESP2 connection from issuing normal commands (only SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE/PUNSUBSCRIBE/PING/QUIT/RESET). Applications consequently kept a **second connection** for subscriptions. RESP3 adds the `>` prefix. A push frame is structurally an array but explicitly typed as out-of-band, so a client reader can route it to a listener callback while reply frames continue to satisfy waiting callers on the same connection. What that unlocks: - **Pub/Sub multiplexed** with ordinary commands on one connection. - **Server-pushed cache invalidation** — with `CLIENT TRACKING ON`, the server sends `invalidate` push messages naming keys the client has cached and must discard. - **Keyspace notifications and monitoring events** delivered without a dedicated subscriber connection. Ordering caveat: a push may arrive interleaved between replies, so client readers must handle it at any point in the stream.

code

text · 16 lines
text
# --- RESP2: connection is locked once subscribed ---
SUBSCRIBE news
GET user:1
(error) ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context

# --- RESP3: HELLO 3 first, then both work on one connection ---
HELLO 3
SUBSCRIBE news
GET user:1            # allowed

# on the wire, a published message arrives as a PUSH frame:
>3\r\n$7\r\nmessage\r\n$4\r\nnews\r\n$5\r\nhello\r\n

# and a tracking invalidation likewise:
CLIENT TRACKING ON
>2\r\n$10\r\ninvalidate\r\n*1\r\n$6\r\nuser:1\r\n

go deeper

for a junior

Know that RESP3 adds a frame type marked > for messages the server sends on its own, such as published Pub/Sub messages.

for a middle

Explain why RESP2 had to restrict subscribed connections and how the explicit push marker lets a client separate notifications from replies.

for a senior

Cover the capabilities it unlocked — multiplexed Pub/Sub and CLIENT TRACKING invalidation — plus reader-loop implications, interleaving, and the staleness window.

for a principal

Discuss it as a protocol-level enabling change: giving the server a channel to notify clients turns caching from pure client polling into server-driven invalidation, with the accompanying eventual-consistency and output-buffer tradeoffs.

## The request/response assumption RESP is fundamentally a request/response protocol over a single TCP connection with **ordered replies**: the Nth reply corresponds to the Nth request. That property is what makes pipelining trivial — write ten requests, read ten replies, match by position. A client reader is therefore usually a simple loop: read one frame, hand it to the caller at the head of the pending queue. That model has no room for a frame the client did not ask for. ## How RESP2 coped, badly Pub/Sub needs exactly such frames: after `SUBSCRIBE news`, the server sends a frame whenever anyone publishes, at an arbitrary time. In RESP2 that frame is an ordinary array: ``` *3\r\n$7\r\nmessage\r\n$4\r\nnews\r\n$5\r\nhello\r\n ``` Shape-wise this is indistinguishable from, say, an `LRANGE` reply of three strings. A client reading frames in order cannot know whether the array it just read is the answer to its pending `GET` or a published message. Redis solved this by **constraining the connection**: once a RESP2 connection enters subscribe mode, it may only run subscription commands plus `PING`, `QUIT`, `RESET` (and `SUBSCRIBE`-family variants). Any other command returns an error. The practical cost: every application that both reads/writes keys and subscribes needs **two connections** and two pools, and Pub/Sub cannot be layered onto a general-purpose client without extra machinery. It also means the server has no general way to notify a client about anything at all. ## What RESP3's push type changes RESP3 (Redis 6.0+, opted into per connection with `HELLO 3`) introduces the `>` prefix: ``` >4\r\n$7\r\nmessage\r\n$4\r\nnews\r\n$5\r\nhello\r\n... ``` Structurally it is an array of N elements; semantically the `>` says **this is not a reply**. That single bit of type information is enough to restore the client's ability to demultiplex: - the reader loop reads a frame; - if it is `>`, dispatch it to a registered listener (by the first element, which names the kind: `message`, `pmessage`, `subscribe`, `unsubscribe`, `invalidate`, ...); - otherwise, complete the oldest outstanding request. The pending-request queue is untouched by push frames, so ordering guarantees for replies still hold. ## Capabilities it unlocked **1. Pub/Sub on a shared connection.** In RESP3 a subscribed connection is no longer restricted; a client can `SUBSCRIBE` and still run `GET`/`SET` on the same socket. This simplifies pooling, halves connection counts for subscriber-heavy apps, and lets libraries expose subscription as an ordinary API rather than a special connection mode. **2. Client-side caching (`CLIENT TRACKING`).** This is the flagship use. With tracking enabled, the server remembers which keys a connection has read and, when such a key changes, **pushes** an `invalidate` message listing the keys. The client keeps a local in-process cache and drops entries on invalidation, eliminating round trips entirely for hot keys. Variants exist — default mode tracks read keys per connection; `BCAST` mode pushes invalidations for key prefixes without per-key bookkeeping; `OPTIN`/`OPTOUT` control which reads are tracked; and `REDIRECT` sends invalidation pushes to a *different* connection id, which is how RESP2 clients can participate at all (they keep a second connection in subscribe mode receiving `__redis__:invalidate`). The push type is what makes the primary path clean. **3. Server-initiated notifications generally.** Keyspace notifications and similar event streams can be delivered without hijacking the connection, and the protocol now has a place for future server-to-client signalling. ## Implementation consequences for client authors - **Any frame may be a push.** The reader must check the type byte on every frame, not only when it believes a subscription is active — an invalidation can arrive between two replies in a pipeline. - **Thread-safety.** Dispatching a push to user code from the reader thread means user callbacks must not block the reader; libraries usually hand off to a queue or executor. - **Ordering.** A push is delivered in stream order relative to replies, but carries no guarantee about *when* relative to the key operation that caused it in another client — an invalidation is an eventual signal, and there is a genuine race window in which a client may serve a stale locally-cached value. Designs that need correctness under that race use short local TTLs alongside invalidation. - **Backpressure.** A client that ignores pushes still has them accumulate in its socket buffer, and the server accounts them against the client output buffer limits; a slow reader can be disconnected. ## The short version for an interview RESP2's frames were all replies, so unsolicited messages had to masquerade as replies and the connection had to be locked into subscribe mode. RESP3's `>` push type names unsolicited messages explicitly, which restores multiplexing and, more importantly, gives the server a channel to tell clients things — with invalidation-driven client-side caching being the capability that channel was really built for.

  • How can a client that is stuck on RESP2 still receive cache-invalidation messages?
    By using the REDIRECT option of `CLIENT TRACKING`: tracking is enabled on the working connection but invalidation messages are delivered to a different connection id, which is held in subscribe mode on the special `__redis__:invalidate` channel. That second connection receives them as ordinary RESP2 Pub/Sub messages, sidestepping the missing push type at the cost of an extra connection and the bookkeeping to keep the two associated.
  • What must a client library's read loop do differently once RESP3 is enabled?
    It must inspect the type byte of every frame rather than assuming the next frame answers the oldest pending request, because a push can arrive at any point — including between two replies of a pipeline. Push frames are routed to listeners and must not consume an entry from the pending-request queue. Libraries also avoid running user callbacks on the reader thread, since a blocking callback would stall reply delivery for the whole connection.
  • Does receiving an invalidation push guarantee the client never serves stale data?
    No. The invalidation is an asynchronous signal delivered after the key changed, so there is a window between another client's write and this client's processing of the push during which the local cache still answers with the old value. Client-side caching is therefore an eventual-consistency optimisation; designs that cannot tolerate the window pair invalidation with a short local time-to-live, or simply do not cache the values where the staleness matters.

RESP2 is a counter where you only ever hand things back to the person who just asked; RESP3 adds a tannoy — announcements can go out at any moment and everyone knows they are announcements, not someone's order.

saying these in an interview costs you the question

  • Believing RESP2 Pub/Sub messages are structurally distinguishable from command replies
  • Claiming a RESP2 connection can freely mix subscriptions and normal commands
  • Thinking push frames break reply ordering or consume a pending request slot
  • Assuming client-side caching invalidation is synchronous and eliminates all staleness
  • Saying RESP3 push is just a cosmetic rename of pub/sub message arrays

context