mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
Allow compression of interior quicklist nodes
Let user set how many nodes to *not* compress. We can specify a compression "depth" of how many nodes to leave uncompressed on each end of the quicklist. Depth 0 = disable compression. Depth 1 = only leave head/tail uncompressed. - (read as: "skip 1 node on each end of the list before compressing") Depth 2 = leave head, head->next, tail->prev, tail uncompressed. - ("skip 2 nodes on each end of the list before compressing") Depth 3 = Depth 2 + head->next->next + tail->prev->prev - ("skip 3 nodes...") etc. This also: - updates RDB storage to use native quicklist compression (if node is already compressed) instead of uncompressing, generating the RDB string, then re-compressing the quicklist node. - internalizes the "fill" parameter for the quicklist so we don't need to pass it to _every_ function. Now it's just a property of the list. - allows a runtime-configurable compression option, so we can expose a compresion parameter in the configuration file if people want to trade slight request-per-second performance for up to 90%+ memory savings in some situations. - updates the quicklist tests to do multiple passes: 200k+ tests now.
This commit is contained in:
parent
5127e39980
commit
abdd1414a8
@ -307,7 +307,7 @@ void debugCommand(redisClient *c) {
|
||||
int remaining = sizeof(extra);
|
||||
quicklist *ql = val->ptr;
|
||||
double avg = (double)ql->count/ql->len;
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%lu", ql->len);
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
snprintf(nextra, remaining, " ql_avg_node:%.2f", avg);
|
||||
|
2250
src/quicklist.c
2250
src/quicklist.c
File diff suppressed because it is too large
Load Diff
@ -33,19 +33,50 @@
|
||||
|
||||
/* Node, quicklist, and Iterator are the only data structures used currently. */
|
||||
|
||||
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
|
||||
* We use bit fields keep the quicklistNode at 32 bytes.
|
||||
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
|
||||
* encoding: 2 bits, RAW=1, LZF=2.
|
||||
* container: 2 bits, NONE=1, ZIPLIST=2.
|
||||
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
|
||||
* attempted_compress: 1 bit, boolean, used for verifying during testing.
|
||||
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
|
||||
typedef struct quicklistNode {
|
||||
struct quicklistNode *prev;
|
||||
struct quicklistNode *next;
|
||||
unsigned char *zl;
|
||||
unsigned int count; /* cached count of items in ziplist */
|
||||
unsigned int sz; /* ziplist size in bytes */
|
||||
unsigned int sz; /* ziplist size in bytes */
|
||||
unsigned int count : 16; /* count of items in ziplist */
|
||||
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
|
||||
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
|
||||
unsigned int recompress : 1; /* was this node previous compressed? */
|
||||
unsigned int attempted_compress : 1; /* node can't compress; too small */
|
||||
unsigned int extra : 10; /* more bits to steal for future usage */
|
||||
} quicklistNode;
|
||||
|
||||
/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
|
||||
* 'sz' is byte length of 'compressed' field.
|
||||
* 'compressed' is LZF data with total (compressed) length 'sz'
|
||||
* NOTE: uncompressed length is stored in quicklistNode->sz.
|
||||
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
|
||||
typedef struct quicklistLZF {
|
||||
unsigned int sz; /* LZF size in bytes*/
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
|
||||
* 'count' is the number of total entries.
|
||||
* 'len' is the number of quicklist nodes.
|
||||
* 'compress' is: -1 if compression disabled, otherwise it's the number
|
||||
* of quicklistNodes to leave uncompressed at ends of quicklist.
|
||||
* 'fill' is the user-requested (or default) fill factor. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long len; /* number of quicklistNodes */
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned int len; /* number of quicklistNodes */
|
||||
int fill : 16; /* fill factor for individual nodes */
|
||||
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
@ -69,23 +100,40 @@ typedef struct quicklistEntry {
|
||||
#define QUICKLIST_HEAD 0
|
||||
#define QUICKLIST_TAIL -1
|
||||
|
||||
/* quicklist node encodings */
|
||||
#define QUICKLIST_NODE_ENCODING_RAW 1
|
||||
#define QUICKLIST_NODE_ENCODING_LZF 2
|
||||
|
||||
/* quicklist compression disable */
|
||||
#define QUICKLIST_NOCOMPRESS 0
|
||||
|
||||
/* quicklist container formats */
|
||||
#define QUICKLIST_NODE_CONTAINER_NONE 1
|
||||
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2
|
||||
|
||||
#define quicklistNodeIsCompressed(node) \
|
||||
((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
|
||||
|
||||
/* Prototypes */
|
||||
quicklist *quicklistCreate(void);
|
||||
quicklist *quicklistNew(int fill, int compress);
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int depth);
|
||||
void quicklistSetFill(quicklist *quicklist, int fill);
|
||||
void quicklistSetOptions(quicklist *quicklist, int fill, int depth);
|
||||
void quicklistRelease(quicklist *quicklist);
|
||||
quicklist *quicklistPushHead(quicklist *quicklist, const int fill, void *value,
|
||||
const size_t sz);
|
||||
quicklist *quicklistPushTail(quicklist *quicklist, const int fill, void *value,
|
||||
const size_t sz);
|
||||
void quicklistPush(quicklist *quicklist, const int fill, void *value,
|
||||
const size_t sz, int where);
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
|
||||
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
int where);
|
||||
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
|
||||
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
|
||||
const int fill, unsigned char *zl);
|
||||
quicklist *quicklistCreateFromZiplist(int fill, unsigned char *zl);
|
||||
void quicklistInsertAfter(quicklist *quicklist, const int fill,
|
||||
quicklistEntry *node, void *value, const size_t sz);
|
||||
void quicklistInsertBefore(quicklist *quicklist, const int fill,
|
||||
quicklistEntry *node, void *value, const size_t sz);
|
||||
unsigned char *zl);
|
||||
quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
unsigned char *zl);
|
||||
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
|
||||
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
|
||||
int sz);
|
||||
@ -100,7 +148,7 @@ int quicklistIndex(const quicklist *quicklist, const long long index,
|
||||
quicklistEntry *entry);
|
||||
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRotate(quicklist *quicklist, const int fill);
|
||||
void quicklistRotate(quicklist *quicklist);
|
||||
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, long long *sval,
|
||||
void *(*saver)(unsigned char *data, unsigned int sz));
|
||||
@ -108,6 +156,7 @@ int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, long long *slong);
|
||||
unsigned int quicklistCount(quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
|
62
src/rdb.c
62
src/rdb.c
@ -209,10 +209,33 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
|
||||
return rdbEncodeInteger(value,enc);
|
||||
}
|
||||
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
int rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,
|
||||
size_t original_len) {
|
||||
unsigned char byte;
|
||||
int n, nwritten = 0;
|
||||
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
void *out;
|
||||
|
||||
/* We require at least four bytes compression for this to be worth it */
|
||||
@ -224,26 +247,9 @@ int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
zfree(out);
|
||||
return 0;
|
||||
}
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
size_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);
|
||||
zfree(out);
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
robj *rdbLoadLzfStringObject(rio *rdb) {
|
||||
@ -491,8 +497,15 @@ int rdbSaveObject(rio *rdb, robj *o) {
|
||||
nwritten += n;
|
||||
|
||||
do {
|
||||
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if (quicklistNodeIsCompressed(node)) {
|
||||
void *data;
|
||||
size_t compress_len = quicklistGetLzf(node, &data);
|
||||
if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;
|
||||
nwritten += n;
|
||||
} else {
|
||||
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} while ((node = node->next));
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
@ -822,14 +835,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
o = createQuicklistObject();
|
||||
quicklistSetFill(o->ptr, server.list_max_ziplist_entries);
|
||||
quicklistSetCompress(o->ptr, 0 /*FIXME*/);
|
||||
|
||||
/* Load every single element of the list */
|
||||
while(len--) {
|
||||
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
|
||||
dec = getDecodedObject(ele);
|
||||
size_t len = sdslen(dec->ptr);
|
||||
size_t zlen = server.list_max_ziplist_entries;
|
||||
o->ptr = quicklistPushTail(o->ptr, zlen, dec->ptr, len);
|
||||
o->ptr = quicklistPushTail(o->ptr, dec->ptr, len);
|
||||
decrRefCount(dec);
|
||||
decrRefCount(ele);
|
||||
}
|
||||
|
16
src/t_list.c
16
src/t_list.c
@ -43,10 +43,7 @@ void listTypePush(robj *subject, robj *value, int where) {
|
||||
int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
|
||||
value = getDecodedObject(value);
|
||||
size_t len = sdslen(value->ptr);
|
||||
size_t zlen = server.list_max_ziplist_entries;
|
||||
/* If this value is greater than our allowed values, create it in
|
||||
* an isolated ziplist */
|
||||
quicklistPush(subject->ptr, zlen, value->ptr, len, pos);
|
||||
quicklistPush(subject->ptr, value->ptr, len, pos);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
@ -146,12 +143,11 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
|
||||
value = getDecodedObject(value);
|
||||
sds str = value->ptr;
|
||||
size_t len = sdslen(str);
|
||||
size_t zlen = server.list_max_ziplist_entries;
|
||||
if (where == REDIS_TAIL) {
|
||||
quicklistInsertAfter((quicklist *)entry->entry.quicklist, zlen,
|
||||
quicklistInsertAfter((quicklist *)entry->entry.quicklist,
|
||||
&entry->entry, str, len);
|
||||
} else if (where == REDIS_HEAD) {
|
||||
quicklistInsertBefore((quicklist *)entry->entry.quicklist, zlen,
|
||||
quicklistInsertBefore((quicklist *)entry->entry.quicklist,
|
||||
&entry->entry, str, len);
|
||||
}
|
||||
decrRefCount(value);
|
||||
@ -188,7 +184,7 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
size_t zlen = server.list_max_ziplist_entries;
|
||||
|
||||
subject->encoding = REDIS_ENCODING_QUICKLIST;
|
||||
subject->ptr = quicklistCreateFromZiplist(zlen, subject->ptr);
|
||||
subject->ptr = quicklistCreateFromZiplist(zlen, 0 /*FIXME*/, subject->ptr);
|
||||
} else {
|
||||
redisPanic("Unsupported list conversion");
|
||||
}
|
||||
@ -211,6 +207,8 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
c->argv[j] = tryObjectEncoding(c->argv[j]);
|
||||
if (!lobj) {
|
||||
lobj = createQuicklistObject();
|
||||
quicklistSetFill(lobj->ptr, server.list_max_ziplist_entries);
|
||||
quicklistSetCompress(lobj->ptr, 0 /*FIXME*/);
|
||||
dbAdd(c->db,c->argv[1],lobj);
|
||||
}
|
||||
listTypePush(lobj,c->argv[j],where);
|
||||
@ -539,6 +537,8 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dstobj) {
|
||||
dstobj = createQuicklistObject();
|
||||
quicklistSetFill(dstobj->ptr, server.list_max_ziplist_entries);
|
||||
quicklistSetCompress(dstobj->ptr, 0 /*FIXME*/);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
|
Loading…
Reference in New Issue
Block a user