To communicate with the Valkey server, Valkey clients use a protocol called REdis Serialization Protocol (RESP). While the protocol was designed for Redis, it’s used by many other client-server software projects.
RESP is a compromise among the following considerations:
RESP can serialize different data types including integers, strings, and arrays. It also features an error-specific type. A client sends a request to the Valkey server as an array of strings. The array’s contents are the command and its arguments that the server should execute. The server’s reply type is command-specific.
RESP is binary-safe and uses prefixed length to transfer bulk data so it does not require processing bulk data transferred from one process to another.
RESP is the protocol you should implement in your Valkey client.
Note: The protocol outlined here is used only for client-server communication. Valkey Cluster uses a different binary protocol for exchanging messages between nodes.
The first version of the RESP protocol was experimental and was never widely used.
The next version, RESP2, early became the standard communication method for clients with Redis OSS.
RESP3
is a superset of RESP2 that mainly aims to make a client author’s life a
little bit easier. Redis OSS 6.0 introduced experimental opt-in support
of RESP3’s features (excluding streaming strings and streaming
aggregates). In addition, the introduction of the HELLO
command allows clients to handshake and upgrade the connection’s
protocol version (see Client
handshake).
Up to and including Redis OSS 7, both RESP2 and RESP3 clients can invoke all core commands. However, commands may return differently typed replies for different protocol versions.
Future versions of Valkey may change the default protocol version, but it is unlikely that RESP2 will become entirely deprecated. It is possible, however, that new features in upcoming versions will require the use of RESP3.
A client connects to a Valkey server by creating a TCP connection to its port (the default is 6379).
While RESP is technically non-TCP specific, the protocol is used exclusively with TCP connections (or equivalent stream-oriented connections like Unix sockets) in the context of Valkey.
The Valkey server accepts commands composed of different arguments. Then, the server processes the command and sends the reply back to the client.
This is the simplest model possible; however, there are some exceptions:
MONITOR
command. Invoking the MONITOR
command switches the connection to an ad-hoc push mode. The protocol of
this mode is not specified but is obvious to parse.-DENIED
reply, regardless of whether the client writes to
the socket.SUBSCRIBE
,
UNSUBSCRIBE
and their pattern and sharded variants, return
either an error reply or one or more Push replies, without any
regular in-band reply. This is considered a design mistake of these
commands but the behaviour is kept for backward compatibility. Clients
need to compensate for this behaviour.Excluding these exceptions, the Valkey protocol is a simple request-response protocol.
RESP is essentially a serialization protocol that supports several data types. In RESP, the first byte of data determines its type.
Valkey generally uses RESP as a request-response protocol in the following way:
RESP is a binary protocol that uses control sequences encoded in
standard ASCII. The A
character, for example, is encoded
with the binary byte of value 65. Similarly, the characters CR
(\r
), LF (\n
) and SP () have
binary byte values of 13, 10 and 32, respectively.
The \r\n
(CRLF) is the protocol’s terminator,
which always separates its parts.
The first byte in an RESP-serialized payload always identifies its type. Subsequent bytes constitute the type’s contents.
We categorize every RESP data type as either simple, bulk or aggregate.
Simple types are similar to scalars in programming languages that represent plain literal values. Booleans and Integers are such examples.
RESP strings are either simple or bulk. Simple
strings never contain carriage return (\r
) or line feed
(\n
) characters. Bulk strings can contain any binary data
and may also be referred to as binary or blob. Note
that bulk strings may be further encoded and decoded, e.g. with a wide
multi-byte encoding, by the client.
Aggregates, such as Arrays and Maps, can have varying numbers of sub-elements and nesting levels.
The following table summarizes the RESP data types that Valkey supports:
RESP data type | Minimal protocol version | Category | First byte |
---|---|---|---|
Simple strings | RESP2 | Simple | + |
Simple Errors | RESP2 | Simple | - |
Integers | RESP2 | Simple | : |
Bulk strings | RESP2 | Aggregate | $ |
Arrays | RESP2 | Aggregate | * |
Nulls | RESP3 | Simple | _ |
Booleans | RESP3 | Simple | # |
Doubles | RESP3 | Simple | , |
Big numbers | RESP3 | Simple | ( |
Bulk errors | RESP3 | Aggregate | ! |
Verbatim strings | RESP3 | Aggregate | = |
Maps | RESP3 | Aggregate | % |
Sets | RESP3 | Aggregate | ~ |
Pushes | RESP3 | Aggregate | > |
Simple strings are encoded as a plus (+
) character,
followed by a string. The string mustn’t contain a CR (\r
)
or LF (\n
) character and is terminated by CRLF (i.e.,
\r\n
).
Simple strings transmit short, non-binary strings with minimal overhead. For example, many Valkey commands reply with just “OK” on success. The encoding of this Simple String is the following 5 bytes:
+OK\r\n
When Valkey replies with a simple string, a client library should
return to the caller a string value composed of the first character
after the +
up to the end of the string, excluding the
final CRLF bytes.
To send binary strings, use bulk strings instead.
RESP has specific data types for errors. Simple errors, or simply
just errors, are similar to simple
strings, but their first character is the minus (-
)
character. The difference between simple strings and errors in RESP is
that clients should treat errors as exceptions, whereas the string
encoded in the error type is the error message itself.
The basic format is:
-Error message\r\n
Valkey replies with an error only when something goes wrong, for example, when you try to operate against the wrong data type, or when the command does not exist. The client should raise an exception when it receives an Error reply.
The following are examples of error replies:
-ERR unknown command 'asdf'
-WRONGTYPE Operation against a key holding the wrong kind of value
The first upper-case word after the -
, up to the first
space or newline, represents the kind of error returned. This word is
called an error prefix. Note that the error prefix is a
convention used by Valkey rather than part of the RESP error type.
For example, in Valkey, ERR
is a generic error, whereas
WRONGTYPE
is a more specific error that implies that the
client attempted an operation against the wrong data type. The error
prefix allows the client to understand the type of error returned by the
server without checking the exact error message.
A client implementation can return different types of exceptions for various errors, or provide a generic way for trapping errors by directly providing the error name to the caller as a string.
However, such a feature should not be considered vital as it is
rarely useful. Also, simpler client implementations can return a generic
error value, such as false
.
This type is a CRLF-terminated string that represents a signed, base-10, 64-bit integer.
RESP encodes integers in the following way:
:[<+|->]<value>\r\n
:
) as the first byte.+
) or minus (-
) as the
sign.0
..9
) as the
integer’s unsigned, base-10 value.For example, :0\r\n
and :1000\r\n
are
integer replies (of zero and one thousand, respectively).
Many Valkey commands return RESP integers, including
INCR
, LLEN
, and LASTSAVE
. An
integer, by itself, has no special meaning other than in the context of
the command that returned it. For example, it is an incremental number
for INCR
, a UNIX timestamp for LASTSAVE
, and
so forth. However, the returned integer is guaranteed to be in the range
of a signed 64-bit integer.
In some cases, integers can represent true and false Boolean values.
For instance, SISMEMBER
returns 1 for true and 0 for
false.
Other commands, including SADD
, SREM
, and
SETNX
, return 1 when the data changes and 0 otherwise.
A bulk string represents a single binary string. The string can be of
any size, but by default, Valkey limits it to 512 MB (see the
proto-max-bulk-len
configuration directive).
RESP encodes bulk strings in the following way:
$<length>\r\n<data>\r\n
$
) as the first byte.0
..9
) as the
string’s length, in bytes, as an unsigned, base-10 value.So the string “hello” is encoded as follows:
$5\r\nhello\r\n
The empty string’s encoding is:
$0\r\n\r\n
Clients send commands to the Valkey server as RESP arrays. Similarly,
some Valkey commands that return collections of elements use arrays as
their replies. An example is the LRANGE
command that
returns elements of a list.
RESP Arrays’ encoding uses the following format:
*<number-of-elements>\r\n<element-1>...<element-n>
*
) as the first byte.0
..9
) as the
number of elements in the array as an unsigned, base-10 value.So an empty Array is just the following:
*0\r\n
Whereas the encoding of an array consisting of the two bulk strings “hello” and “world” is:
*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n
As you can see, after the *<count>CRLF
part
prefixing the array, the other data types that compose the array are
concatenated one after the other. For example, an Array of three
integers is encoded as follows:
*3\r\n:1\r\n:2\r\n:3\r\n
Arrays can contain mixed data types. For instance, the following encoding is of a list of four integers and a bulk string:
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$5\r\n
hello\r\n
(The raw RESP encoding is split into multiple lines for readability).
The first line the server sent is *5\r\n
. This numeric
value tells the client that five reply types are about to follow it.
Then, every successive reply constitutes an element in the array.
All of the aggregate RESP types support nesting. For example, a nested array of two arrays is encoded as follows:
*2\r\n
*3\r\n
:1\r\n
:2\r\n
:3\r\n
*2\r\n
+Hello\r\n
-World\r\n
(The raw RESP encoding is split into multiple lines for readability).
The above encodes a two-element array. The first element is an array that, in turn, contains three integers (1, 2, 3). The second element is another array containing a simple string and an error.
Note: In some places, the RESP Array type may be referred to as multi bulk. The two are the same.
The null data type represents non-existent values.
In RESP3, null is encoded using the underscore (_
)
character, followed by the CRLF terminator (\r\n
). Here’s
null’s raw RESP encoding:
_\r\n
RESP2 features two specially crafted values for representing null
values, known as “null bulk strings” and “null arrays”. This duality has
always been a redundancy that added zero semantical value to the
protocol itself. The null type, introduced in RESP3, aims to fix this
wrong. Clients should handle all these representations of null in the
same way. For example, a Ruby library should return nil
while a C library should return NULL
(or set a special flag
in the reply object).
Whereas RESP3 has a dedicated data type for null values, RESP2 has no such type. Instead, due to historical reasons, the representation of null values in RESP2 is via predetermined forms of the bulk strings and arrays types.
The null bulk string represents a non-existing value. The
GET
command returns the Null Bulk String when the target
key doesn’t exist.
It is encoded as a bulk string with the length of negative one (-1), like so:
$-1\r\n
A Valkey client should return a nil object when the server replies with a null bulk string rather than the empty string.
Whereas RESP3 has a dedicated data type for null values, RESP2 has no such type. Instead, due to historical reasons, the representation of null values in RESP2 is via predetermined forms of the Bulk Strings and arrays types.
Null arrays exist as an alternative way of representing a null value.
For instance, when the BLPOP
command times out, it returns
a null array.
The encoding of a null array is that of an array with the length of -1, i.e.
*-1\r\n
When Valkey replies with a null array, the client should return a null object rather than an empty array.
Single elements of an array may be null. This is
used in Valkey replies to signal that these elements are missing and not
empty strings. This can happen, for example, with the SORT
command when used with the GET pattern
option if the
specified key is missing.
Here’s an example of an array reply containing a null element, represented as a RESP2 null bulk string:
*3\r\n
$5\r\n
hello\r\n
$-1\r\n
$5\r\n
world\r\n
Above, the second element is null. The client library should return to its caller something like this:
["hello",nil,"world"]
RESP booleans are encoded as follows:
#<t|f>\r\n
#
) as the first byte.t
character for true values, or an f
character for false ones.The Double RESP type encodes a double-precision floating point value. Doubles are encoded as follows:
,[<+|->]<integral>[.<fractional>][<E|e>[sign]<exponent>]\r\n
,
) as the first byte.+
) or minus (-
) as the
sign.0
..9
) as an
unsigned, base-10 integral value..
), followed by one or more decimal
digits (0
..9
) as an unsigned, base-10
fractional value.E
or
e
), followed by an optional plus (+
) or minus
(-
) as the exponent’s sign, ending with one or more decimal
digits (0
..9
) as an unsigned, base-10 exponent
value.Here’s the encoding of the number 1.23:
,1.23\r\n
Because the fractional part is optional, the integer value of ten (10) can, therefore, be RESP-encoded both as an integer as well as a double:
:10\r\n
,10\r\n
In such cases, the Valkey client should return native integer and double values, respectively, providing that these types are supported by the language of its implementation.
The positive infinity, negative infinity and NaN values are encoded as follows:
,inf\r\n
,-inf\r\n
,nan\r\n
This type can encode integer values outside the range of signed 64-bit integers.
Big numbers use the following encoding:
([+|-]<number>\r\n
(
) as the first
byte.+
) or minus (-
) as the
sign.0
..9
) as an
unsigned, base-10 value.Example:
(3492890328409238509324850943850943825024385\r\n
Big numbers can be positive or negative but can’t include fractionals. Client libraries written in languages with a big number type should return a big number. When big numbers aren’t supported, the client should return a string and, when possible, signal to the caller that the reply is a big integer (depending on the API used by the client library).
This type combines the purpose of simple errors with the expressive power of bulk strings.
It is encoded as:
!<length>\r\n<error>\r\n
!
) as the first byte.0
..9
) as the
error’s length, in bytes, as an unsigned, base-10 value.As a convention, the error begins with an uppercase (space-delimited) word that conveys the error message.
For instance, the error “SYNTAX invalid syntax” is represented by the following protocol encoding:
!21\r\n
SYNTAX invalid syntax\r\n
(The raw RESP encoding is split into multiple lines for readability).
This type is similar to the bulk string, with the addition of providing a hint about the data’s encoding.
A verbatim string’s RESP encoding is as follows:
=<length>\r\n<encoding>:<data>\r\n
=
) as the first byte.0
..9
) as the
string’s total length, in bytes, as an unsigned, base-10 value.:
) character separates the encoding and
data.Example:
=15\r\n
txt:Some string\r\n
(The raw RESP encoding is split into multiple lines for readability).
Some client libraries may ignore the difference between this type and
the string type and return a native string in both cases. However,
interactive clients, such as command line interfaces (e.g., valkey-cli
), can use this type and know
that their output should be presented to the human user as is and
without quoting the string.
For example, the Valkey command INFO
outputs a report
that includes newlines. When using RESP3, valkey-cli
displays it correctly because it is sent as a Verbatim String reply
(with its three bytes being “txt”). When using RESP2, however, the
valkey-cli
is hard-coded to look for the INFO
command to ensure its correct display to the user.
The RESP map encodes a collection of key-value tuples, i.e., a dictionary or a hash.
It is encoded as follows:
%<number-of-entries>\r\n<key-1><value-1>...<key-n><value-n>
%
) as the first byte.0
..9
) as the
number of entries, or key-value tuples, in the map as an unsigned,
base-10 value.For example, the following JSON object:
{
"first": 1,
"second": 2
}
Can be encoded in RESP like so:
%2\r\n
+first\r\n
:1\r\n
+second\r\n
:2\r\n
(The raw RESP encoding is split into multiple lines for readability).
Both map keys and values can be any of RESP’s types.
Valkey clients should return the idiomatic dictionary type that their language provides. However, low-level programming languages (such as C, for example) will likely return an array along with type information that indicates to the caller that it is a dictionary.
Note: RESP2 doesn’t have a map type. A map in RESP2
is represented by a flat array containing the keys and the values. The
first element is a key, followed by the corresponding value, then the
next key and so on, like this:
key1, value1, key2, value2, ...
.
Sets are somewhat like Arrays but are unordered and should only contain unique elements.
RESP set’s encoding is:
~<number-of-elements>\r\n<element-1>...<element-n>
~
) as the first byte.0
..9
) as the
number of elements in the set as an unsigned, base-10 value.Clients should return the native set type if it is available in their programming language. Alternatively, in the absence of a native set type, an array coupled with type information can be used (in C, for example).
RESP’s pushes contain out-of-band data. They are an exception to the protocol’s request-response model and provide a generic push mode for connections.
Push events are encoded similarly to arrays, differing only in their first byte:
><number-of-elements>\r\n<element-1>...<element-n>
>
) as the first byte.0
..9
) as the
number of elements in the message as an unsigned, base-10 value.Pushed data may precede or follow any of RESP’s data types but never inside them. That means a client won’t find push data in the middle of a map reply, for example. It also means that pushed data may appear before or after a command’s reply, as well as by itself (without calling any command).
Clients should react to pushes by invoking a callback that implements their handling of the pushed data.
New RESP connections should begin the session by calling the
HELLO
command. This practice accomplishes two things:
HELLO
command returns information about the server
and the protocol that the client can use for different goals.The HELLO
command has the following high-level
syntax:
HELLO <protocol-version> [optional-arguments]
The first argument of the command is the protocol version we want the
connection to be set. By default, the connection starts in RESP2 mode.
If we specify a connection version that is too big and unsupported by
the server, it should reply with a -NOPROTO
error.
Example:
Client: HELLO 4
Server: -NOPROTO sorry, this protocol version is not supported.
At that point, the client may retry with a lower protocol version.
Similarly, the client can easily detect a server that is only able to speak RESP2:
Client: HELLO 3
Server: -ERR unknown command 'HELLO'
The client can then proceed and use RESP2 to communicate with the server.
Note that even if the protocol’s version is supported, the
HELLO
command may return an error, perform no action and
remain in RESP2 mode. For example, when used with invalid authentication
credentials in the command’s optional AUTH
clause:
Client: HELLO 3 AUTH default mypassword
Server: -ERR invalid password
(the connection remains in RESP2 mode)
A successful reply to the HELLO
command is a map reply.
The information in the reply is partly server-dependent, but certain
fields are mandatory for all the RESP3 implementations: *
server: “redis” (or other software name). *
version: the server’s version. *
proto: the highest supported version of the RESP
protocol.
In Valkey’ RESP3 implementation, the following fields are also emitted:
Now that you are familiar with the RESP serialization format, you can use it to help write a Valkey client library. We can further specify how the interaction between the client and the server works:
So, for example, a typical interaction could be the following.
The client sends the command LLEN mylist
to get the
length of the list stored at the key mylist. Then the server
replies with an integer reply as in the
following example (C:
is the client, S:
the
server).
C: *2\r\n
C: $4\r\n
C: LLEN\r\n
C: $6\r\n
C: mylist\r\n
S: :48293\r\n
As usual, we separate different parts of the protocol with newlines
for simplicity, but the actual interaction is the client sending
*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n
as a whole.
A client can use the same connection to issue multiple commands. Pipelining is supported, so multiple commands can be sent with a single write operation by the client. The client can skip reading replies and continue to send the commands one after the other. All the replies can be read at the end.
For more information, see Pipelining.
Sometimes you may need to send a command to the Valkey server but
only have telnet
available. While the Valkey protocol is
simple to implement, it is not ideal for interactive sessions, and
valkey-cli
may not always be available. For this reason,
Valkey also accepts commands in the inline command format.
The following example demonstrates a server/client exchange using an
inline command (the server chat starts with S:
, the client
chat with C:
):
C: PING
S: +PONG
Here’s another example of an inline command where the server returns an integer:
C: EXISTS somekey
S: :0
Basically, to issue an inline command, you write space-separated
arguments in a telnet session. Since no command starts with
*
(the identifying byte of RESP Arrays), Valkey detects
this condition and parses your command inline.
While the Valkey protocol is human-readable and easy to implement, its implementation can exhibit performance similar to that of a binary protocol.
RESP uses prefixed lengths to transfer bulk data. That makes scanning the payload for special characters unnecessary (unlike parsing JSON, for example). For the same reason, quoting and escaping the payload isn’t needed.
Reading the length of aggregate types (for example, bulk strings or arrays) can be processed with code that performs a single operation per character while at the same time scanning for the CR character.
Example (in C):
#include <stdio.h>
int main(void) {
unsigned char *p = "$123\r\n";
int len = 0;
++;
pwhile(*p != '\r') {
= (len*10)+(*p - '0');
len ++;
p}
/* Now p points at '\r', and the len is in bulk_len. */
("%d\n", len);
printfreturn 0;
}
After the first CR is identified, it can be skipped along with the following LF without further processing. Then, the bulk data can be read with a single read operation that doesn’t inspect the payload in any way. Finally, the remaining CR and LF characters are discarded without additional processing.
While comparable in performance to a binary protocol, the Valkey protocol is significantly more straightforward to implement in most high-level languages, reducing the number of bugs in client software.
For testing purposes, use Lua’s type conversions to have Valkey reply with any RESP2/RESP3 needed. As an example, a RESP3 double can be generated like so:
EVAL "return { double = tonumber(ARGV[1]) }" 0 1e0