Let’s Dance on the “Redis-Floor”

Shubham Soni
Dev Genius
Published in
11 min readSep 15, 2022

--

Hello there!! Well in today's world, everything is dynamic implies Data is everywhere and it’s increasing rapidly. To manage that rapid pace, we have to process it at the same pace and that is where “Redis” is our dancing floor to do freestyle fast moves with data.

The other day I was doing some data engineering tasks with document databases

When…

Charlie Puth pinged me:

How long has this been goin’ on? :(

Then…

Shawn Mendes Replied:

There’s Nothing Holdin’ You Back to use Redis :)

So I played the same song, set high bass, and jumped on the Redis Dancing floor. The data was dancing at the Fast n Furious pitch and the floor was like this…

Well, the above illustration is very true once you use Redis but how do these whole things come into the picture? Why is 𝐑𝐞𝐝𝐢𝐬 so 𝐟𝐚𝐬𝐭?

So in this article, we are going to deep diving into the facts which explain the Fast word for Redis. But before going to focus on this part, let's ask Redis to introduce itself:

The official logo: Redis

Redis:

I’m an open-source in-memory data structure storage system, I can be used as a database, cache, and message middleware.

I support multiple types of data structures, such as String, Hash, List, Set, Sorted Set or ZSet and range query, Bitmaps, Hyperloglogs, and Geospatial index radius query. The common data structure types are String, List, Set, Hash, and ZSet.

I have built-in replication (Replication), LUA scripting (Lua scripting), LRU driver events (LRU eviction), transactions (Transactions), and different levels of disk persistence (Persistence), and through Redis sentry and automatic partition (Cluster) to provide high availability.

I also provide options for persistence, these options allow users to save their data to disk for storage. According to the actual situation, you can export the data set to disk (snapshot) at a certain time, or append it to the command log (AOF only appends files), I will copy the executed write command to the hard disk when executing the write command. You can also turn off persistence and use me as an efficient network data cache function.

I do not use tables, and his database will not pre-define or force users to associate different data stored in me.

Now let’s talk about its fast processing or try to figure out the answer to the question: Why is 𝐑𝐞𝐝𝐢𝐬 so 𝐟𝐚𝐬𝐭?

Let’s pay attention here:

The working mode of the database can be divided into a hard disk database and a memory database. Redis stores data in memory, and is not limited by the hard disk I / O speed when reading and writing data, so the speed is extremely fast.

So we have 3 part to cover this story:

How Fast is Redis?

The reason for Redis’s high performance in detail.

Factors affecting Redis performance.

How Fast is Redis:

To understand how fast Redis is going, you need to have an assessment tool. Second, Redis is needed some platform experience data to be used to evaluate Redis performance in the order of magnitude. Fortunately, Redis provides such a tool and gives some experience data of commonly used hardware platforms.

The following is a long article, with the core points as follows:

  1. Redis benchmark can be used to evaluate the performance of Redis. The command line provides the function of evaluating the performance of specific commands in the normal / pipeline mode and under different pressures.
  2. Redis has excellent performance. As a key-value system, the maximum load level is 10W / s, and the set and get time consumption levels are 10ms and 5ms. Using pipelines can improve the performance of Redis operations.

If you don’t care about the specific data, you can directly jump to the second part to understand the reason for the excellent performance of Redis.

Redis performance evaluation tool:

Redis includes the Redis benchmark utility, which can simulate n clients sending m running commands of total queries at the same time (similar to Apache AB utility). You can use the Redis benchmark to evaluate the performance of Redis.

The following options are supported:

Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

-H < hostname > server hostname (default 127.0.0.1)
-P < port > server port (default 6379) -S < socket > server socket (covering host and port) -A < password > server authentication key -C < clients > number of clients started (parallelism) (default 50) -N < requests > total requests (default 100000) -D < size > get and set request data size (default 2 bytes) --DB number selected by dbnum < DB > (default 0) -K < Boolean > 1 = keep alive 0 = connect (default 1) -R < keyspacelen > use random key value in set / get / incr and random VA in Sadd -P < numreq > the number of requests contained in a pipeline. The default value is 1 (pipeline is not used) -Q quiet mode. Only show QPS value --CSV output in CSV format -L generate loop to execute test permanently -T < tests > make the command of test command, and the command list is separated by commas -I idle mode, only open n idle connections and wait

Before starting the benchmark, you need to have a running Redis instance. I tried an example with default parameters:

D:\data\soft\redis-windows>redis-benchmark.exe
.....
====== SET ======
100000 requests completed in 0.81 seconds
50 parallel clients
3 bytes payload
keep alive: 1

