After a Redis client issues the SUBSCRIBE command, what state is that connection in, which commands can it still run, and how does it get back to normal?
answer
- SUBSCRIBE flips connection into subscriber mode
- RESP2: only (P|S)SUBSCRIBE/UNSUBSCRIBE, PING, RESET, QUIT
- Replies are arrays: subscribe / message / unsubscribe
- RESET returns connection to clean state
- RESP3 push type lifts the restriction
basics
~20 sUnder RESP2 the connection enters subscriber mode: it may only run SUBSCRIBE/UNSUBSCRIBE (and their pattern and sharded variants), plus PING, RESET and QUIT. Other commands error. Leave by unsubscribing from everything, or with RESET. Under RESP3 the restriction is gone.
solid answer
~50 sWith the RESP2 protocol, SUBSCRIBE puts the connection into a dedicated subscriber mode. From then on the server only accepts subscribe/unsubscribe family commands plus PING, RESET and QUIT; anything like GET returns an error saying only those commands are allowed in this context. That is why every client library keeps a separate connection for subscriptions rather than borrowing one from the normal pool. The connection is also push-driven: after the initial confirmation reply, the server sends message frames whenever someone publishes, so the client has to sit in a read loop rather than a request/response loop. You exit by unsubscribing from all channels (UNSUBSCRIBE with no arguments) or by sending RESET, which returns the connection to a clean, non-subscribed state. RESP3 changes this: push messages are a distinct protocol type, so the same connection can carry normal commands and subscriptions at once. Many clients still keep them separate for simplicity.
code
text · 16 lines> SUBSCRIBE news
1) "subscribe"
2) "news"
3) (integer) 1
> GET somekey
(error) ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context
> PING
1) "pong" 2) ""
> UNSUBSCRIBE # no args = leave all channels
1) "unsubscribe" 2) "news" 3) (integer) 0
> GET somekey # works again
(nil)go deeper
Recall that SUBSCRIBE takes over the connection, that only a few commands remain legal under RESP2, and that UNSUBSCRIBE with no arguments releases it.
Explain the wire mechanics: confirmation arrays, unsolicited message frames, PING and RESET, and why a dedicated connection is the normal pattern.
Add the RESP3 push-type change, connection-pool hygiene with RESET, and why slow callbacks turn into server-side buffer growth and dropped subscribers.
Position it as a protocol-design consequence: server-initiated delivery needs an out-of-band channel, RESP2 solved it by restricting the connection, RESP3 by typing the frame, and library conventions should be chosen accordingly.
## Subscriber mode Redis is normally strict request/response: the client sends a command and reads exactly one reply. Pub/Sub breaks that model, because the server must be able to speak first, at any time, when someone publishes. Under the RESP2 protocol Redis handles this by flipping the connection into *subscriber mode* on the first SUBSCRIBE. In that mode, the allowed command set is deliberately tiny: SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, SSUBSCRIBE, SUNSUBSCRIBE, PING, RESET (Redis 6.2+) and QUIT. Anything else is refused with an error along the lines of "only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context". A very common first bug is reusing a pooled connection: some code path calls GET on the connection that a background listener already subscribed on, and the command fails for reasons that look unrelated to Pub/Sub. ## What the wire looks like SUBSCRIBE does not return a simple OK. It replies with one confirmation array per channel: the string `subscribe`, the channel name, and the running count of subscriptions this connection now holds. Delivered messages then arrive as arrays of `message`, channel, payload. UNSUBSCRIBE replies symmetrically with `unsubscribe`, channel, and the remaining count. Because messages arrive unsolicited, the client must be in a blocking read loop; PING is allowed precisely so a subscriber can keep the connection alive and detect a dead peer without leaving the mode. ## Leaving the mode UNSUBSCRIBE with no arguments unsubscribes from every channel this connection holds and, once the count reaches zero, the connection returns to normal command handling. RESET (6.2+) is the blunt instrument: it exits subscriber mode, discards a queued MULTI, unwatches keys, and returns the connection to a fresh state, which is why connection pools use it as a cleanup step before handing a connection back. Closing the socket obviously also ends the subscription, and with it any chance of receiving messages published afterwards. ## RESP3 changes the constraint RESP3 (available from Redis 6.0, negotiated with `HELLO 3`) introduces a separate *push* reply type. Because pushes are distinguishable from command replies on the wire, the server no longer needs to lock the connection down: a RESP3 client can subscribe and keep issuing ordinary commands on the same connection. Many libraries still dedicate a connection anyway, since a single-threaded read loop is easier to reason about and a slow subscriber then cannot delay unrelated commands. ## Practical guidance Treat a subscription as owning its connection. Do the minimum work inside the message callback and hand payloads to a worker queue, because whatever you do there blocks reading the socket and lets the server-side output buffer for that client grow. Make sure your library re-issues subscriptions on reconnect, and know that it does so silently, so add your own logging if you need to know a gap occurred.
- Why do client libraries usually dedicate a connection to subscriptions instead of using the shared pool?Because under RESP2 a subscribed connection cannot serve ordinary commands, so returning it to the pool would break the next borrower. Even under RESP3, where mixing is legal, a subscriber must sit in a read loop and any slow message handling would delay unrelated commands on the same socket. A dedicated connection also makes reconnect-and-resubscribe logic self-contained.
- What is the risk of doing heavy work inside the message-handling callback?The client stops reading from the socket while it works, so the server keeps buffering messages for that client. If the pub/sub output-buffer limit is exceeded, Redis disconnects the subscriber and those messages are lost with no error to the publisher. The fix is to hand the payload to a worker queue and return to the read loop immediately.
saying these in an interview costs you the question
- Reusing a subscribed connection from a pool for GET/SET and being surprised by the error
- Thinking SUBSCRIBE returns a plain +OK rather than a confirmation array with the subscription count
- Believing you must close the socket to leave subscriber mode instead of unsubscribing or using RESET
- Assuming RESP2 and RESP3 behave identically here
- Doing blocking or long-running work inside the message callback