Decoding Redis Serialization Protocol

Yash KhareYash Khare
6 min read

Redis is an open-source(debatable), in-memory data structure store that can be used as a database, cache, message broker, and streaming engine. It supports various data structures such as strings, hashes, lists, sets, sorted sets, streams, and more. Originally developed by Salvatore Sanfilippo in 2009, Redis has become known for its exceptional performance due to its in-memory nature, with operations typically completing in less than a millisecond, making it an ideal solution for applications requiring real-time data processing and low-latency responses.

What sets Redis apart from traditional databases is its versatility and simplicity. While primarily operating in memory for speed, Redis provides persistence options to save data to disk, ensuring data durability. Its lightweight architecture and straightforward command structure make it accessible to developers, while its support for advanced features like transactions, Lua scripting, and pub/sub messaging patterns allows for sophisticated application development. Redis is widely adopted across industries for use cases including caching, session management, real-time analytics, leaderboards, and job queues.

Understanding RESP(Redis Serialization Protocol)

RESP is the standard protocol used to decode and encode Redis packets, the latest version being RESP3, although RESP2 is still supported.

RESP (Redis Serialization Protocol) is the binary protocol used by Redis clients and servers to communicate with each other. Designed with simplicity, performance, and human readability in mind, RESP efficiently encodes different data types including simple strings, errors, integers, bulk strings, and arrays. This protocol strikes an excellent balance between being easy to parse by machines and remaining comprehensible to humans when examining network traffic, making debugging and development significantly easier.

What makes RESP particularly effective is its straightforward approach to data representation. Each data type begins with a prefix character (like "+" for simple strings or ":" for integers) followed by the actual data and terminated with CRLF. This design allows for efficient parsing without complex state machines while supporting nested data structures through its array type. RESP has evolved through multiple versions, with RESP3 introducing additional data types such as maps and sets to better align with Redis's expanding capabilities, though backward compatibility with earlier protocol versions is maintained for ecosystem stability.

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 Redis 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.

Working Principle of RESP

The first byte in an RESP-serialized payload always identifies its type. Subsequent bytes constitute the type's contents. This design creates an immediate type identification system that allows parsers to efficiently process incoming data without complex look-ahead operations.

Every RESP data type is categorized as either simple, bulk or aggregate:

  • Simple types function like scalars in programming languages, representing plain literal values. Booleans and Integers exemplify this category, where the data is represented directly without additional encoding complexity. Simple types provide the foundational building blocks for more complex data representations.

  • RESP strings exist in two forms: simple or bulk. Simple strings never contain carriage return (\r) or line feed (\n) characters, ensuring they can be reliably terminated with CRLF sequences. Bulk strings, alternatively, can contain any binary data and may also be referred to as binary or blob data. It's important to note that bulk strings may undergo further encoding and decoding processes (such as wide multi-byte encoding) by the client, adding flexibility for internationalization and special character handling.

  • Aggregates, including Arrays and Maps, represent collections that can contain varying numbers of sub-elements and support multiple nesting levels. This capability enables RESP to represent complex data structures and hierarchical relationships, essential for many Redis operations and data modeling requirements.

Below is the table of RESP supported data types and respective first byte(prefix) -

Data TypeFirst Byte (Prefix)Description
Simple String+ (Plus sign)Simple strings that can't contain CR or LF
Error- (Minus sign)Error messages with a similar format to Simple Strings
Integer: (Colon)Signed 64-bit integers
Bulk String$ (Dollar sign)Binary-safe strings with length prefix
Null Bulk String$-1\r\nSpecial case of Bulk String representing null
Array* (Asterisk)Collection of other RESP types
Null Array*-1\r\nSpecial case of Array representing null
Boolean (RESP3)# (Hash/number sign)Boolean true (#t\r\n) or false (#f\r\n)
Double (RESP3), (Comma)Floating-point number
Big Number (RESP3)( (Left parenthesis)Large numbers that don't fit in standard integers
Bulk Error (RESP3)! (Exclamation mark)Error with binary-safe string payload
Verbatim String (RESP3)= (Equals sign)String with a type prefix
Map (RESP3)% (Percent sign)Collection of key-value pairs
Set (RESP3)~ (Tilde)Unordered collection of elements
Null (RESP3)_ (Underscore)Simple null value (_\r\n)
Push (RESP3)> (Greater than)Push data type for pub/sub

Some famous RESP Packets -

  1. “PING” - This packet is used to initialize communication from Redis server to client, used as initial packet for RESP2 version (also present in RESP3, but later)

  2. “PONG” - This packet is sent as a signal for a succesful response from Redis Client to Server upon receiving PING request

  3. “HELLO” - This packet is specific to RESP 3 format , acts as an initial handshake, between client and server , in response we receive a set of Redis parameters like version and protocol

Decoding Redis Packets - A Tough Nut to Crack

Since every packet is in a form of string, it can become very difficult to parse packets , here is an example

packetToBeProcessed := "*2\r\n$5\r\nhello\r\n$1\r\n3\r\n"

The packet received is of type array(denoted by *) of size 2(immediate character after * symbol), followed by CRLF Characters. The first entry of the array is a Bulk String(denoted by $) of size 5, with value “hello
“, and the second entry being another Bulk string of size 3.

Introducing , A Redis Packet Decoder. - https://pkg.go.dev/github.com/khareyash05/redis-packet-decoder

Supports both RESP 2 and RESP 3 protocol and all supported data types , returns in a well decode format! All one has to do is to put in the packet as a whole and enjoy the magic! Below is an example of the magic

import (
    "fmt"
    "log"
    rpd "github.com/khareyash05/redis-packet-decoder"
)

func main() {
    packetToBeProcessed := "*2\r\n$5\r\nhello\r\n$1\r\n3\r\n" // the initial packet in case of RESP3
    decodedPacket, err := rpd.ParseRedis(packetToBeProcessed)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Here is the Decoded Packet", decodedPacket) // output [{Type:array Size:2 Data:[{Type:string Size:5 Data:hello} {Type:string Size:1 Data:3}]}]
}

Try it out in your projects to understand Redis and the protocol underlying the same!

Here is the github repo for the project https://github.com/khareyash05/redis-packet-decoder , if you like it, do not forget to star the repo!

1
Subscribe to my newsletter

Read articles from Yash Khare directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yash Khare
Yash Khare

I am a Computer Vision Analyst