skip to content

Redis 6 introduced version 3 of its serialization protocol alongside the existing one. How does a client select the protocol version on a connection, and what does the newer version add?

level: middleimportance: should knowfreq 38%

answer

  1. connection starts RESP2; HELLO 3 opts in, per connection
  2. HELLO also does AUTH + SETNAME in one round trip
  3. pre-6.0: HELLO is unknown command → fall back
  4. new types: % map, ~ set , double # bool ( bignum = verbatim _ null
  5. > push = out-of-band → pub/sub on shared conn + tracking invalidation

basics

~20 s

A connection starts in RESP2; the client opts in by sending HELLO 3 (optionally with AUTH and SETNAME), and the server replies with a map of server info or an error on older servers. RESP3 adds typed replies — map, set, double, boolean, big number, verbatim string, null — plus out-of-band push frames and attributes.

solid answer

~60 s

Negotiation is explicit and per connection. A new connection speaks **RESP2**; the client sends `HELLO 3 [AUTH user pass] [SETNAME name]` to upgrade. The reply is a map describing the server (`server`, `version`, `proto`, `id`, `mode`, `role`, `modules`). `HELLO` with no argument reports the current protocol without changing it, and on a pre-6.0 server `HELLO` itself is an unknown command — that error is how a client detects it must stay on RESP2. Downgrading with `HELLO 2` is allowed. RESP3 adds: - **`%` map** — `HGETALL`, `CONFIG GET`, and `XPENDING` return real key/value pairs instead of flat arrays the client had to re-pair. - **`~` set**, **`,` double**, **`#` boolean** (`#t`/`#f`), **`(` big number**, **`=` verbatim string** (carries a format hint such as `txt`/`mkd`), **`_` null** — one null instead of RESP2's `$-1` and `*-1`. - **`>` push** — out-of-band messages that are not replies, which is what lets Pub/Sub and client-side caching invalidations share a connection with normal commands. - **`|` attributes** — optional metadata attached to a reply. RESP2 remains fully supported; RESP3 is opt-in per connection, so a fleet can mix.

code

text · 21 lines
text
# default connection is RESP2
HGETALL user:1
1) "name"      <- flat array; client must re-pair
2) "ada"
3) "age"
4) "36"

HELLO 3 AUTH default s3cret SETNAME api-worker-7
1# "server" => "redis"
2# "version" => "7.2.4"
3# "proto" => (integer) 3
4# "id" => (integer) 118
5# "mode" => "standalone"
6# "role" => "master"

HGETALL user:1
1# "name" => "ada"      <- real map (%) frame
2# "age" => "36"

HELLO          # report protocol without changing it
HELLO 2        # downgrade back to RESP2

go deeper

for a junior

Know that HELLO 3 switches a connection to the newer protocol and that the newer one adds richer types like maps and booleans.

for a middle

Explain per-connection negotiation, the fallback on the unknown-command error for pre-6.0 servers, the main new types, and that RESP2 remains the default.

for a senior

Emphasise the push type as the structural change enabling Pub/Sub on a shared connection and server-pushed invalidations, and warn that enabling RESP3 changes reply shapes in application code.

for a principal

Frame it as protocol evolution done without a flag day: opt-in per connection, feature detection by error, RESP2 supported indefinitely, and a client-library migration cost that is application-visible rather than transparent.

