Note: The Redis source code version analyzed in this series is github.com/Sidfate/red… , is the latest version of the article published.
Prospects for the feed
Before introducing quick lists, it is recommended that you take a look at Ziplist and AdList, especially Ziplist (see my article “Redis- Structure – Compressed Lists”). I will explain the adlist briefly below.
adlist
Adlist is just a regular bidirectional list implementation, show you source code:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free) (void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len;
} list;
Copy the code
If you are not familiar with the above structure, you can find it on the Internet or in any data structure book, I won’t go into the details here.
quicklist
In the early version of Redis, the list key is implemented in two ways, ziplist is used when the number of elements is small, and adlist is used when the number of elements is large. However, in the version after 3.2, quickList is used instead:
> lpush test_list 1
(integer) 1
> object encoding test_list
"quicklist"
Copy the code
Why design a QuickList specifically to redefine? Next from the source point of view to interpret. Redis source code comments:
quicklist.c – A doubly linked list of ziplists
Then look at the source code structure implementation:
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int sz; /* Total number of bytes used by ziplist */
unsigned int count : 16; /* Number of ziplist elements */
unsigned int encoding : 2; /* Compressed, 2 indicates compressed, 1 indicates native */
unsigned int container : 2;
unsigned int recompress : 1;
unsigned int attempted_compress : 1;
unsigned int extra : 10;
} quicklistNode;
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* Sum of all ziplists elements */
unsigned long len; /* Number of quicklistNodes */
int fill : 16;
unsigned int compress : 16;
} quicklist;
Copy the code
For detailed explanation of the fields, please refer to the field description section at the end of this article.
So you see why I started with ziplist and Adlist, because quickList is actually a ziplist adlist. No picture to say JB:
Now that we know the structure, back to our original question, why is the structure of QuickList designed this way? Quicklist balances the pros and cons of Ziplist and AdList:
- Bidirectional linked lists are easy to push and pop on both sides of the table, but they are expensive in memory. First, it holds two Pointers on each node in addition to data; Secondly, each node of the bidirectional linked list is a separate memory block, and the address is not continuous, so the number of nodes is easy to generate memory fragmentation, which is not conducive to memory management.
- Ziplist is efficient because it is a single block of contiguous memory. However, it is not conducive to modification operations, as each data change causes an in-memory realLOC. Especially when the Ziplist length is very long, one realloc may result in a large number of data copies, further reducing performance.
But the question remains: how long is the Ziplist contained in a QuickList Node? If the length is small, with the ordinary bidirectional linked list is about the same, or memory fragmentation problem; As the size increases, the ziplist on each QuickList node requires large chunks of contiguous memory, and the efficiency of operating memory decreases. So this length must be a balance value. It is an option provided by Redis. The default value is -2.
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2
Copy the code
The only thing to note is that when list-max-ziplist-size is set to a positive number, it indicates the number of elements stored in each list node.
The compression mechanism
If you look closely at the source code structure of QuickList, you may also notice that the word COMPRESS appears a lot. This is because Redis provides a compression mechanism for QuickList.
When the QuickList is long, the data that is most easily accessed is likely to be on both ends, with the data in the middle being accessed infrequently (and with poor performance). If this is the case, Redis also provides an option to compress the data nodes in the middle for further memory savings. The Redis configuration parameter, list-compressed-depth, is used to do this.
# Lists can also be compressed. The depth of compression is the depth at which ziplist nodes are not needed at the beginning of the list (explained below). In order to perform fast push/ POP operations, the head and tail of the list are usually uncompressed. # 0: the compression mechanism is disabled. # 1: The compression depth 1 means that all internal nodes except the head and tail are compressed. For example: # [head]->node->node->... ->node->[tail] # Since [head], [tail] is never compressed, their direct nodes are compressed later. # 2: [head]->[next]->node->node->... ->node->[prev]->[tail] # 2 uncompress head, head->next, tail->prev and tail. # 3: [head]->[next]->[next]->node->node->... ->node->[prev]->[prev]->[tail] # list-compress-depth 0Copy the code
This parameter is the default is 0, that is, without compression, Redis for internal node quicklist compression algorithm, using LZF – a lossless compression algorithm, interested can look at the zh.wikipedia.org/wiki/LZFSE.
Structure field details
Most of the fields in the source structure have been explained in the previous section, but there are still some fields that I will explain in general here.
First add a structure, quicklistLZF, which will appear in the following instructions:
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
Copy the code
attribute | The size of the | meaning |
---|---|---|
prev | 8 bytes | A pointer to the previous node in the list. |
next | 8 bytes | A pointer to the next node in the linked list. |
zl | 8 bytes | Data pointer. If the current node’s data is not compressed, it points to a Ziplist structure; Otherwise, it points to a quicklistLZF structure. |
sz | 4 bytes | The total number of bytes occupied by ziplist. If the pointed Ziplist is compressed, it still represents the total number of bytes before compression. |
count | 16 | The number of elements contained in ziplist. |
encoding | two | Indicates whether ziplist is compressed. Currently, there are only two values: 2 means compressed (using LZF compression) and 1 means not compressed. |
container | two | Data container. 1 indicates NONE, that is, data is stored directly under a QuickList node. 2 indicates ZIPLIST, that is, data is stored using ZIPLIST. |
recompress | 1 a | Bool. Recompress = 1 is set to bool when we need to decompress the compressed data using a lindex command. |
attempted_compress | 1 a | Bool for unit testing purposes. |
extra | 10 | Expand the field for later use. |
One of the things I find missing from many articles on the web is that the official explanation states that quicklistNode is a 32-byte structure, which should be for 64-bit systems because prev, Next, and ZL are Pointers. It is 8 bytes on a 64-bit system, and the following structure is the same.
attribute | The size of the | meaning |
---|---|---|
head | 8 bytes | Pointer to the head node. |
tail | 8 bytes | Pointer to the tail node. |
count | 8 bytes | Total number of elements of all Ziplists. |
len | 8 bytes | Number of QuickList nodes. |
fill | 16 | Ziplist size Settings for storagelist-max-ziplist-size Parameter value. |
compress | 16 | Node compression depthlist-compress-depth Parameter value. |
It is also important to note that the QuickList structure is 40 bytes on a 64-bit system, but the length I calculated above is 36 bytes. This involves the structure’s byte alignment convention, which is also intended to improve the efficiency of data reading.
attribute | The size of the | meaning |
---|---|---|
sz | 4 bytes | Ziplist size after compression |
compressed | To be determined | LZF Compressed data |
Why is the count in a quicklistNode 16 bits?
We already know that the ziplist size is limited by the list-max-ziplist-size parameter. There are two cases in terms of positive and negative values:
- When this parameter is positive, it exactly represents the maximum number of items contained in the ziplist that zl points to in a quicklistNode structure.
list-max-ziplist-size
The parameter is stored by the QuickList structure’s Fill field, which is 16 bits, so the value it can express can be represented in 16 bits. - When this parameter is negative, the maximum ziplist length that can be represented is 64 Kb. Each data item in Ziplist requires at least 2 bytes to represent (see Redis- Structure – Compressed List). Therefore, the number of items in ziplist should not exceed 32 KILobytes; 16 bits is sufficient.