mirror of https://github.com/redis/redis.git
Replace all usage of ziplist with listpack for t_hash (#8887)
Part one of implementing #8702 (taking hashes first before other types) ## Description of the feature 1. Change ziplist encoded hash objects to listpack encoding. 2. Convert existing ziplists on RDB loading time. an O(n) operation. ## Rdb format changes 1. Add RDB_TYPE_HASH_LISTPACK rdb type. 2. Bump RDB_VERSION to 10 ## Interface changes 1. New `hash-max-listpack-entries` config is an alias for `hash-max-ziplist-entries` (same with `hash-max-listpack-value`) 2. OBJECT ENCODING will return `listpack` instead of `ziplist` ## Listpack improvements: 1. Support direct insert, replace integer element (rather than convert back and forth from string) 3. Add more listpack capabilities to match the ziplist ones (like `lpFind`, `lpRandomPairs` and such) 4. Optimize element length fetching, avoid multiple calculations 5. Use inline to avoid function call overhead. ## Tests 1. Add a new test to the RDB load time conversion 2. Adding the listpack unit tests. (based on the one in ziplist.c) 3. Add a few "corrupt payload: fuzzer findings" tests, and slightly modify existing ones. Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
parent
cbda492909
commit
02fd76b97c
|
@ -1704,8 +1704,8 @@ notify-keyspace-events ""
|
||||||
# Hashes are encoded using a memory efficient data structure when they have a
|
# Hashes are encoded using a memory efficient data structure when they have a
|
||||||
# small number of entries, and the biggest entry does not exceed a given
|
# small number of entries, and the biggest entry does not exceed a given
|
||||||
# threshold. These thresholds can be configured using the following directives.
|
# threshold. These thresholds can be configured using the following directives.
|
||||||
hash-max-ziplist-entries 512
|
hash-max-listpack-entries 512
|
||||||
hash-max-ziplist-value 64
|
hash-max-listpack-value 64
|
||||||
|
|
||||||
# Lists are also encoded in a special way to save a lot of space.
|
# Lists are also encoded in a special way to save a lot of space.
|
||||||
# The number of entries allowed per internal list node can be specified
|
# The number of entries allowed per internal list node can be specified
|
||||||
|
|
|
@ -1105,12 +1105,12 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||||
*
|
*
|
||||||
* The function returns 0 on error, non-zero on success. */
|
* The function returns 0 on error, non-zero on success. */
|
||||||
static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
|
static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
|
||||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *vstr = NULL;
|
unsigned char *vstr = NULL;
|
||||||
unsigned int vlen = UINT_MAX;
|
unsigned int vlen = UINT_MAX;
|
||||||
long long vll = LLONG_MAX;
|
long long vll = LLONG_MAX;
|
||||||
|
|
||||||
hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
|
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll);
|
||||||
if (vstr)
|
if (vstr)
|
||||||
return rioWriteBulkString(r, (char*)vstr, vlen);
|
return rioWriteBulkString(r, (char*)vstr, vlen);
|
||||||
else
|
else
|
||||||
|
|
|
@ -2636,11 +2636,11 @@ standardConfig configs[] = {
|
||||||
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
|
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
|
||||||
|
|
||||||
/* Size_t configs */
|
/* Size_t configs */
|
||||||
createSizeTConfig("hash-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
createSizeTConfig("hash-max-listpack-entries", "hash-max-ziplist-entries", MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("zset-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_entries, 128, INTEGER_CONFIG, NULL, NULL),
|
createSizeTConfig("zset-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_entries, 128, INTEGER_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */
|
createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */
|
||||||
createSizeTConfig("hash-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("hash-max-listpack-value", "hash-max-ziplist-value", MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
||||||
|
|
14
src/db.c
14
src/db.c
|
@ -935,7 +935,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||||
while(intsetGet(o->ptr,pos++,&ll))
|
while(intsetGet(o->ptr,pos++,&ll))
|
||||||
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
|
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
} else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {
|
} else if (o->type == OBJ_ZSET) {
|
||||||
unsigned char *p = ziplistIndex(o->ptr,0);
|
unsigned char *p = ziplistIndex(o->ptr,0);
|
||||||
unsigned char *vstr;
|
unsigned char *vstr;
|
||||||
unsigned int vlen;
|
unsigned int vlen;
|
||||||
|
@ -949,6 +949,18 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||||
p = ziplistNext(o->ptr,p);
|
p = ziplistNext(o->ptr,p);
|
||||||
}
|
}
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
|
} else if (o->type == OBJ_HASH) {
|
||||||
|
unsigned char *p = lpFirst(o->ptr);
|
||||||
|
unsigned char *vstr;
|
||||||
|
int64_t vlen;
|
||||||
|
unsigned char intbuf[LP_INTBUF_SIZE];
|
||||||
|
|
||||||
|
while(p) {
|
||||||
|
vstr = lpGet(p,&vlen,intbuf);
|
||||||
|
listAddNodeTail(keys, createStringObject((char*)vstr,vlen));
|
||||||
|
p = lpNext(o->ptr,p);
|
||||||
|
}
|
||||||
|
cursor = 0;
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Not handled encoding in SCAN.");
|
serverPanic("Not handled encoding in SCAN.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -864,7 +864,7 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
}
|
}
|
||||||
} else if (ob->type == OBJ_HASH) {
|
} else if (ob->type == OBJ_HASH) {
|
||||||
if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (ob->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||||
defragged++, ob->ptr = newzl;
|
defragged++, ob->ptr = newzl;
|
||||||
} else if (ob->encoding == OBJ_ENCODING_HT) {
|
} else if (ob->encoding == OBJ_ENCODING_HT) {
|
||||||
|
|
1178
src/listpack.c
1178
src/listpack.c
File diff suppressed because it is too large
Load Diff
|
@ -45,22 +45,47 @@
|
||||||
#define LP_AFTER 1
|
#define LP_AFTER 1
|
||||||
#define LP_REPLACE 2
|
#define LP_REPLACE 2
|
||||||
|
|
||||||
|
/* Each entry in the listpack is either a string or an integer. */
|
||||||
|
typedef struct {
|
||||||
|
/* When string is used, it is provided with the length (slen). */
|
||||||
|
unsigned char *sval;
|
||||||
|
uint32_t slen;
|
||||||
|
/* When integer is used, 'sval' is NULL, and lval holds the value. */
|
||||||
|
long long lval;
|
||||||
|
} listpackEntry;
|
||||||
|
|
||||||
unsigned char *lpNew(size_t capacity);
|
unsigned char *lpNew(size_t capacity);
|
||||||
void lpFree(unsigned char *lp);
|
void lpFree(unsigned char *lp);
|
||||||
unsigned char* lpShrinkToFit(unsigned char *lp);
|
unsigned char* lpShrinkToFit(unsigned char *lp);
|
||||||
unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, unsigned char *p, int where, unsigned char **newp);
|
unsigned char *lpPrepend(unsigned char *lp, unsigned char *s, uint32_t slen);
|
||||||
unsigned char *lpAppend(unsigned char *lp, unsigned char *ele, uint32_t size);
|
unsigned char *lpPrependInteger(unsigned char *lp, long long lval);
|
||||||
|
unsigned char *lpAppend(unsigned char *lp, unsigned char *s, uint32_t slen);
|
||||||
|
unsigned char *lpAppendInteger(unsigned char *lp, long long lval);
|
||||||
|
unsigned char *lpReplace(unsigned char *lp, unsigned char **p, unsigned char *s, uint32_t slen);
|
||||||
|
unsigned char *lpReplaceInteger(unsigned char *lp, unsigned char **p, long long lval);
|
||||||
unsigned char *lpDelete(unsigned char *lp, unsigned char *p, unsigned char **newp);
|
unsigned char *lpDelete(unsigned char *lp, unsigned char *p, unsigned char **newp);
|
||||||
uint32_t lpLength(unsigned char *lp);
|
unsigned long lpLength(unsigned char *lp);
|
||||||
unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf);
|
unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf);
|
||||||
|
unsigned char *lpGetValue(unsigned char *p, unsigned int *slen, long long *lval);
|
||||||
|
unsigned char *lpFind(unsigned char *lp, unsigned char *p, unsigned char *s, uint32_t slen, unsigned int skip);
|
||||||
unsigned char *lpFirst(unsigned char *lp);
|
unsigned char *lpFirst(unsigned char *lp);
|
||||||
unsigned char *lpLast(unsigned char *lp);
|
unsigned char *lpLast(unsigned char *lp);
|
||||||
unsigned char *lpNext(unsigned char *lp, unsigned char *p);
|
unsigned char *lpNext(unsigned char *lp, unsigned char *p);
|
||||||
unsigned char *lpPrev(unsigned char *lp, unsigned char *p);
|
unsigned char *lpPrev(unsigned char *lp, unsigned char *p);
|
||||||
uint32_t lpBytes(unsigned char *lp);
|
size_t lpBytes(unsigned char *lp);
|
||||||
unsigned char *lpSeek(unsigned char *lp, long index);
|
unsigned char *lpSeek(unsigned char *lp, long index);
|
||||||
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep);
|
typedef int (*listpackValidateEntryCB)(unsigned char *p, void *userdata);
|
||||||
|
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep,
|
||||||
|
listpackValidateEntryCB entry_cb, void *cb_userdata);
|
||||||
unsigned char *lpValidateFirst(unsigned char *lp);
|
unsigned char *lpValidateFirst(unsigned char *lp);
|
||||||
int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes);
|
int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes);
|
||||||
|
unsigned int lpCompare(unsigned char *p, unsigned char *s, uint32_t slen);
|
||||||
|
void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val);
|
||||||
|
void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals);
|
||||||
|
unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals);
|
||||||
|
|
||||||
|
#ifdef REDIS_TEST
|
||||||
|
int listpackTest(int argc, char *argv[], int accurate);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
21
src/module.c
21
src/module.c
|
@ -7990,7 +7990,7 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||||
cursor->cursor = 1;
|
cursor->cursor = 1;
|
||||||
cursor->done = 1;
|
cursor->done = 1;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
} else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {
|
} else if (o->type == OBJ_ZSET) {
|
||||||
unsigned char *p = ziplistIndex(o->ptr,0);
|
unsigned char *p = ziplistIndex(o->ptr,0);
|
||||||
unsigned char *vstr;
|
unsigned char *vstr;
|
||||||
unsigned int vlen;
|
unsigned int vlen;
|
||||||
|
@ -8013,6 +8013,25 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||||
cursor->cursor = 1;
|
cursor->cursor = 1;
|
||||||
cursor->done = 1;
|
cursor->done = 1;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
} else if (o->type == OBJ_HASH) {
|
||||||
|
unsigned char *p = lpFirst(o->ptr);
|
||||||
|
unsigned char *vstr;
|
||||||
|
int64_t vlen;
|
||||||
|
unsigned char intbuf[LP_INTBUF_SIZE];
|
||||||
|
while(p) {
|
||||||
|
vstr = lpGet(p,&vlen,intbuf);
|
||||||
|
robj *field = createStringObject((char*)vstr,vlen);
|
||||||
|
p = lpNext(o->ptr,p);
|
||||||
|
vstr = lpGet(p,&vlen,intbuf);
|
||||||
|
robj *value = createStringObject((char*)vstr,vlen);
|
||||||
|
fn(key, field, value, privdata);
|
||||||
|
p = lpNext(o->ptr,p);
|
||||||
|
decrRefCount(field);
|
||||||
|
decrRefCount(value);
|
||||||
|
}
|
||||||
|
cursor->cursor = 1;
|
||||||
|
cursor->done = 1;
|
||||||
|
ret = 0;
|
||||||
}
|
}
|
||||||
errno = 0;
|
errno = 0;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
11
src/object.c
11
src/object.c
|
@ -255,9 +255,9 @@ robj *createIntsetObject(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
robj *createHashObject(void) {
|
robj *createHashObject(void) {
|
||||||
unsigned char *zl = ziplistNew();
|
unsigned char *zl = lpNew(0);
|
||||||
robj *o = createObject(OBJ_HASH, zl);
|
robj *o = createObject(OBJ_HASH, zl);
|
||||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
o->encoding = OBJ_ENCODING_LISTPACK;
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,8 +342,8 @@ void freeHashObject(robj *o) {
|
||||||
case OBJ_ENCODING_HT:
|
case OBJ_ENCODING_HT:
|
||||||
dictRelease((dict*) o->ptr);
|
dictRelease((dict*) o->ptr);
|
||||||
break;
|
break;
|
||||||
case OBJ_ENCODING_ZIPLIST:
|
case OBJ_ENCODING_LISTPACK:
|
||||||
zfree(o->ptr);
|
lpFree(o->ptr);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
serverPanic("Unknown hash encoding type");
|
serverPanic("Unknown hash encoding type");
|
||||||
|
@ -929,6 +929,7 @@ char *strEncoding(int encoding) {
|
||||||
case OBJ_ENCODING_HT: return "hashtable";
|
case OBJ_ENCODING_HT: return "hashtable";
|
||||||
case OBJ_ENCODING_QUICKLIST: return "quicklist";
|
case OBJ_ENCODING_QUICKLIST: return "quicklist";
|
||||||
case OBJ_ENCODING_ZIPLIST: return "ziplist";
|
case OBJ_ENCODING_ZIPLIST: return "ziplist";
|
||||||
|
case OBJ_ENCODING_LISTPACK: return "listpack";
|
||||||
case OBJ_ENCODING_INTSET: return "intset";
|
case OBJ_ENCODING_INTSET: return "intset";
|
||||||
case OBJ_ENCODING_SKIPLIST: return "skiplist";
|
case OBJ_ENCODING_SKIPLIST: return "skiplist";
|
||||||
case OBJ_ENCODING_EMBSTR: return "embstr";
|
case OBJ_ENCODING_EMBSTR: return "embstr";
|
||||||
|
@ -1038,7 +1039,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
|
||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
}
|
}
|
||||||
} else if (o->type == OBJ_HASH) {
|
} else if (o->type == OBJ_HASH) {
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
asize = sizeof(*o)+zmalloc_size(o->ptr);
|
asize = sizeof(*o)+zmalloc_size(o->ptr);
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
d = o->ptr;
|
d = o->ptr;
|
||||||
|
|
76
src/rdb.c
76
src/rdb.c
|
@ -676,8 +676,8 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
|
||||||
else
|
else
|
||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
case OBJ_HASH:
|
case OBJ_HASH:
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST)
|
if (o->encoding == OBJ_ENCODING_LISTPACK)
|
||||||
return rdbSaveType(rdb,RDB_TYPE_HASH_ZIPLIST);
|
return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK);
|
||||||
else if (o->encoding == OBJ_ENCODING_HT)
|
else if (o->encoding == OBJ_ENCODING_HT)
|
||||||
return rdbSaveType(rdb,RDB_TYPE_HASH);
|
return rdbSaveType(rdb,RDB_TYPE_HASH);
|
||||||
else
|
else
|
||||||
|
@ -897,12 +897,11 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
|
||||||
}
|
}
|
||||||
} else if (o->type == OBJ_HASH) {
|
} else if (o->type == OBJ_HASH) {
|
||||||
/* Save a hash value */
|
/* Save a hash value */
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
|
size_t l = lpBytes((unsigned char*)o->ptr);
|
||||||
|
|
||||||
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
|
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
|
||||||
nwritten += n;
|
nwritten += n;
|
||||||
|
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
dictIterator *di = dictGetIterator(o->ptr);
|
dictIterator *di = dictGetIterator(o->ptr);
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
|
@ -1703,7 +1702,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
o = createHashObject();
|
o = createHashObject();
|
||||||
|
|
||||||
/* Too many entries? Use a hash table right from the start. */
|
/* Too many entries? Use a hash table right from the start. */
|
||||||
if (len > server.hash_max_ziplist_entries)
|
if (len > server.hash_max_listpack_entries)
|
||||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
else if (deep_integrity_validation) {
|
else if (deep_integrity_validation) {
|
||||||
/* In this mode, we need to guarantee that the server won't crash
|
/* In this mode, we need to guarantee that the server won't crash
|
||||||
|
@ -1715,7 +1714,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
|
|
||||||
|
|
||||||
/* Load every field and value into the ziplist */
|
/* Load every field and value into the ziplist */
|
||||||
while (o->encoding == OBJ_ENCODING_ZIPLIST && len > 0) {
|
while (o->encoding == OBJ_ENCODING_LISTPACK && len > 0) {
|
||||||
len--;
|
len--;
|
||||||
/* Load raw strings */
|
/* Load raw strings */
|
||||||
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||||
|
@ -1743,15 +1742,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add pair to ziplist */
|
/* Add pair to listpack */
|
||||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
o->ptr = lpAppend(o->ptr, (unsigned char*)field, sdslen(field));
|
||||||
sdslen(field), ZIPLIST_TAIL);
|
o->ptr = lpAppend(o->ptr, (unsigned char*)value, sdslen(value));
|
||||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
|
||||||
sdslen(value), ZIPLIST_TAIL);
|
|
||||||
|
|
||||||
/* Convert to hash table if size threshold is exceeded */
|
/* Convert to hash table if size threshold is exceeded */
|
||||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
if (sdslen(field) > server.hash_max_listpack_value ||
|
||||||
sdslen(value) > server.hash_max_ziplist_value)
|
sdslen(value) > server.hash_max_listpack_value)
|
||||||
{
|
{
|
||||||
sdsfree(field);
|
sdsfree(field);
|
||||||
sdsfree(value);
|
sdsfree(value);
|
||||||
|
@ -1845,7 +1842,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
rdbtype == RDB_TYPE_LIST_ZIPLIST ||
|
rdbtype == RDB_TYPE_LIST_ZIPLIST ||
|
||||||
rdbtype == RDB_TYPE_SET_INTSET ||
|
rdbtype == RDB_TYPE_SET_INTSET ||
|
||||||
rdbtype == RDB_TYPE_ZSET_ZIPLIST ||
|
rdbtype == RDB_TYPE_ZSET_ZIPLIST ||
|
||||||
rdbtype == RDB_TYPE_HASH_ZIPLIST)
|
rdbtype == RDB_TYPE_HASH_ZIPLIST ||
|
||||||
|
rdbtype == RDB_TYPE_HASH_LISTPACK)
|
||||||
{
|
{
|
||||||
size_t encoded_len;
|
size_t encoded_len;
|
||||||
unsigned char *encoded =
|
unsigned char *encoded =
|
||||||
|
@ -1874,7 +1872,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
/* Convert to ziplist encoded hash. This must be deprecated
|
/* Convert to ziplist encoded hash. This must be deprecated
|
||||||
* when loading dumps created by Redis 2.4 gets deprecated. */
|
* when loading dumps created by Redis 2.4 gets deprecated. */
|
||||||
{
|
{
|
||||||
unsigned char *zl = ziplistNew();
|
unsigned char *zl = lpNew(0);
|
||||||
unsigned char *zi = zipmapRewind(o->ptr);
|
unsigned char *zi = zipmapRewind(o->ptr);
|
||||||
unsigned char *fstr, *vstr;
|
unsigned char *fstr, *vstr;
|
||||||
unsigned int flen, vlen;
|
unsigned int flen, vlen;
|
||||||
|
@ -1884,8 +1882,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
||||||
if (flen > maxlen) maxlen = flen;
|
if (flen > maxlen) maxlen = flen;
|
||||||
if (vlen > maxlen) maxlen = vlen;
|
if (vlen > maxlen) maxlen = vlen;
|
||||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
zl = lpAppend(zl, fstr, flen);
|
||||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
zl = lpAppend(zl, vstr, vlen);
|
||||||
|
|
||||||
/* search for duplicate records */
|
/* search for duplicate records */
|
||||||
sds field = sdstrynewlen(fstr, flen);
|
sds field = sdstrynewlen(fstr, flen);
|
||||||
|
@ -1904,10 +1902,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
zfree(o->ptr);
|
zfree(o->ptr);
|
||||||
o->ptr = zl;
|
o->ptr = zl;
|
||||||
o->type = OBJ_HASH;
|
o->type = OBJ_HASH;
|
||||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
o->encoding = OBJ_ENCODING_LISTPACK;
|
||||||
|
|
||||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries ||
|
if (hashTypeLength(o) > server.hash_max_listpack_entries ||
|
||||||
maxlen > server.hash_max_ziplist_value)
|
maxlen > server.hash_max_listpack_value)
|
||||||
{
|
{
|
||||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
}
|
}
|
||||||
|
@ -1970,16 +1968,44 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
zsetConvert(o,OBJ_ENCODING_SKIPLIST);
|
zsetConvert(o,OBJ_ENCODING_SKIPLIST);
|
||||||
break;
|
break;
|
||||||
case RDB_TYPE_HASH_ZIPLIST:
|
case RDB_TYPE_HASH_ZIPLIST:
|
||||||
|
{
|
||||||
|
unsigned char *lp = lpNew(encoded_len);
|
||||||
|
if (!hashZiplistConvertAndValidateIntegrity(encoded, encoded_len, &lp)) {
|
||||||
|
rdbReportCorruptRDB("Hash ziplist integrity check failed.");
|
||||||
|
zfree(lp);
|
||||||
|
zfree(encoded);
|
||||||
|
o->ptr = NULL;
|
||||||
|
decrRefCount(o);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
zfree(o->ptr);
|
||||||
|
o->ptr = lp;
|
||||||
|
o->type = OBJ_HASH;
|
||||||
|
o->encoding = OBJ_ENCODING_LISTPACK;
|
||||||
|
if (hashTypeLength(o) == 0) {
|
||||||
|
zfree(o->ptr);
|
||||||
|
o->ptr = NULL;
|
||||||
|
decrRefCount(o);
|
||||||
|
goto emptykey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashTypeLength(o) > server.hash_max_listpack_entries) {
|
||||||
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RDB_TYPE_HASH_LISTPACK:
|
||||||
if (deep_integrity_validation) server.stat_dump_payload_sanitizations++;
|
if (deep_integrity_validation) server.stat_dump_payload_sanitizations++;
|
||||||
if (!hashZiplistValidateIntegrity(encoded, encoded_len, deep_integrity_validation)) {
|
if (!hashListpackValidateIntegrity(encoded, encoded_len, deep_integrity_validation)) {
|
||||||
rdbReportCorruptRDB("Hash ziplist integrity check failed.");
|
rdbReportCorruptRDB("Hash listpack integrity check failed.");
|
||||||
zfree(encoded);
|
zfree(encoded);
|
||||||
o->ptr = NULL;
|
o->ptr = NULL;
|
||||||
decrRefCount(o);
|
decrRefCount(o);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
o->type = OBJ_HASH;
|
o->type = OBJ_HASH;
|
||||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
o->encoding = OBJ_ENCODING_LISTPACK;
|
||||||
if (hashTypeLength(o) == 0) {
|
if (hashTypeLength(o) == 0) {
|
||||||
zfree(encoded);
|
zfree(encoded);
|
||||||
o->ptr = NULL;
|
o->ptr = NULL;
|
||||||
|
@ -1987,7 +2013,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||||
goto emptykey;
|
goto emptykey;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
if (hashTypeLength(o) > server.hash_max_listpack_entries)
|
||||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
/* The current RDB version. When the format changes in a way that is no longer
|
/* The current RDB version. When the format changes in a way that is no longer
|
||||||
* backward compatible this number gets incremented. */
|
* backward compatible this number gets incremented. */
|
||||||
#define RDB_VERSION 9
|
#define RDB_VERSION 10
|
||||||
|
|
||||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||||
|
@ -91,10 +91,11 @@
|
||||||
#define RDB_TYPE_HASH_ZIPLIST 13
|
#define RDB_TYPE_HASH_ZIPLIST 13
|
||||||
#define RDB_TYPE_LIST_QUICKLIST 14
|
#define RDB_TYPE_LIST_QUICKLIST 14
|
||||||
#define RDB_TYPE_STREAM_LISTPACKS 15
|
#define RDB_TYPE_STREAM_LISTPACKS 15
|
||||||
|
#define RDB_TYPE_HASH_LISTPACK 16
|
||||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||||
|
|
||||||
/* Test if a type is an object type. */
|
/* Test if a type is an object type. */
|
||||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15))
|
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 16))
|
||||||
|
|
||||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||||
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
|
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
|
||||||
|
|
|
@ -90,7 +90,8 @@ char *rdb_type_string[] = {
|
||||||
"zset-ziplist",
|
"zset-ziplist",
|
||||||
"hash-ziplist",
|
"hash-ziplist",
|
||||||
"quicklist",
|
"quicklist",
|
||||||
"stream"
|
"stream",
|
||||||
|
"hash-listpack"
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Show a few stats collected into 'rdbstate' */
|
/* Show a few stats collected into 'rdbstate' */
|
||||||
|
|
|
@ -6241,7 +6241,8 @@ struct redisTest {
|
||||||
{"crc64", crc64Test},
|
{"crc64", crc64Test},
|
||||||
{"zmalloc", zmalloc_test},
|
{"zmalloc", zmalloc_test},
|
||||||
{"sds", sdsTest},
|
{"sds", sdsTest},
|
||||||
{"dict", dictTest}
|
{"dict", dictTest},
|
||||||
|
{"listpack", listpackTest}
|
||||||
};
|
};
|
||||||
redisTestProc *getTestProcByName(const char *name) {
|
redisTestProc *getTestProcByName(const char *name) {
|
||||||
int numtests = sizeof(redisTests)/sizeof(struct redisTest);
|
int numtests = sizeof(redisTests)/sizeof(struct redisTest);
|
||||||
|
|
16
src/server.h
16
src/server.h
|
@ -707,6 +707,7 @@ typedef struct RedisModuleDigest {
|
||||||
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
|
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
|
||||||
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
|
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
|
||||||
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
|
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
|
||||||
|
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
|
||||||
|
|
||||||
#define LRU_BITS 24
|
#define LRU_BITS 24
|
||||||
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
||||||
|
@ -1569,8 +1570,8 @@ struct redisServer {
|
||||||
int sort_bypattern;
|
int sort_bypattern;
|
||||||
int sort_store;
|
int sort_store;
|
||||||
/* Zip structure config, see redis.conf for more information */
|
/* Zip structure config, see redis.conf for more information */
|
||||||
size_t hash_max_ziplist_entries;
|
size_t hash_max_listpack_entries;
|
||||||
size_t hash_max_ziplist_value;
|
size_t hash_max_listpack_value;
|
||||||
size_t set_max_intset_entries;
|
size_t set_max_intset_entries;
|
||||||
size_t zset_max_ziplist_entries;
|
size_t zset_max_ziplist_entries;
|
||||||
size_t zset_max_ziplist_value;
|
size_t zset_max_ziplist_value;
|
||||||
|
@ -2341,10 +2342,10 @@ unsigned long hashTypeLength(const robj *o);
|
||||||
hashTypeIterator *hashTypeInitIterator(robj *subject);
|
hashTypeIterator *hashTypeInitIterator(robj *subject);
|
||||||
void hashTypeReleaseIterator(hashTypeIterator *hi);
|
void hashTypeReleaseIterator(hashTypeIterator *hi);
|
||||||
int hashTypeNext(hashTypeIterator *hi);
|
int hashTypeNext(hashTypeIterator *hi);
|
||||||
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
|
void hashTypeCurrentFromListpack(hashTypeIterator *hi, int what,
|
||||||
unsigned char **vstr,
|
unsigned char **vstr,
|
||||||
unsigned int *vlen,
|
unsigned int *vlen,
|
||||||
long long *vll);
|
long long *vll);
|
||||||
sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what);
|
sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what);
|
||||||
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll);
|
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll);
|
||||||
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what);
|
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what);
|
||||||
|
@ -2352,7 +2353,8 @@ robj *hashTypeLookupWriteOrCreate(client *c, robj *key);
|
||||||
robj *hashTypeGetValueObject(robj *o, sds field);
|
robj *hashTypeGetValueObject(robj *o, sds field);
|
||||||
int hashTypeSet(robj *o, sds field, sds value, int flags);
|
int hashTypeSet(robj *o, sds field, sds value, int flags);
|
||||||
robj *hashTypeDup(robj *o);
|
robj *hashTypeDup(robj *o);
|
||||||
int hashZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep);
|
int hashZiplistConvertAndValidateIntegrity(unsigned char *zl, size_t size, unsigned char **lp);
|
||||||
|
int hashListpackValidateIntegrity(unsigned char *lp, size_t size, int deep);
|
||||||
|
|
||||||
/* Pub / Sub */
|
/* Pub / Sub */
|
||||||
int pubsubUnsubscribeAllChannels(client *c, int notify);
|
int pubsubUnsubscribeAllChannels(client *c, int notify);
|
||||||
|
|
288
src/t_hash.c
288
src/t_hash.c
|
@ -35,16 +35,16 @@
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/* Check the length of a number of objects to see if we need to convert a
|
/* Check the length of a number of objects to see if we need to convert a
|
||||||
* ziplist to a real hash. Note that we only check string encoded objects
|
* listpack to a real hash. Note that we only check string encoded objects
|
||||||
* as their string length can be queried in constant time. */
|
* as their string length can be queried in constant time. */
|
||||||
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
|
if (o->encoding != OBJ_ENCODING_LISTPACK) return;
|
||||||
|
|
||||||
for (i = start; i <= end; i++) {
|
for (i = start; i <= end; i++) {
|
||||||
if (sdsEncodedObject(argv[i]) &&
|
if (sdsEncodedObject(argv[i]) &&
|
||||||
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
sdslen(argv[i]->ptr) > server.hash_max_listpack_value)
|
||||||
{
|
{
|
||||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
break;
|
break;
|
||||||
|
@ -52,32 +52,30 @@ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the value from a ziplist encoded hash, identified by field.
|
/* Get the value from a listpack encoded hash, identified by field.
|
||||||
* Returns -1 when the field cannot be found. */
|
* Returns -1 when the field cannot be found. */
|
||||||
int hashTypeGetFromZiplist(robj *o, sds field,
|
int hashTypeGetFromListpack(robj *o, sds field,
|
||||||
unsigned char **vstr,
|
unsigned char **vstr,
|
||||||
unsigned int *vlen,
|
unsigned int *vlen,
|
||||||
long long *vll)
|
long long *vll)
|
||||||
{
|
{
|
||||||
unsigned char *zl, *fptr = NULL, *vptr = NULL;
|
unsigned char *zl, *fptr = NULL, *vptr = NULL;
|
||||||
int ret;
|
|
||||||
|
|
||||||
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
|
serverAssert(o->encoding == OBJ_ENCODING_LISTPACK);
|
||||||
|
|
||||||
zl = o->ptr;
|
zl = o->ptr;
|
||||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
fptr = lpFirst(zl);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
fptr = lpFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
/* Grab pointer to the value (fptr points to the field) */
|
/* Grab pointer to the value (fptr points to the field) */
|
||||||
vptr = ziplistNext(zl, fptr);
|
vptr = lpNext(zl, fptr);
|
||||||
serverAssert(vptr != NULL);
|
serverAssert(vptr != NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vptr != NULL) {
|
if (vptr != NULL) {
|
||||||
ret = ziplistGet(vptr, vstr, vlen, vll);
|
*vstr = lpGetValue(vptr, vlen, vll);
|
||||||
serverAssert(ret);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +105,9 @@ sds hashTypeGetFromHashTable(robj *o, sds field) {
|
||||||
* can always check the function return by checking the return value
|
* can always check the function return by checking the return value
|
||||||
* for C_OK and checking if vll (or vstr) is NULL. */
|
* for C_OK and checking if vll (or vstr) is NULL. */
|
||||||
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
*vstr = NULL;
|
*vstr = NULL;
|
||||||
if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0)
|
if (hashTypeGetFromListpack(o, field, vstr, vlen, vll) == 0)
|
||||||
return C_OK;
|
return C_OK;
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
sds value;
|
sds value;
|
||||||
|
@ -143,12 +141,12 @@ robj *hashTypeGetValueObject(robj *o, sds field) {
|
||||||
* exist. */
|
* exist. */
|
||||||
size_t hashTypeGetValueLength(robj *o, sds field) {
|
size_t hashTypeGetValueLength(robj *o, sds field) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *vstr = NULL;
|
unsigned char *vstr = NULL;
|
||||||
unsigned int vlen = UINT_MAX;
|
unsigned int vlen = UINT_MAX;
|
||||||
long long vll = LLONG_MAX;
|
long long vll = LLONG_MAX;
|
||||||
|
|
||||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
|
if (hashTypeGetFromListpack(o, field, &vstr, &vlen, &vll) == 0)
|
||||||
len = vstr ? vlen : sdigits10(vll);
|
len = vstr ? vlen : sdigits10(vll);
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
sds aux;
|
sds aux;
|
||||||
|
@ -164,12 +162,12 @@ size_t hashTypeGetValueLength(robj *o, sds field) {
|
||||||
/* Test if the specified field exists in the given hash. Returns 1 if the field
|
/* Test if the specified field exists in the given hash. Returns 1 if the field
|
||||||
* exists, and 0 when it doesn't. */
|
* exists, and 0 when it doesn't. */
|
||||||
int hashTypeExists(robj *o, sds field) {
|
int hashTypeExists(robj *o, sds field) {
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *vstr = NULL;
|
unsigned char *vstr = NULL;
|
||||||
unsigned int vlen = UINT_MAX;
|
unsigned int vlen = UINT_MAX;
|
||||||
long long vll = LLONG_MAX;
|
long long vll = LLONG_MAX;
|
||||||
|
|
||||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;
|
if (hashTypeGetFromListpack(o, field, &vstr, &vlen, &vll) == 0) return 1;
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
if (hashTypeGetFromHashTable(o, field) != NULL) return 1;
|
if (hashTypeGetFromHashTable(o, field) != NULL) return 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,36 +200,33 @@ int hashTypeExists(robj *o, sds field) {
|
||||||
int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
||||||
int update = 0;
|
int update = 0;
|
||||||
|
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *zl, *fptr, *vptr;
|
unsigned char *zl, *fptr, *vptr;
|
||||||
|
|
||||||
zl = o->ptr;
|
zl = o->ptr;
|
||||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
fptr = lpFirst(zl);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
fptr = lpFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
/* Grab pointer to the value (fptr points to the field) */
|
/* Grab pointer to the value (fptr points to the field) */
|
||||||
vptr = ziplistNext(zl, fptr);
|
vptr = lpNext(zl, fptr);
|
||||||
serverAssert(vptr != NULL);
|
serverAssert(vptr != NULL);
|
||||||
update = 1;
|
update = 1;
|
||||||
|
|
||||||
/* Replace value */
|
/* Replace value */
|
||||||
zl = ziplistReplace(zl, vptr, (unsigned char*)value,
|
zl = lpReplace(zl, &vptr, (unsigned char*)value, sdslen(value));
|
||||||
sdslen(value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!update) {
|
if (!update) {
|
||||||
/* Push new field/value pair onto the tail of the ziplist */
|
/* Push new field/value pair onto the tail of the listpack */
|
||||||
zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
|
zl = lpAppend(zl, (unsigned char*)field, sdslen(field));
|
||||||
ZIPLIST_TAIL);
|
zl = lpAppend(zl, (unsigned char*)value, sdslen(value));
|
||||||
zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
|
|
||||||
ZIPLIST_TAIL);
|
|
||||||
}
|
}
|
||||||
o->ptr = zl;
|
o->ptr = zl;
|
||||||
|
|
||||||
/* Check if the ziplist needs to be converted to a hash table */
|
/* Check if the listpack needs to be converted to a hash table */
|
||||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
if (hashTypeLength(o) > server.hash_max_listpack_entries)
|
||||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
dictEntry *de = dictFind(o->ptr,field);
|
dictEntry *de = dictFind(o->ptr,field);
|
||||||
|
@ -276,16 +271,16 @@ int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
||||||
int hashTypeDelete(robj *o, sds field) {
|
int hashTypeDelete(robj *o, sds field) {
|
||||||
int deleted = 0;
|
int deleted = 0;
|
||||||
|
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *zl, *fptr;
|
unsigned char *zl, *fptr;
|
||||||
|
|
||||||
zl = o->ptr;
|
zl = o->ptr;
|
||||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
fptr = lpFirst(zl);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
fptr = lpFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
|
||||||
if (fptr != NULL) {
|
if (fptr != NULL) {
|
||||||
zl = ziplistDelete(zl,&fptr); /* Delete the key. */
|
zl = lpDelete(zl,fptr,&fptr); /* Delete the key. */
|
||||||
zl = ziplistDelete(zl,&fptr); /* Delete the value. */
|
zl = lpDelete(zl,fptr,&fptr); /* Delete the value. */
|
||||||
o->ptr = zl;
|
o->ptr = zl;
|
||||||
deleted = 1;
|
deleted = 1;
|
||||||
}
|
}
|
||||||
|
@ -308,8 +303,8 @@ int hashTypeDelete(robj *o, sds field) {
|
||||||
unsigned long hashTypeLength(const robj *o) {
|
unsigned long hashTypeLength(const robj *o) {
|
||||||
unsigned long length = ULONG_MAX;
|
unsigned long length = ULONG_MAX;
|
||||||
|
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
length = ziplistLen(o->ptr) / 2;
|
length = lpLength(o->ptr) / 2;
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
length = dictSize((const dict*)o->ptr);
|
length = dictSize((const dict*)o->ptr);
|
||||||
} else {
|
} else {
|
||||||
|
@ -323,7 +318,7 @@ hashTypeIterator *hashTypeInitIterator(robj *subject) {
|
||||||
hi->subject = subject;
|
hi->subject = subject;
|
||||||
hi->encoding = subject->encoding;
|
hi->encoding = subject->encoding;
|
||||||
|
|
||||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
hi->fptr = NULL;
|
hi->fptr = NULL;
|
||||||
hi->vptr = NULL;
|
hi->vptr = NULL;
|
||||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||||
|
@ -343,7 +338,7 @@ void hashTypeReleaseIterator(hashTypeIterator *hi) {
|
||||||
/* Move to the next entry in the hash. Return C_OK when the next entry
|
/* Move to the next entry in the hash. Return C_OK when the next entry
|
||||||
* could be found and C_ERR when the iterator reaches the end. */
|
* could be found and C_ERR when the iterator reaches the end. */
|
||||||
int hashTypeNext(hashTypeIterator *hi) {
|
int hashTypeNext(hashTypeIterator *hi) {
|
||||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *zl;
|
unsigned char *zl;
|
||||||
unsigned char *fptr, *vptr;
|
unsigned char *fptr, *vptr;
|
||||||
|
|
||||||
|
@ -354,16 +349,16 @@ int hashTypeNext(hashTypeIterator *hi) {
|
||||||
if (fptr == NULL) {
|
if (fptr == NULL) {
|
||||||
/* Initialize cursor */
|
/* Initialize cursor */
|
||||||
serverAssert(vptr == NULL);
|
serverAssert(vptr == NULL);
|
||||||
fptr = ziplistIndex(zl, 0);
|
fptr = lpFirst(zl);
|
||||||
} else {
|
} else {
|
||||||
/* Advance cursor */
|
/* Advance cursor */
|
||||||
serverAssert(vptr != NULL);
|
serverAssert(vptr != NULL);
|
||||||
fptr = ziplistNext(zl, vptr);
|
fptr = lpNext(zl, vptr);
|
||||||
}
|
}
|
||||||
if (fptr == NULL) return C_ERR;
|
if (fptr == NULL) return C_ERR;
|
||||||
|
|
||||||
/* Grab pointer to the value (fptr points to the field) */
|
/* Grab pointer to the value (fptr points to the field) */
|
||||||
vptr = ziplistNext(zl, fptr);
|
vptr = lpNext(zl, fptr);
|
||||||
serverAssert(vptr != NULL);
|
serverAssert(vptr != NULL);
|
||||||
|
|
||||||
/* fptr, vptr now point to the first or next pair */
|
/* fptr, vptr now point to the first or next pair */
|
||||||
|
@ -378,22 +373,18 @@ int hashTypeNext(hashTypeIterator *hi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the field or value at iterator cursor, for an iterator on a hash value
|
/* Get the field or value at iterator cursor, for an iterator on a hash value
|
||||||
* encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
|
* encoded as a listpack. Prototype is similar to `hashTypeGetFromListpack`. */
|
||||||
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
|
void hashTypeCurrentFromListpack(hashTypeIterator *hi, int what,
|
||||||
unsigned char **vstr,
|
unsigned char **vstr,
|
||||||
unsigned int *vlen,
|
unsigned int *vlen,
|
||||||
long long *vll)
|
long long *vll)
|
||||||
{
|
{
|
||||||
int ret;
|
serverAssert(hi->encoding == OBJ_ENCODING_LISTPACK);
|
||||||
|
|
||||||
serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST);
|
|
||||||
|
|
||||||
if (what & OBJ_HASH_KEY) {
|
if (what & OBJ_HASH_KEY) {
|
||||||
ret = ziplistGet(hi->fptr, vstr, vlen, vll);
|
*vstr = lpGetValue(hi->fptr, vlen, vll);
|
||||||
serverAssert(ret);
|
|
||||||
} else {
|
} else {
|
||||||
ret = ziplistGet(hi->vptr, vstr, vlen, vll);
|
*vstr = lpGetValue(hi->vptr, vlen, vll);
|
||||||
serverAssert(ret);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,9 +412,9 @@ sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what) {
|
||||||
* can always check the function return by checking the return value
|
* can always check the function return by checking the return value
|
||||||
* type checking if vstr == NULL. */
|
* type checking if vstr == NULL. */
|
||||||
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {
|
||||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
*vstr = NULL;
|
*vstr = NULL;
|
||||||
hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll);
|
hashTypeCurrentFromListpack(hi, what, vstr, vlen, vll);
|
||||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||||
sds ele = hashTypeCurrentFromHashTable(hi, what);
|
sds ele = hashTypeCurrentFromHashTable(hi, what);
|
||||||
*vstr = (unsigned char*) ele;
|
*vstr = (unsigned char*) ele;
|
||||||
|
@ -456,10 +447,11 @@ robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
void hashTypeConvertZiplist(robj *o, int enc) {
|
|
||||||
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
|
|
||||||
|
|
||||||
if (enc == OBJ_ENCODING_ZIPLIST) {
|
void hashTypeConvertListpack(robj *o, int enc) {
|
||||||
|
serverAssert(o->encoding == OBJ_ENCODING_LISTPACK);
|
||||||
|
|
||||||
|
if (enc == OBJ_ENCODING_LISTPACK) {
|
||||||
/* Nothing to do... */
|
/* Nothing to do... */
|
||||||
|
|
||||||
} else if (enc == OBJ_ENCODING_HT) {
|
} else if (enc == OBJ_ENCODING_HT) {
|
||||||
|
@ -480,9 +472,9 @@ void hashTypeConvertZiplist(robj *o, int enc) {
|
||||||
value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||||
ret = dictAdd(dict, key, value);
|
ret = dictAdd(dict, key, value);
|
||||||
if (ret != DICT_OK) {
|
if (ret != DICT_OK) {
|
||||||
serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
|
serverLogHexDump(LL_WARNING,"listpack with dup elements dump",
|
||||||
o->ptr,ziplistBlobLen(o->ptr));
|
o->ptr,lpBytes(o->ptr));
|
||||||
serverPanic("Ziplist corruption detected");
|
serverPanic("Listpack corruption detected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hashTypeReleaseIterator(hi);
|
hashTypeReleaseIterator(hi);
|
||||||
|
@ -495,8 +487,8 @@ void hashTypeConvertZiplist(robj *o, int enc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void hashTypeConvert(robj *o, int enc) {
|
void hashTypeConvert(robj *o, int enc) {
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
hashTypeConvertZiplist(o, enc);
|
hashTypeConvertListpack(o, enc);
|
||||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||||
serverPanic("Not implemented");
|
serverPanic("Not implemented");
|
||||||
} else {
|
} else {
|
||||||
|
@ -515,13 +507,13 @@ robj *hashTypeDup(robj *o) {
|
||||||
|
|
||||||
serverAssert(o->type == OBJ_HASH);
|
serverAssert(o->type == OBJ_HASH);
|
||||||
|
|
||||||
if(o->encoding == OBJ_ENCODING_ZIPLIST){
|
if(o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *zl = o->ptr;
|
unsigned char *zl = o->ptr;
|
||||||
size_t sz = ziplistBlobLen(zl);
|
size_t sz = lpBytes(zl);
|
||||||
unsigned char *new_zl = zmalloc(sz);
|
unsigned char *new_zl = zmalloc(sz);
|
||||||
memcpy(new_zl, zl, sz);
|
memcpy(new_zl, zl, sz);
|
||||||
hobj = createObject(OBJ_HASH, new_zl);
|
hobj = createObject(OBJ_HASH, new_zl);
|
||||||
hobj->encoding = OBJ_ENCODING_ZIPLIST;
|
hobj->encoding = OBJ_ENCODING_LISTPACK;
|
||||||
} else if(o->encoding == OBJ_ENCODING_HT){
|
} else if(o->encoding == OBJ_ENCODING_HT){
|
||||||
dict *d = dictCreate(&hashDictType);
|
dict *d = dictCreate(&hashDictType);
|
||||||
dictExpand(d, dictSize((const dict*)o->ptr));
|
dictExpand(d, dictSize((const dict*)o->ptr));
|
||||||
|
@ -549,8 +541,45 @@ robj *hashTypeDup(robj *o) {
|
||||||
return hobj;
|
return hobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* callback for to check the ziplist doesn't have duplicate records */
|
/* callback for hashZiplistConvertAndValidateIntegrity.
|
||||||
static int _hashZiplistEntryValidation(unsigned char *p, void *userdata) {
|
* Check that the ziplist doesn't have duplicate hash field names.
|
||||||
|
* The ziplist element pointed by 'p' will be converted and stored into listpack. */
|
||||||
|
static int _hashZiplistEntryConvertAndValidation(unsigned char *p, void *userdata) {
|
||||||
|
unsigned char *str;
|
||||||
|
unsigned int slen;
|
||||||
|
long long vll;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
long count;
|
||||||
|
dict *fields;
|
||||||
|
unsigned char **lp;
|
||||||
|
} *data = userdata;
|
||||||
|
|
||||||
|
if (!ziplistGet(p, &str, &slen, &vll))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Even records are field names, add to dict and check that's not a dup */
|
||||||
|
if (((data->count) & 1) == 0) {
|
||||||
|
sds field = str? sdsnewlen(str, slen): sdsfromlonglong(vll);
|
||||||
|
if (dictAdd(data->fields, field, NULL) != DICT_OK) {
|
||||||
|
/* Duplicate, return an error */
|
||||||
|
sdsfree(field);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
*(data->lp) = lpAppend(*(data->lp), (unsigned char*)str, slen);
|
||||||
|
} else {
|
||||||
|
*(data->lp) = lpAppendInteger(*(data->lp), vll);
|
||||||
|
}
|
||||||
|
|
||||||
|
(data->count)++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* callback for to check the listpack doesn't have duplicate records */
|
||||||
|
static int _hashListpackEntryValidation(unsigned char *p, void *userdata) {
|
||||||
struct {
|
struct {
|
||||||
long count;
|
long count;
|
||||||
dict *fields;
|
dict *fields;
|
||||||
|
@ -559,11 +588,11 @@ static int _hashZiplistEntryValidation(unsigned char *p, void *userdata) {
|
||||||
/* Even records are field names, add to dict and check that's not a dup */
|
/* Even records are field names, add to dict and check that's not a dup */
|
||||||
if (((data->count) & 1) == 0) {
|
if (((data->count) & 1) == 0) {
|
||||||
unsigned char *str;
|
unsigned char *str;
|
||||||
unsigned int slen;
|
int64_t slen;
|
||||||
long long vll;
|
unsigned char buf[LP_INTBUF_SIZE];
|
||||||
if (!ziplistGet(p, &str, &slen, &vll))
|
|
||||||
return 0;
|
str = lpGet(p, &slen, buf);
|
||||||
sds field = str? sdsnewlen(str, slen): sdsfromlonglong(vll);
|
sds field = sdsnewlen(str, slen);
|
||||||
if (dictAdd(data->fields, field, NULL) != DICT_OK) {
|
if (dictAdd(data->fields, field, NULL) != DICT_OK) {
|
||||||
/* Duplicate, return an error */
|
/* Duplicate, return an error */
|
||||||
sdsfree(field);
|
sdsfree(field);
|
||||||
|
@ -575,20 +604,19 @@ static int _hashZiplistEntryValidation(unsigned char *p, void *userdata) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate the integrity of the data structure.
|
/* Validate the integrity of the data structure while converting it to
|
||||||
* when `deep` is 0, only the integrity of the header is validated.
|
* listpack and storing it at 'lp'.
|
||||||
* when `deep` is 1, we scan all the entries one by one. */
|
* The function is safe to call on non-validated ziplists, it returns 0
|
||||||
int hashZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) {
|
* when encounter an integrity validation issue. */
|
||||||
if (!deep)
|
int hashZiplistConvertAndValidateIntegrity(unsigned char *zl, size_t size, unsigned char **lp) {
|
||||||
return ziplistValidateIntegrity(zl, size, 0, NULL, NULL);
|
|
||||||
|
|
||||||
/* Keep track of the field names to locate duplicate ones */
|
/* Keep track of the field names to locate duplicate ones */
|
||||||
struct {
|
struct {
|
||||||
long count;
|
long count;
|
||||||
dict *fields;
|
dict *fields;
|
||||||
} data = {0, dictCreate(&hashDictType)};
|
unsigned char **lp;
|
||||||
|
} data = {0, dictCreate(&hashDictType), lp};
|
||||||
|
|
||||||
int ret = ziplistValidateIntegrity(zl, size, 1, _hashZiplistEntryValidation, &data);
|
int ret = ziplistValidateIntegrity(zl, size, 1, _hashZiplistEntryConvertAndValidation, &data);
|
||||||
|
|
||||||
/* make sure we have an even number of records. */
|
/* make sure we have an even number of records. */
|
||||||
if (data.count & 1)
|
if (data.count & 1)
|
||||||
|
@ -598,13 +626,36 @@ int hashZiplistValidateIntegrity(unsigned char *zl, size_t size, int deep) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a new sds string from the ziplist entry. */
|
/* Validate the integrity of the listpack structure.
|
||||||
sds hashSdsFromZiplistEntry(ziplistEntry *e) {
|
* when `deep` is 0, only the integrity of the header is validated.
|
||||||
|
* when `deep` is 1, we scan all the entries one by one. */
|
||||||
|
int hashListpackValidateIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||||
|
if (!deep)
|
||||||
|
return lpValidateIntegrity(lp, size, 0, NULL, NULL);
|
||||||
|
|
||||||
|
/* Keep track of the field names to locate duplicate ones */
|
||||||
|
struct {
|
||||||
|
long count;
|
||||||
|
dict *fields;
|
||||||
|
} data = {0, dictCreate(&hashDictType)};
|
||||||
|
|
||||||
|
int ret = lpValidateIntegrity(lp, size, 1, _hashListpackEntryValidation, &data);
|
||||||
|
|
||||||
|
/* make sure we have an even number of records. */
|
||||||
|
if (data.count & 1)
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
dictRelease(data.fields);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a new sds string from the listpack entry. */
|
||||||
|
sds hashSdsFromListpackEntry(listpackEntry *e) {
|
||||||
return e->sval ? sdsnewlen(e->sval, e->slen) : sdsfromlonglong(e->lval);
|
return e->sval ? sdsnewlen(e->sval, e->slen) : sdsfromlonglong(e->lval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reply with bulk string from the ziplist entry. */
|
/* Reply with bulk string from the listpack entry. */
|
||||||
void hashReplyFromZiplistEntry(client *c, ziplistEntry *e) {
|
void hashReplyFromListpackEntry(client *c, listpackEntry *e) {
|
||||||
if (e->sval)
|
if (e->sval)
|
||||||
addReplyBulkCBuffer(c, e->sval, e->slen);
|
addReplyBulkCBuffer(c, e->sval, e->slen);
|
||||||
else
|
else
|
||||||
|
@ -615,7 +666,7 @@ void hashReplyFromZiplistEntry(client *c, ziplistEntry *e) {
|
||||||
* 'key' and 'val' will be set to hold the element.
|
* 'key' and 'val' will be set to hold the element.
|
||||||
* The memory in them is not to be freed or modified by the caller.
|
* The memory in them is not to be freed or modified by the caller.
|
||||||
* 'val' can be NULL in which case it's not extracted. */
|
* 'val' can be NULL in which case it's not extracted. */
|
||||||
void hashTypeRandomElement(robj *hashobj, unsigned long hashsize, ziplistEntry *key, ziplistEntry *val) {
|
void hashTypeRandomElement(robj *hashobj, unsigned long hashsize, listpackEntry *key, listpackEntry *val) {
|
||||||
if (hashobj->encoding == OBJ_ENCODING_HT) {
|
if (hashobj->encoding == OBJ_ENCODING_HT) {
|
||||||
dictEntry *de = dictGetFairRandomKey(hashobj->ptr);
|
dictEntry *de = dictGetFairRandomKey(hashobj->ptr);
|
||||||
sds s = dictGetKey(de);
|
sds s = dictGetKey(de);
|
||||||
|
@ -626,8 +677,8 @@ void hashTypeRandomElement(robj *hashobj, unsigned long hashsize, ziplistEntry *
|
||||||
val->sval = (unsigned char*)s;
|
val->sval = (unsigned char*)s;
|
||||||
val->slen = sdslen(s);
|
val->slen = sdslen(s);
|
||||||
}
|
}
|
||||||
} else if (hashobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
} else if (hashobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
ziplistRandomPair(hashobj->ptr, hashsize, key, val);
|
lpRandomPair(hashobj->ptr, hashsize, key, val);
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Unknown hash encoding");
|
serverPanic("Unknown hash encoding");
|
||||||
}
|
}
|
||||||
|
@ -774,12 +825,12 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *vstr = NULL;
|
unsigned char *vstr = NULL;
|
||||||
unsigned int vlen = UINT_MAX;
|
unsigned int vlen = UINT_MAX;
|
||||||
long long vll = LLONG_MAX;
|
long long vll = LLONG_MAX;
|
||||||
|
|
||||||
ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
|
ret = hashTypeGetFromListpack(o, field, &vstr, &vlen, &vll);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
addReplyNull(c);
|
addReplyNull(c);
|
||||||
} else {
|
} else {
|
||||||
|
@ -871,12 +922,12 @@ void hstrlenCommand(client *c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) {
|
static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) {
|
||||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hi->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
unsigned char *vstr = NULL;
|
unsigned char *vstr = NULL;
|
||||||
unsigned int vlen = UINT_MAX;
|
unsigned int vlen = UINT_MAX;
|
||||||
long long vll = LLONG_MAX;
|
long long vll = LLONG_MAX;
|
||||||
|
|
||||||
hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
|
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll);
|
||||||
if (vstr)
|
if (vstr)
|
||||||
addReplyBulkCBuffer(c, vstr, vlen);
|
addReplyBulkCBuffer(c, vstr, vlen);
|
||||||
else
|
else
|
||||||
|
@ -957,7 +1008,7 @@ void hscanCommand(client *c) {
|
||||||
scanGenericCommand(c,o,cursor);
|
scanGenericCommand(c,o,cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void harndfieldReplyWithZiplist(client *c, unsigned int count, ziplistEntry *keys, ziplistEntry *vals) {
|
static void harndfieldReplyWithListpack(client *c, unsigned int count, listpackEntry *keys, listpackEntry *vals) {
|
||||||
for (unsigned long i = 0; i < count; i++) {
|
for (unsigned long i = 0; i < count; i++) {
|
||||||
if (vals && c->resp > 2)
|
if (vals && c->resp > 2)
|
||||||
addReplyArrayLen(c,2);
|
addReplyArrayLen(c,2);
|
||||||
|
@ -1028,18 +1079,19 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||||
if (withvalues)
|
if (withvalues)
|
||||||
addReplyBulkCBuffer(c, value, sdslen(value));
|
addReplyBulkCBuffer(c, value, sdslen(value));
|
||||||
}
|
}
|
||||||
} else if (hash->encoding == OBJ_ENCODING_ZIPLIST) {
|
} else if (hash->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
ziplistEntry *keys, *vals = NULL;
|
listpackEntry *keys, *vals = NULL;
|
||||||
unsigned long limit, sample_count;
|
unsigned long limit, sample_count;
|
||||||
|
|
||||||
limit = count > HRANDFIELD_RANDOM_SAMPLE_LIMIT ? HRANDFIELD_RANDOM_SAMPLE_LIMIT : count;
|
limit = count > HRANDFIELD_RANDOM_SAMPLE_LIMIT ? HRANDFIELD_RANDOM_SAMPLE_LIMIT : count;
|
||||||
keys = zmalloc(sizeof(ziplistEntry)*limit);
|
keys = zmalloc(sizeof(listpackEntry)*limit);
|
||||||
if (withvalues)
|
if (withvalues)
|
||||||
vals = zmalloc(sizeof(ziplistEntry)*limit);
|
vals = zmalloc(sizeof(listpackEntry)*limit);
|
||||||
while (count) {
|
while (count) {
|
||||||
sample_count = count > limit ? limit : count;
|
sample_count = count > limit ? limit : count;
|
||||||
count -= sample_count;
|
count -= sample_count;
|
||||||
ziplistRandomPairs(hash->ptr, sample_count, keys, vals);
|
lpRandomPairs(hash->ptr, sample_count, keys, vals);
|
||||||
harndfieldReplyWithZiplist(c, sample_count, keys, vals);
|
harndfieldReplyWithListpack(c, sample_count, keys, vals);
|
||||||
}
|
}
|
||||||
zfree(keys);
|
zfree(keys);
|
||||||
zfree(vals);
|
zfree(vals);
|
||||||
|
@ -1133,15 +1185,15 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||||
* to the temporary hash, trying to eventually get enough unique elements
|
* to the temporary hash, trying to eventually get enough unique elements
|
||||||
* to reach the specified count. */
|
* to reach the specified count. */
|
||||||
else {
|
else {
|
||||||
if (hash->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (hash->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
/* it is inefficient to repeatedly pick one random element from a
|
/* it is inefficient to repeatedly pick one random element from a
|
||||||
* ziplist. so we use this instead: */
|
* listpack. so we use this instead: */
|
||||||
ziplistEntry *keys, *vals = NULL;
|
listpackEntry *keys, *vals = NULL;
|
||||||
keys = zmalloc(sizeof(ziplistEntry)*count);
|
keys = zmalloc(sizeof(listpackEntry)*count);
|
||||||
if (withvalues)
|
if (withvalues)
|
||||||
vals = zmalloc(sizeof(ziplistEntry)*count);
|
vals = zmalloc(sizeof(listpackEntry)*count);
|
||||||
serverAssert(ziplistRandomPairsUnique(hash->ptr, count, keys, vals) == count);
|
serverAssert(lpRandomPairsUnique(hash->ptr, count, keys, vals) == count);
|
||||||
harndfieldReplyWithZiplist(c, count, keys, vals);
|
harndfieldReplyWithListpack(c, count, keys, vals);
|
||||||
zfree(keys);
|
zfree(keys);
|
||||||
zfree(vals);
|
zfree(vals);
|
||||||
return;
|
return;
|
||||||
|
@ -1149,7 +1201,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||||
|
|
||||||
/* Hashtable encoding (generic implementation) */
|
/* Hashtable encoding (generic implementation) */
|
||||||
unsigned long added = 0;
|
unsigned long added = 0;
|
||||||
ziplistEntry key, value;
|
listpackEntry key, value;
|
||||||
dict *d = dictCreate(&hashDictType);
|
dict *d = dictCreate(&hashDictType);
|
||||||
dictExpand(d, count);
|
dictExpand(d, count);
|
||||||
while(added < count) {
|
while(added < count) {
|
||||||
|
@ -1158,7 +1210,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||||
/* Try to add the object to the dictionary. If it already exists
|
/* Try to add the object to the dictionary. If it already exists
|
||||||
* free it, otherwise increment the number of objects we have
|
* free it, otherwise increment the number of objects we have
|
||||||
* in the result dictionary. */
|
* in the result dictionary. */
|
||||||
sds skey = hashSdsFromZiplistEntry(&key);
|
sds skey = hashSdsFromListpackEntry(&key);
|
||||||
if (dictAdd(d,skey,NULL) != DICT_OK) {
|
if (dictAdd(d,skey,NULL) != DICT_OK) {
|
||||||
sdsfree(skey);
|
sdsfree(skey);
|
||||||
continue;
|
continue;
|
||||||
|
@ -1168,9 +1220,9 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||||
/* We can reply right away, so that we don't need to store the value in the dict. */
|
/* We can reply right away, so that we don't need to store the value in the dict. */
|
||||||
if (withvalues && c->resp > 2)
|
if (withvalues && c->resp > 2)
|
||||||
addReplyArrayLen(c,2);
|
addReplyArrayLen(c,2);
|
||||||
hashReplyFromZiplistEntry(c, &key);
|
hashReplyFromListpackEntry(c, &key);
|
||||||
if (withvalues)
|
if (withvalues)
|
||||||
hashReplyFromZiplistEntry(c, &value);
|
hashReplyFromListpackEntry(c, &value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Release memory */
|
/* Release memory */
|
||||||
|
@ -1183,7 +1235,7 @@ void hrandfieldCommand(client *c) {
|
||||||
long l;
|
long l;
|
||||||
int withvalues = 0;
|
int withvalues = 0;
|
||||||
robj *hash;
|
robj *hash;
|
||||||
ziplistEntry ele;
|
listpackEntry ele;
|
||||||
|
|
||||||
if (c->argc >= 3) {
|
if (c->argc >= 3) {
|
||||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return;
|
||||||
|
@ -1203,5 +1255,5 @@ void hrandfieldCommand(client *c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
hashTypeRandomElement(hash,hashTypeLength(hash),&ele,NULL);
|
hashTypeRandomElement(hash,hashTypeLength(hash),&ele,NULL);
|
||||||
hashReplyFromZiplistEntry(c, &ele);
|
hashReplyFromListpackEntry(c, &ele);
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,24 +241,6 @@ robj *streamDup(robj *o) {
|
||||||
return sobj;
|
return sobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is just a wrapper for lpAppend() to directly use a 64 bit integer
|
|
||||||
* instead of a string. */
|
|
||||||
unsigned char *lpAppendInteger(unsigned char *lp, int64_t value) {
|
|
||||||
char buf[LONG_STR_SIZE];
|
|
||||||
int slen = ll2string(buf,sizeof(buf),value);
|
|
||||||
return lpAppend(lp,(unsigned char*)buf,slen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is just a wrapper for lpReplace() to directly use a 64 bit integer
|
|
||||||
* instead of a string to replace the current element. The function returns
|
|
||||||
* the new listpack as return value, and also updates the current cursor
|
|
||||||
* by updating '*pos'. */
|
|
||||||
unsigned char *lpReplaceInteger(unsigned char *lp, unsigned char **pos, int64_t value) {
|
|
||||||
char buf[LONG_STR_SIZE];
|
|
||||||
int slen = ll2string(buf,sizeof(buf),value);
|
|
||||||
return lpInsert(lp, (unsigned char*)buf, slen, *pos, LP_REPLACE, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is a wrapper function for lpGet() to directly get an integer value
|
/* This is a wrapper function for lpGet() to directly get an integer value
|
||||||
* from the listpack (that may store numbers as a string), converting
|
* from the listpack (that may store numbers as a string), converting
|
||||||
* the string if needed.
|
* the string if needed.
|
||||||
|
@ -3577,7 +3559,7 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||||
|
|
||||||
/* Since we don't want to run validation of all records twice, we'll
|
/* Since we don't want to run validation of all records twice, we'll
|
||||||
* run the listpack validation of just the header and do the rest here. */
|
* run the listpack validation of just the header and do the rest here. */
|
||||||
if (!lpValidateIntegrity(lp, size, 0))
|
if (!lpValidateIntegrity(lp, size, 0, NULL, NULL))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* In non-deep mode we just validated the listpack header (encoded size) */
|
/* In non-deep mode we just validated the listpack header (encoded size) */
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,28 @@
|
||||||
|
tags {"external:skip"} {
|
||||||
|
|
||||||
|
# Copy RDB with ziplist encoded hash to server path
|
||||||
|
set server_path [tmpdir "server.convert-ziplist-hash-on-load"]
|
||||||
|
|
||||||
|
exec cp -f tests/assets/hash-ziplist.rdb $server_path
|
||||||
|
start_server [list overrides [list "dir" $server_path "dbfilename" "hash-ziplist.rdb"]] {
|
||||||
|
test "RDB load ziplist hash: converts to listpack when RDB loading" {
|
||||||
|
r select 0
|
||||||
|
|
||||||
|
assert_encoding listpack hash
|
||||||
|
assert_equal 2 [r hlen hash]
|
||||||
|
assert_match {v1 v2} [r hmget hash f1 f2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec cp -f tests/assets/hash-ziplist.rdb $server_path
|
||||||
|
start_server [list overrides [list "dir" $server_path "dbfilename" "hash-ziplist.rdb" "hash-max-ziplist-entries" 1]] {
|
||||||
|
test "RDB load ziplist hash: converts to hash table when hash-max-ziplist-entries is exceeded" {
|
||||||
|
r select 0
|
||||||
|
|
||||||
|
assert_encoding hashtable hash
|
||||||
|
assert_equal 2 [r hlen hash]
|
||||||
|
assert_match {v1 v2} [r hmget hash f1 f2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,10 +5,10 @@ set server_path [tmpdir "server.convert-zipmap-hash-on-load"]
|
||||||
|
|
||||||
exec cp -f tests/assets/hash-zipmap.rdb $server_path
|
exec cp -f tests/assets/hash-zipmap.rdb $server_path
|
||||||
start_server [list overrides [list "dir" $server_path "dbfilename" "hash-zipmap.rdb"]] {
|
start_server [list overrides [list "dir" $server_path "dbfilename" "hash-zipmap.rdb"]] {
|
||||||
test "RDB load zipmap hash: converts to ziplist" {
|
test "RDB load zipmap hash: converts to listpack" {
|
||||||
r select 0
|
r select 0
|
||||||
|
|
||||||
assert_match "*ziplist*" [r debug object hash]
|
assert_match "*listpack*" [r debug object hash]
|
||||||
assert_equal 2 [r hlen hash]
|
assert_equal 2 [r hlen hash]
|
||||||
assert_match {v1 v2} [r hmget hash f1 f2]
|
assert_match {v1 v2} [r hmget hash f1 f2]
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,32 +43,31 @@ test {corrupt payload: #7445 - without sanitize - 2} {
|
||||||
|
|
||||||
test {corrupt payload: hash with valid zip list header, invalid entry len} {
|
test {corrupt payload: hash with valid zip list header, invalid entry len} {
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
catch {
|
||||||
r restore key 0 "\x0D\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x14\x63\x00\x04\x02\x64\x00\xFF\x09\x00\xD9\x10\x54\x92\x15\xF5\x5F\x52"
|
r restore key 0 "\x0D\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x14\x63\x00\x04\x02\x64\x00\xFF\x09\x00\xD9\x10\x54\x92\x15\xF5\x5F\x52"
|
||||||
r config set hash-max-ziplist-entries 1
|
} err
|
||||||
catch {r hset key b b}
|
assert_match "*Bad data format*" $err
|
||||||
verify_log_message 0 "*zipEntrySafe*" 0
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {corrupt payload: invalid zlbytes header} {
|
test {corrupt payload: invalid zlbytes header} {
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
|
||||||
catch {
|
catch {
|
||||||
r restore key 0 "\x0D\x1B\x25\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x02\x63\x00\x04\x02\x64\x00\xFF\x09\x00\xB7\xF7\x6E\x9F\x43\x43\x14\xC6"
|
r restore key 0 "\x0D\x1B\x25\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x02\x63\x00\x04\x02\x64\x00\xFF\x09\x00\xB7\xF7\x6E\x9F\x43\x43\x14\xC6"
|
||||||
} err
|
} err
|
||||||
assert_match "*Bad data format*" $err
|
assert_match "*Bad data format*" $err
|
||||||
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {corrupt payload: valid zipped hash header, dup records} {
|
test {corrupt payload: valid zipped hash header, dup records} {
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
catch {
|
||||||
r restore key 0 "\x0D\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x02\x61\x00\x04\x02\x64\x00\xFF\x09\x00\xA1\x98\x36\x78\xCC\x8E\x93\x2E"
|
r restore key 0 "\x0D\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x02\x61\x00\x04\x02\x64\x00\xFF\x09\x00\xA1\x98\x36\x78\xCC\x8E\x93\x2E"
|
||||||
r config set hash-max-ziplist-entries 1
|
} err
|
||||||
# cause an assertion when converting to hash table
|
assert_match "*Bad data format*" $err
|
||||||
catch {r hset key b b}
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
verify_log_message 0 "*ziplist with dup elements dump*" 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +234,16 @@ test {corrupt payload: hash ziplist with duplicate records} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {corrupt payload: hash listpack with duplicate records} {
|
||||||
|
# when we do perform full sanitization, we expect duplicate records to fail the restore
|
||||||
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
|
r config set sanitize-dump-payload yes
|
||||||
|
r debug set-skip-checksum-validation 1
|
||||||
|
catch { r RESTORE _hash 0 "\x10\x17\x17\x00\x00\x00\x04\x00\x82a\x00\x03\x82b\x00\x03\x82a\x00\x03\x82d\x00\x03\xff\n\x00\xc0\xcf\xa6\x87\xe5\xa7\xc5\xbe" } err
|
||||||
|
assert_match "*Bad data format*" $err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test {corrupt payload: hash ziplist uneven record count} {
|
test {corrupt payload: hash ziplist uneven record count} {
|
||||||
# when we do perform full sanitization, we expect duplicate records to fail the restore
|
# when we do perform full sanitization, we expect duplicate records to fail the restore
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
|
@ -304,16 +313,14 @@ test {corrupt payload: fuzzer findings - NPD in quicklistIndex} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {corrupt payload: fuzzer findings - invalid read in ziplistFind} {
|
test {corrupt payload: fuzzer findings - encoded entry header reach outside the allocation} {
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
|
||||||
r debug set-skip-checksum-validation 1
|
r debug set-skip-checksum-validation 1
|
||||||
catch {
|
catch {
|
||||||
r RESTORE key 0 "\x0D\x19\x19\x00\x00\x00\x16\x00\x00\x00\x06\x00\x00\xF1\x02\xF1\x02\xF2\x02\x02\x5F\x31\x04\x99\x02\xF3\xFF\x09\x00\xC5\xB8\x10\xC0\x8A\xF9\x16\xDF"
|
r RESTORE key 0 "\x0D\x19\x19\x00\x00\x00\x16\x00\x00\x00\x06\x00\x00\xF1\x02\xF1\x02\xF2\x02\x02\x5F\x31\x04\x99\x02\xF3\xFF\x09\x00\xC5\xB8\x10\xC0\x8A\xF9\x16\xDF"
|
||||||
r HEXISTS key -688319650333
|
} err
|
||||||
}
|
assert_match "*Bad data format*" $err
|
||||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,12 +349,12 @@ test {corrupt payload: fuzzer findings - hash crash} {
|
||||||
|
|
||||||
test {corrupt payload: fuzzer findings - uneven entry count in hash} {
|
test {corrupt payload: fuzzer findings - uneven entry count in hash} {
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
|
||||||
r debug set-skip-checksum-validation 1
|
r debug set-skip-checksum-validation 1
|
||||||
r RESTORE _hashbig 0 "\x0D\x3D\x3D\x00\x00\x00\x38\x00\x00\x00\x14\x00\x00\xF2\x02\x02\x5F\x31\x04\x1C\x02\xF7\x02\xF1\x02\xF1\x02\xF5\x02\xF5\x02\xF4\x02\x02\x5F\x33\x04\xF6\x02\x02\x5F\x35\x04\xF8\x02\x02\x5F\x37\x04\xF9\x02\xF9\x02\xF3\x02\xF3\x02\xFA\x02\x02\x5F\x39\xFF\x09\x00\x73\xB7\x68\xC8\x97\x24\x8E\x88"
|
catch {
|
||||||
catch { r HSCAN _hashbig -250 }
|
r RESTORE _hashbig 0 "\x0D\x3D\x3D\x00\x00\x00\x38\x00\x00\x00\x14\x00\x00\xF2\x02\x02\x5F\x31\x04\x1C\x02\xF7\x02\xF1\x02\xF1\x02\xF5\x02\xF5\x02\xF4\x02\x02\x5F\x33\x04\xF6\x02\x02\x5F\x35\x04\xF8\x02\x02\x5F\x37\x04\xF9\x02\xF9\x02\xF3\x02\xF3\x02\xFA\x02\x02\x5F\x39\xFF\x09\x00\x73\xB7\x68\xC8\x97\x24\x8E\x88"
|
||||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
} err
|
||||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
assert_match "*Bad data format*" $err
|
||||||
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,15 +481,14 @@ test {corrupt payload: fuzzer findings - infinite loop} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {corrupt payload: fuzzer findings - hash convert asserts on RESTORE with shallow sanitization} {
|
test {corrupt payload: fuzzer findings - hash ziplist too long entry len} {
|
||||||
# if we don't perform full sanitization, and the next command can assert on converting
|
|
||||||
# a ziplist to hash records, then we're ok with that happning in RESTORE too
|
|
||||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
r config set sanitize-dump-payload no
|
|
||||||
r debug set-skip-checksum-validation 1
|
r debug set-skip-checksum-validation 1
|
||||||
catch { r RESTORE _hash 0 "\x0D\x3D\x3D\x00\x00\x00\x3A\x00\x00\x00\x14\x13\x00\xF5\x02\xF5\x02\xF2\x02\x53\x5F\x31\x04\xF3\x02\xF3\x02\xF7\x02\xF7\x02\xF8\x02\x02\x5F\x37\x04\xF1\x02\xF1\x02\xF6\x02\x02\x5F\x35\x04\xF4\x02\x02\x5F\x33\x04\xFA\x02\x02\x5F\x39\x04\xF9\x02\xF9\xFF\x09\x00\xB5\x48\xDE\x62\x31\xD0\xE5\x63" }
|
catch {
|
||||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
r RESTORE _hash 0 "\x0D\x3D\x3D\x00\x00\x00\x3A\x00\x00\x00\x14\x13\x00\xF5\x02\xF5\x02\xF2\x02\x53\x5F\x31\x04\xF3\x02\xF3\x02\xF7\x02\xF7\x02\xF8\x02\x02\x5F\x37\x04\xF1\x02\xF1\x02\xF6\x02\x02\x5F\x35\x04\xF4\x02\x02\x5F\x33\x04\xFA\x02\x02\x5F\x39\x04\xF9\x02\xF9\xFF\x09\x00\xB5\x48\xDE\x62\x31\xD0\xE5\x63"
|
||||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
} err
|
||||||
|
assert_match "*Bad data format*" $err
|
||||||
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,5 +694,15 @@ test {corrupt payload: fuzzer findings - hash with len of 0} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {corrupt payload: fuzzer findings - hash listpack first element too long entry len} {
|
||||||
|
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||||
|
r debug set-skip-checksum-validation 1
|
||||||
|
r config set sanitize-dump-payload yes
|
||||||
|
catch { r restore _hash 0 "\x10\x15\x15\x00\x00\x00\x06\x00\xF0\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x02\x01\x02\x01\xFF\x0A\x00\x94\x21\x0A\xFA\x06\x52\x9F\x44" replace } err
|
||||||
|
assert_match "*Bad data format*" $err
|
||||||
|
verify_log_message 0 "*integrity check failed*" 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} ;# tags
|
} ;# tags
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ set ::all_tests {
|
||||||
integration/corrupt-dump
|
integration/corrupt-dump
|
||||||
integration/corrupt-dump-fuzzer
|
integration/corrupt-dump-fuzzer
|
||||||
integration/convert-zipmap-hash-on-load
|
integration/convert-zipmap-hash-on-load
|
||||||
|
integration/convert-ziplist-hash-on-load
|
||||||
integration/logging
|
integration/logging
|
||||||
integration/psync2
|
integration/psync2
|
||||||
integration/psync2-reg
|
integration/psync2-reg
|
||||||
|
|
|
@ -127,10 +127,10 @@ start_server {tags {"aofrw external:skip"} overrides {aof-use-rdb-preamble no}}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach d {string int} {
|
foreach d {string int} {
|
||||||
foreach e {ziplist hashtable} {
|
foreach e {listpack hashtable} {
|
||||||
test "AOF rewrite of hash with $e encoding, $d data" {
|
test "AOF rewrite of hash with $e encoding, $d data" {
|
||||||
r flushall
|
r flushall
|
||||||
if {$e eq {ziplist}} {set len 10} else {set len 1000}
|
if {$e eq {listpack}} {set len 10} else {set len 1000}
|
||||||
for {set j 0} {$j < $len} {incr j} {
|
for {set j 0} {$j < $len} {incr j} {
|
||||||
if {$d eq {string}} {
|
if {$d eq {string}} {
|
||||||
set data [randstring 0 16 alpha]
|
set data [randstring 0 16 alpha]
|
||||||
|
|
|
@ -313,10 +313,10 @@ start_server {tags {"keyspace"}} {
|
||||||
r config set zset-max-ziplist-entries $original_max
|
r config set zset-max-ziplist-entries $original_max
|
||||||
}
|
}
|
||||||
|
|
||||||
test {COPY basic usage for ziplist hash} {
|
test {COPY basic usage for listpack hash} {
|
||||||
r del hash1{t} newhash1{t}
|
r del hash1{t} newhash1{t}
|
||||||
r hset hash1{t} tmp 17179869184
|
r hset hash1{t} tmp 17179869184
|
||||||
assert_encoding ziplist hash1{t}
|
assert_encoding listpack hash1{t}
|
||||||
r copy hash1{t} newhash1{t}
|
r copy hash1{t} newhash1{t}
|
||||||
set digest [debug_digest_value hash1{t}]
|
set digest [debug_digest_value hash1{t}]
|
||||||
assert_equal $digest [debug_digest_value newhash1{t}]
|
assert_equal $digest [debug_digest_value newhash1{t}]
|
||||||
|
|
|
@ -132,11 +132,11 @@ start_server {tags {"scan network"}} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach enc {ziplist hashtable} {
|
foreach enc {listpack hashtable} {
|
||||||
test "HSCAN with encoding $enc" {
|
test "HSCAN with encoding $enc" {
|
||||||
# Create the Hash
|
# Create the Hash
|
||||||
r del hash
|
r del hash
|
||||||
if {$enc eq {ziplist}} {
|
if {$enc eq {listpack}} {
|
||||||
set count 30
|
set count 30
|
||||||
} else {
|
} else {
|
||||||
set count 1000
|
set count 1000
|
||||||
|
|
|
@ -14,8 +14,8 @@ start_server {tags {"hash"}} {
|
||||||
list [r hlen smallhash]
|
list [r hlen smallhash]
|
||||||
} {8}
|
} {8}
|
||||||
|
|
||||||
test {Is the small hash encoded with a ziplist?} {
|
test {Is the small hash encoded with a listpack?} {
|
||||||
assert_encoding ziplist smallhash
|
assert_encoding listpack smallhash
|
||||||
}
|
}
|
||||||
|
|
||||||
proc create_hash {key entries} {
|
proc create_hash {key entries} {
|
||||||
|
@ -34,12 +34,15 @@ start_server {tags {"hash"}} {
|
||||||
return $res
|
return $res
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach {type contents} "ziplist {{a 1} {b 2} {c 3}} hashtable {{a 1} {b 2} {[randstring 70 90 alpha] 3}}" {
|
foreach {type contents} "listpack {{a 1} {b 2} {c 3}} hashtable {{a 1} {b 2} {[randstring 70 90 alpha] 3}}" {
|
||||||
set original_max_value [lindex [r config get hash-max-ziplist-value] 1]
|
set original_max_value [lindex [r config get hash-max-ziplist-value] 1]
|
||||||
r config set hash-max-ziplist-value 10
|
r config set hash-max-ziplist-value 10
|
||||||
create_hash myhash $contents
|
create_hash myhash $contents
|
||||||
assert_encoding $type myhash
|
assert_encoding $type myhash
|
||||||
|
|
||||||
|
# coverage for objectComputeSize
|
||||||
|
assert_morethan [r memory usage myhash] 0
|
||||||
|
|
||||||
test "HRANDFIELD - $type" {
|
test "HRANDFIELD - $type" {
|
||||||
unset -nocomplain myhash
|
unset -nocomplain myhash
|
||||||
array set myhash {}
|
array set myhash {}
|
||||||
|
@ -87,7 +90,7 @@ start_server {tags {"hash"}} {
|
||||||
|
|
||||||
foreach {type contents} "
|
foreach {type contents} "
|
||||||
hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {[randstring 70 90 alpha] 10}}
|
hashtable {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {[randstring 70 90 alpha] 10}}
|
||||||
ziplist {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} " {
|
listpack {{a 1} {b 2} {c 3} {d 4} {e 5} {6 f} {7 g} {8 h} {9 i} {10 j}} " {
|
||||||
test "HRANDFIELD with <count> - $type" {
|
test "HRANDFIELD with <count> - $type" {
|
||||||
set original_max_value [lindex [r config get hash-max-ziplist-value] 1]
|
set original_max_value [lindex [r config get hash-max-ziplist-value] 1]
|
||||||
r config set hash-max-ziplist-value 10
|
r config set hash-max-ziplist-value 10
|
||||||
|
|
Loading…
Reference in New Issue