## Why a second protocol RESP2 has five types: simple string, error, integer, bulk string, array. Everything richer is squeezed into those. `HGETALL` returns a flat array `[field, value, field, value]` and the client re-pairs it. `EXISTS`-style booleans are `:0`/`:1`. Floating-point scores from `ZSCORE` are bulk strings the client must parse. There are two different nulls (`$-1` and `*-1`). And there is no way to send a message that is *not* a reply, which is why a RESP2 connection in subscribe mode is effectively hijacked. RESP3, introduced in Redis 6.0, addresses exactly these: **semantic types** so the client does not have to know per-command shape rules, and **out-of-band frames** so the connection can carry server-initiated messages. ## The handshake: HELLO ``` HELLO 3 HELLO 3 AUTH default s3cret HELLO 3 AUTH default s3cret SETNAME api-worker-7 HELLO # report current protocol, change nothing HELLO 2 # go back to RESP2 ``` Key properties: - **Connections start in RESP2.** There is no auto-negotiation, no ALPN-style hint, no server-side default flip. Silence means RESP2, which is what preserves compatibility with every existing client. - **Per connection, not per server.** One application can hold RESP3 connections while another holds RESP2 against the same instance. - **The reply is itself informative** — a map (in RESP3) with `server`, `version`, `proto` (the protocol now in effect), `id` (connection id), `mode` (standalone/sentinel/cluster), `role`, and `modules`. - **Feature detection is by error.** On Redis 5 and earlier, `HELLO` does not exist, so the server returns `-ERR unknown command`. A client that wants RESP3 tries `HELLO 3` first and falls back to RESP2 on that error. This is why `HELLO` also accepts `AUTH`: it lets a client authenticate and upgrade in a single round trip instead of `AUTH` then `HELLO`. ## The new types | byte | type | notes | |---|---|---| | `%` | map | `%2\r\n$1\r\na\r\n:1\r\n$1\r\nb\r\n:2\r\n` — count is the number of **pairs** | | `~` | set | unordered collection; `SMEMBERS`, `SPOP` with count | | `,` | double | `,3.141\r\n`, plus `,inf` / `,-inf` | | `#` | boolean | `#t\r\n` / `#f\r\n` | | `(` | big number | integers beyond 64-bit range, as text | | `=` | verbatim string | like a bulk string but the first 3 bytes plus `:` declare a format, e.g. `txt:` or `mkd:` — used by `LATENCY DOCTOR`, `INFO`-style human output | | `_` | null | a single null replacing `$-1` and `*-1` | | `!` | blob error | an error whose payload is length-prefixed, so it can be long or binary | | `\|` | attribute | metadata attached to the following reply, e.g. client-side-caching hints; a client may ignore it | | `>` | push | **out-of-band**: not a reply to any command | ## Push frames: the structural change Everything above is convenience. The `>` push type is the one that changes what is possible. In RESP2, a Pub/Sub message arrives as an ordinary `*` array indistinguishable in shape from a command reply, so the client cannot tell "this is the answer to my `GET`" from "this is a published message". Redis resolves this by restricting a subscribed RESP2 connection to only subscription commands — meaning applications need a **separate connection** for Pub/Sub. In RESP3 a push frame is explicitly typed, so a client can demultiplex: reply frames go to the caller waiting for them, push frames go to a listener callback. Consequences: Pub/Sub can share a connection with normal commands, and the server can proactively notify the client of other things — most notably **client-side caching invalidation** (the `CLIENT TRACKING` feature), where the server pushes `invalidate` messages naming keys the client has cached and must drop. That feature is only practical because RESP3 has a channel for unsolicited server messages. ## Practical notes - **Client libraries differ.** Many mainstream clients support RESP3 but do not enable it by default, because switching changes the *shape* of returned values in the host language — `HGETALL` becoming a real map rather than a flat list can break application code that indexed the list. Treat enabling RESP3 as an application-visible change, not a transparent optimisation. - **Compatibility mode.** Some clients, when running RESP3, deliberately keep RESP2-shaped return values for existing APIs to avoid that break, exposing the richer shapes only through new APIs. - **Scripting.** Lua scripts see conversions between RESP types and Lua types; RESP3 adds conversion rules for the new types, and Redis 7 lets a script declare which protocol version it wants for its own replies. - **Mixed fleets are fine.** Because negotiation is per connection and RESP2 remains fully supported indefinitely, there is no coordinated migration to run.

  • How does a client library detect whether the server supports RESP3 at all?
    It sends `HELLO 3` as the first command. On Redis 6.0 and later the server replies with a map including `proto: 3`. On Redis 5 and earlier `HELLO` is not a known command, so the server returns `-ERR unknown command`, and the client treats that specific error as the signal to remain on RESP2. Because `HELLO` also accepts `AUTH` and `SETNAME`, a client can authenticate, name the connection, and negotiate in one round trip.
  • Why do many client libraries not enable RESP3 by default even when the server supports it?
    Switching protocol changes the shape of values returned to application code: `HGETALL` becomes a real map instead of a flat list, `ZSCORE` returns a double rather than a string, booleans become true/false instead of 1/0. Any application that indexed positions in the old flat replies would break silently. Libraries therefore either keep it opt-in or run a compatibility mode that preserves RESP2-shaped results for existing APIs while exposing new shapes through new methods.
  • What does the RESP3 attribute type carry, and must a client understand it?
    An attribute frame, prefixed with `|`, attaches auxiliary metadata to the reply that follows — for example hints related to client-side caching. It is explicitly optional: a conforming client that does not care about a given attribute may parse and discard it without changing how it handles the actual reply. That design lets Redis add out-of-band metadata over time without breaking clients that were written before the attribute existed.

saying these in an interview costs you the question

  • Believing Redis 6 servers speak RESP3 by default rather than starting every connection in RESP2
  • Thinking the protocol version is a server-wide setting instead of per connection
  • Assuming enabling RESP3 is transparent to application code when it changes reply shapes
  • Not knowing HELLO can carry AUTH and SETNAME, and so spending extra round trips
  • Claiming RESP3 replaces RESP2 and that RESP2 is deprecated or removed

context