99.90% <= 1 milliseconds
99.93% <= 2 milliseconds
99.95% <= 78 milliseconds
99.96% <= 79 milliseconds
100.00% <= 79 milliseconds
123609.39 requests per second

====== GET ======
100000 requests completed in 0.70 seconds
50 parallel clients
3 bytes payload
keep alive: 1

100.00% <= 0 milliseconds
142045.45 requests per second

====== INCR ======
100000 requests completed in 0.71 seconds
50 parallel clients
3 bytes payload
keep alive: 1

99.95% <= 1 milliseconds
99.95% <= 2 milliseconds
100.00% <= 2 milliseconds
140252.45 requests per second
.....

In the above example, the test result of set / get / incr is intercepted.

The test results include the environment parameters of the test (request quantity, client quantity, payload) and the TP value of the request time.

By default, the Redis benchmark uses 100000 requests, 50 clients, and a payload of 3 bytes for testing.

The returned result shows that the total request time of set /get / incr command is less than 0.1s under 100000 requests. Taking QPS = 10W as an example, the calculated average time is about 2ms (1 / (10W / 50)).

Redis performance is related to many factors, which will be described in detail in the third part. For example, the network status of the client, whether to use pipelining and the linked client. To show how fast Redis is, we use a set of data tested by the Redis benchmark on its official website.

Warning: Please note that most of the following benchmarks are several years old and have been obtained using older hardware compared to today’s standards. The page should be updated, but in many cases, using hardware status, you will expect to see twice that number. In addition, in many workloads, redis 4.0 is faster than 2.6

Hardware environment and software configuration:

The test was done by 50 clients executing 2 million requests at the same time.All tests are run on redis 2.6.14.The test was performed using the loopback address (127.0.0.1).Perform the test using a million key space.Perform tests with and without a pipeline (16 command pipelines).
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz

Redis system load:

  1. Test results without pipeline:
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -qSET: 122556.53 requests per second
GET: 123601.76 requests per second
LPUSH: 136752.14 requests per second
LPOP: 132424.03 requests per second

2. Use pipeline test results:

$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q -P 16SET: 195503.42 requests per second
GET: 250187.64 requests per second
LPUSH: 230547.55 requests per second
LPOP: 250815.16 requests per second

It can be seen from the above that, Redis as a key value system, has a read-write load of about 10W + QPS.

Using pipeline technology can significantly improve the read-write performance.

Time Consuming (Test results without pipeline):

$ redis-benchmark -n 100000

====== SET ======
100007 requests completed in 0.88 seconds
50 parallel clients
3 bytes payload
keep alive: 1

58.50% <= 0 milliseconds
99.17% <= 1 milliseconds
99.58% <= 2 milliseconds
99.85% <= 3 milliseconds
99.90% <= 6 milliseconds
100.00% <= 9 milliseconds
114293.71 requests per second

====== GET ======
100000 requests completed in 1.23 seconds
50 parallel clients
3 bytes payload
keep alive: 1

43.12% <= 0 milliseconds
96.82% <= 1 milliseconds
98.62% <= 2 milliseconds
100.00% <= 3 milliseconds
81234.77 requests per second
....

All set operations are completed in 10ms, and get operations are less than 5ms.

Why is Redis so Fast:

Redis is a single-thread application, which means Redis uses a single thread to process the client’s requests. However, we cannot use the multi-core CPU performance in a single-threaded way, but we can improve it by opening multiple Redis instances on a single machine!

The reasons for the high performance of Redis:

Well, we have three main reasons why makes Redis Fast and Furious:

  1. In Memory Storage
  2. IO multiplexing and single-threaded implementation
  3. Optimized lower-lever data structures

So let’s see these three reasons deeply:

In Memory Storage:

Illustration of in-memory storage in Redis (Credit: ByteByteGo)

Redis is fast because it is an in-memory database. Memory access is several orders of magnitude faster than random disk I/O. Pure memory access provides high read and writes throughput and low latency. The trade-off is that the dataset cannot be larger than memory. Code-wise, in-memory data structures are also much easier to implement than their on-disk counterparts. This keeps the code simple, and it contributes to Redis’ rock-solid stability.

IO multiplexing and single-threaded implementation:

Another reason Redis is fast is a bit unintuitive. It is primarily single-threaded which raises some questions.

Why would a single-threaded design lead to high performance? Wouldn’t it be faster if it uses threads to leverage all the CPU cores?

Multi-threaded applications require locks or other synchronization mechanisms. They are notoriously hard to reason about. In many applications, the added complexity is bug-prone and sacrifices stability, making it difficult to justify the performance gain. In the case of Redis, the single-threaded code path is easy to understand.

How does a single-threaded codebase handle many thousands of incoming requests and outgoing responses at the same time? Won’t the thread get blocked waiting for the completion of each request individually?

Now, this is where I/O multiplexing comes into the picture. With I/O multiplexing, the operating system allows a single thread to wait on many socket connections simultaneously. Traditionally, this is done with the select or poll system calls. These system calls are not very performant when there are many thousands of connections. On Linux, Epoll is a performant variant of I/O multiplexing that supports many many thousands of connections in constant time. A drawback of this single-threaded design is that it does not leverage all the CPU cores available in modern hardware. For some workloads, it is not uncommon to have several Redis instances running on a single server to utilize more CPU cores.

Illustration of IO Multiplexing & Single-threaded execution (Credit: ByteByteGo)

let’s see how Redis handles client connections.

In general, Redis uses a reactor design pattern that encapsulates multiple implementations (select, epoll, kqueue, etc.) to multiplex IO to handle requests from clients.

Illustration of Reactor Pattern in Multiplexing

The reactor design pattern is often used to implement event-driven. In addition, Redis encapsulates different libraries for multiplexing io on different platforms. The process is as follows:

Illustration of encapsulation of different libraries of multiplexing I / O in Redis

Because Redis needs to run on multiple platforms, and to maximize the efficiency and performance of execution, different I / O multiplexing functions will be selected as sub-modules according to different compilation platforms.

Redis will preferentially choose the I / O multiplexing function with time complexity of O (1) as the underlying implementation, including the evport in Solaris 10, epoll in Linux, and kqueue in Mac OS / FreeBSD. These functions all use the internal structure of the kernel and can serve hundreds of thousands of file descriptors.

However, if the current compilation environment does not have the above functions, select will be selected as an alternative. Because it will scan all the monitored descriptors when it is used, its time complexity is poor o (n), and it can only serve 1024 file descriptors at the same time, so it is not generally used as the first scheme.

Optimized lower-lever data structures:

Redis provides rich data structures and different implementations in different scenarios.

Redis is a key value system. Different types of keys correspond to different operations or operations correspond to different implementations. The same key will also have different implementations. When Redis operates on the key, it will check the type and call different implementations.

To solve the above problems, Redis has built its own type of system. The main functions of this system include:

Redisobject object.
Type checking based on redisobject objects.
Explicit polymorphic functions based on redisobject objects.
The mechanism to allocate, share and destroy redisobject.

/*
*Redis object
*/
typedef struct redisObject {

//Type
unsigned type:4;

//Alignment bit
unsigned notused:2;

//Coding method
unsigned encoding:4;

//LRU time (relative to server.lruclock )
unsigned lru:22;

//Reference count
int refcount;

//Value to object
void *ptr;

} robj;

Type, encoding, and PTR are the three most important attributes. Redis supports 4 types and 8 codes, respectively:

With redisobject, the operation process of a specific key can be easily implemented:

Illustration of efficient lower-level data structures (Credit: ByteByteGo)

Well, so we alluded to the third reason why Redis is fast. Since Redis is an in-memory database, it could leverage several efficient low-level data structures without worrying about how to persist them to disk efficiently — linked list skip list, and hash table are some examples. There are indeed attempts at implementing new Redis-compatible servers to squeeze more performance out of a single server. With Redis's ease of use, rock-solid stability, and performance, it is our view that Redis still provides the best performance and stability tradeoff in the market.

In addition to providing rich and efficient data structures, Redis also provides efficient algorithms such as hyperloglog and geo index.

Therefore we can conclude that Redis is a very popular in-memory database. It’s rock solid, easy to use, and fast. These attributes explain why it is one of the most loved databases according to Stack Overflow’s annual developer survey

which means…

“Let's Dance on Redis Floor” meaning it's...

“Dance for me, dance for me, dance for me, oh, oh, oh!!”

As we know the string theory of Redis’s Fast performance (well, we’ve explored all 10 dimensions of WH-words) so “Don’t forget to try Redisand stay tuned for the upcoming article where we are going to have an Architecture-Ride on Redis’s Roller-Coaster!!

Ding Dong: This post is in collaboration with Redis.

See some cool References :)

Thank you so much for your Support. See you Again!!

--

--

Data Scientist @Infocepts😎 | Official Tech Writer @Redis 🤘| Lives in Random Forest with Pandas 🐼 & Python 🐍 |