Replace dict with new hashtable: sorted set datatype (#1427)
This PR replaces dict with hashtable in the ZSET datatype. Instead of mapping key to score as dict did, the hashtable maps key to a node in the skiplist, which contains the score. This takes advantage of hashtable performance improvements and saves 15 bytes per set item - 24 bytes overhead before, 9 bytes after. Closes #1096 --------- Signed-off-by: Rain Valentine <rsg000@gmail.com> Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech> Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
This commit is contained in:
parent
8af35a1712
commit
ab627d6721
21
src/aof.c
21
src/aof.c
@ -1890,30 +1890,29 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, zs->ht);
|
||||
void *next;
|
||||
while (hashtableNext(&iter, &next)) {
|
||||
zskiplistNode *node = next;
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (!rioWriteBulkCount(r, '*', 2 + cmd_items * 2) || !rioWriteBulkString(r, "ZADD", 4) ||
|
||||
!rioWriteBulkObject(r, key)) {
|
||||
dictReleaseIterator(di);
|
||||
hashtableResetIterator(&iter);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!rioWriteBulkDouble(r, *score) || !rioWriteBulkString(r, ele, sdslen(ele))) {
|
||||
dictReleaseIterator(di);
|
||||
sds ele = node->ele;
|
||||
if (!rioWriteBulkDouble(r, node->score) || !rioWriteBulkString(r, ele, sdslen(ele))) {
|
||||
hashtableResetIterator(&iter);
|
||||
return 0;
|
||||
}
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
hashtableResetIterator(&iter);
|
||||
} else {
|
||||
serverPanic("Unknown sorted zset encoding");
|
||||
}
|
||||
|
44
src/db.c
44
src/db.c
@ -1004,13 +1004,6 @@ void dictScanCallback(void *privdata, const dictEntry *de) {
|
||||
if (!data->only_keys) {
|
||||
val = dictGetVal(de);
|
||||
}
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
key = sdsdup(keysds);
|
||||
if (!data->only_keys) {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf, sizeof(buf), *(double *)dictGetVal(de), LD_STR_AUTO);
|
||||
val = sdsnewlen(buf, len);
|
||||
}
|
||||
} else {
|
||||
serverPanic("Type not handled in dict SCAN callback.");
|
||||
}
|
||||
@ -1021,13 +1014,26 @@ void dictScanCallback(void *privdata, const dictEntry *de) {
|
||||
|
||||
void hashtableScanCallback(void *privdata, void *entry) {
|
||||
scanData *data = (scanData *)privdata;
|
||||
sds val = NULL;
|
||||
sds key = NULL;
|
||||
|
||||
robj *o = data->o;
|
||||
list *keys = data->keys;
|
||||
data->sampled++;
|
||||
|
||||
/* currently only implemented for SET scan */
|
||||
serverAssert(o && o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HASHTABLE);
|
||||
sds key = (sds)entry; /* Specific for OBJ_SET */
|
||||
/* This callback is only used for scanning elements within a key (hash
|
||||
* fields, set elements, etc.) so o must be set here. */
|
||||
serverAssert(o != NULL);
|
||||
|
||||
/* get key */
|
||||
if (o->type == OBJ_SET) {
|
||||
key = (sds)entry;
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
zskiplistNode *node = (zskiplistNode *)entry;
|
||||
key = node->ele;
|
||||
} else {
|
||||
serverPanic("Type not handled in hashset SCAN callback.");
|
||||
}
|
||||
|
||||
/* Filter element if it does not match the pattern. */
|
||||
if (data->pattern) {
|
||||
@ -1036,7 +1042,23 @@ void hashtableScanCallback(void *privdata, void *entry) {
|
||||
}
|
||||
}
|
||||
|
||||
if (o->type == OBJ_SET) {
|
||||
/* no value, key used by reference */
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
/* zset data is copied */
|
||||
zskiplistNode *node = (zskiplistNode *)entry;
|
||||
key = sdsdup(node->ele);
|
||||
if (!data->only_keys) {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf, sizeof(buf), node->score, LD_STR_AUTO);
|
||||
val = sdsnewlen(buf, len);
|
||||
}
|
||||
} else {
|
||||
serverPanic("Type not handled in hashset SCAN callback.");
|
||||
}
|
||||
|
||||
listAddNodeTail(keys, key);
|
||||
if (val) listAddNodeTail(keys, val);
|
||||
}
|
||||
|
||||
/* Try to parse a SCAN cursor stored at object 'o':
|
||||
@ -1184,7 +1206,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
|
||||
shallow_copied_list_items = 1;
|
||||
} else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dict_table = zs->dict;
|
||||
hashtable_table = zs->ht;
|
||||
/* scanning ZSET allocates temporary strings even though it's a dict */
|
||||
shallow_copied_list_items = 0;
|
||||
}
|
||||
|
26
src/debug.c
26
src/debug.c
@ -206,20 +206,20 @@ void xorObjectDigest(serverDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, zs->ht);
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
const int len = fpconv_dtoa(*score, buf);
|
||||
void *next;
|
||||
while (hashtableNext(&iter, &next)) {
|
||||
zskiplistNode *node = next;
|
||||
const int len = fpconv_dtoa(node->score, buf);
|
||||
buf[len] = '\0';
|
||||
memset(eledigest, 0, 20);
|
||||
mixDigest(eledigest, sdsele, sdslen(sdsele));
|
||||
mixDigest(eledigest, node->ele, sdslen(node->ele));
|
||||
mixDigest(eledigest, buf, strlen(buf));
|
||||
xorDigest(digest, eledigest, 20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
hashtableResetIterator(&iter);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
@ -284,13 +284,11 @@ void xorObjectDigest(serverDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
robj *o;
|
||||
int j;
|
||||
uint32_t aux;
|
||||
|
||||
memset(final, 0, 20); /* Start with a clean result */
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
for (int j = 0; j < server.dbnum; j++) {
|
||||
serverDb *db = server.db + j;
|
||||
if (kvstoreSize(db->keys) == 0) continue;
|
||||
kvstoreIterator *kvs_it = kvstoreIteratorInit(db->keys);
|
||||
@ -300,7 +298,9 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(final, &aux, sizeof(aux));
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while (kvstoreIteratorNext(kvs_it, (void **)&o)) {
|
||||
void *next;
|
||||
while (kvstoreIteratorNext(kvs_it, &next)) {
|
||||
robj *o = next;
|
||||
sds key;
|
||||
robj *keyobj;
|
||||
|
||||
@ -929,7 +929,7 @@ void debugCommand(client *c) {
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_SKIPLIST: {
|
||||
zset *zs = o->ptr;
|
||||
d = zs->dict;
|
||||
ht = zs->ht;
|
||||
} break;
|
||||
case OBJ_ENCODING_HT: d = o->ptr; break;
|
||||
case OBJ_ENCODING_HASHTABLE: ht = o->ptr; break;
|
||||
|
115
src/defrag.c
115
src/defrag.c
@ -297,55 +297,45 @@ static void zslUpdateNode(zskiplist *zsl, zskiplistNode *oldnode, zskiplistNode
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag helper for sorted set.
|
||||
* Update the robj pointer, defrag the skiplist struct and return the new score
|
||||
* reference. We may not access oldele pointer (not even the pointer stored in
|
||||
* the skiplist), as it was already freed. Newele may be null, in which case we
|
||||
* only need to defrag the skiplist, but not update the obj pointer.
|
||||
* When return value is non-NULL, it is the score reference that must be updated
|
||||
* in the dict record. */
|
||||
static double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x, *newx;
|
||||
int i;
|
||||
sds ele = newele ? newele : oldele;
|
||||
/* Hashtable scan callback for sorted set. It defragments a single skiplist
|
||||
* node, updates skiplist pointers, and updates the hashtable pointer to the
|
||||
* node. */
|
||||
static void activeDefragZsetNode(void *privdata, void *entry_ref) {
|
||||
zskiplist *zsl = privdata;
|
||||
zskiplistNode **node_ref = (zskiplistNode **)entry_ref;
|
||||
zskiplistNode *node = *node_ref;
|
||||
|
||||
/* find the skiplist node referring to the object that was moved,
|
||||
* and all pointers that need to be updated if we'll end up moving the skiplist node. */
|
||||
x = zsl->header;
|
||||
for (i = zsl->level - 1; i >= 0; i--) {
|
||||
while (x->level[i].forward && x->level[i].forward->ele != oldele && /* make sure not to access the
|
||||
->obj pointer if it matches
|
||||
oldele */
|
||||
(x->level[i].forward->score < score ||
|
||||
(x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele, ele) < 0)))
|
||||
x = x->level[i].forward;
|
||||
/* defragment node internals */
|
||||
sds newsds = activeDefragSds(node->ele);
|
||||
if (newsds) node->ele = newsds;
|
||||
|
||||
const double score = node->score;
|
||||
const sds ele = node->ele;
|
||||
|
||||
/* find skiplist pointers that need to be updated if we end up moving the
|
||||
* skiplist node. */
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL];
|
||||
zskiplistNode *x = zsl->header;
|
||||
for (int i = zsl->level - 1; i >= 0; i--) {
|
||||
/* stop when we've reached the end of this level or the next node comes
|
||||
* after our target in sorted order */
|
||||
zskiplistNode *next = x->level[i].forward;
|
||||
while (next &&
|
||||
(next->score < score ||
|
||||
(next->score == score && sdscmp(next->ele, ele) < 0))) {
|
||||
x = next;
|
||||
next = x->level[i].forward;
|
||||
}
|
||||
update[i] = x;
|
||||
}
|
||||
|
||||
/* update the robj pointer inside the skip list record. */
|
||||
x = x->level[0].forward;
|
||||
serverAssert(x && score == x->score && x->ele == oldele);
|
||||
if (newele) x->ele = newele;
|
||||
/* should have arrived at intended node */
|
||||
serverAssert(x->level[0].forward == node);
|
||||
|
||||
/* try to defrag the skiplist record itself */
|
||||
newx = activeDefragAlloc(x);
|
||||
if (newx) {
|
||||
zslUpdateNode(zsl, x, newx, update);
|
||||
return &newx->score;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helper for sorted set.
|
||||
* Defrag a single dict entry key name, and corresponding skiplist struct */
|
||||
static void activeDefragZsetEntry(zset *zs, dictEntry *de) {
|
||||
sds newsds;
|
||||
double *newscore;
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele))) dictSetKey(zs->dict, de, newsds);
|
||||
newscore = zslDefrag(zs->zsl, *(double *)dictGetVal(de), sdsele, newsds);
|
||||
if (newscore) {
|
||||
dictSetVal(zs->dict, de, newscore);
|
||||
zskiplistNode *newnode = activeDefragAlloc(node);
|
||||
if (newnode) {
|
||||
zslUpdateNode(zsl, node, newnode, update);
|
||||
*node_ref = newnode; /* update hashtable pointer */
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,24 +462,15 @@ static long scanLaterList(robj *ob, unsigned long *cursor, monotime endtime) {
|
||||
return bookmark_failed ? 1 : 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
zset *zs;
|
||||
} scanLaterZsetData;
|
||||
|
||||
static void scanLaterZsetCallback(void *privdata, const dictEntry *_de) {
|
||||
dictEntry *de = (dictEntry *)_de;
|
||||
scanLaterZsetData *data = privdata;
|
||||
activeDefragZsetEntry(data->zs, de);
|
||||
static void scanLaterZsetCallback(void *privdata, void *element_ref) {
|
||||
activeDefragZsetNode(privdata, element_ref);
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
static void scanLaterZset(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST) return;
|
||||
zset *zs = (zset *)ob->ptr;
|
||||
dict *d = zs->dict;
|
||||
scanLaterZsetData data = {zs};
|
||||
dictDefragFunctions defragfns = {.defragAlloc = activeDefragAlloc};
|
||||
*cursor = dictScanDefrag(d, *cursor, scanLaterZsetCallback, &defragfns, &data);
|
||||
*cursor = hashtableScanDefrag(zs->ht, *cursor, scanLaterZsetCallback, zs->zsl, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
|
||||
}
|
||||
|
||||
/* Used as hashtable scan callback when all we need is to defrag the hashtable
|
||||
@ -533,27 +514,27 @@ static void defragQuicklist(robj *ob) {
|
||||
}
|
||||
|
||||
static void defragZsetSkiplist(robj *ob) {
|
||||
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
|
||||
zset *zs = (zset *)ob->ptr;
|
||||
|
||||
zset *newzs;
|
||||
zskiplist *newzsl;
|
||||
dict *newdict;
|
||||
dictEntry *de;
|
||||
struct zskiplistNode *newheader;
|
||||
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
|
||||
if ((newzs = activeDefragAlloc(zs))) ob->ptr = zs = newzs;
|
||||
if ((newzsl = activeDefragAlloc(zs->zsl))) zs->zsl = newzsl;
|
||||
if ((newheader = activeDefragAlloc(zs->zsl->header))) zs->zsl->header = newheader;
|
||||
if (dictSize(zs->dict) > server.active_defrag_max_scan_fields)
|
||||
|
||||
hashtable *newtable;
|
||||
if ((newtable = hashtableDefragTables(zs->ht, activeDefragAlloc))) zs->ht = newtable;
|
||||
|
||||
if (hashtableSize(zs->ht) > server.active_defrag_max_scan_fields)
|
||||
defragLater(ob);
|
||||
else {
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
activeDefragZsetEntry(zs, de);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
unsigned long cursor = 0;
|
||||
do {
|
||||
cursor = hashtableScanDefrag(zs->ht, cursor, activeDefragZsetNode, zs->zsl, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
|
||||
} while (cursor != 0);
|
||||
}
|
||||
/* defrag the dict struct and tables */
|
||||
if ((newdict = dictDefragTables(zs->dict))) zs->dict = newdict;
|
||||
}
|
||||
|
||||
static void defragHash(robj *ob) {
|
||||
|
@ -642,9 +642,9 @@ int performEvictions(void) {
|
||||
kvs = db->expires;
|
||||
}
|
||||
int slot = kvstoreGetFairRandomHashtableIndex(kvs);
|
||||
int found = kvstoreHashtableRandomEntry(kvs, slot, (void **)&valkey);
|
||||
if (found) {
|
||||
bestkey = objectGetKey(valkey);
|
||||
void *entry;
|
||||
if (kvstoreHashtableRandomEntry(kvs, slot, &entry)) {
|
||||
bestkey = objectGetKey((robj *)entry);
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
|
@ -774,7 +774,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
if (maxelelen < elelen) maxelelen = elelen;
|
||||
totelelen += elelen;
|
||||
znode = zslInsert(zs->zsl, score, gp->member);
|
||||
serverAssert(dictAdd(zs->dict, gp->member, &znode->score) == DICT_OK);
|
||||
serverAssert(hashtableAdd(zs->ht, znode));
|
||||
gp->member = NULL;
|
||||
}
|
||||
|
||||
|
24
src/module.c
24
src/module.c
@ -11096,12 +11096,10 @@ static void moduleScanKeyDictCallback(void *privdata, const dictEntry *de) {
|
||||
robj *o = data->key->value;
|
||||
robj *field = createStringObject(key, sdslen(key));
|
||||
robj *value = NULL;
|
||||
|
||||
if (o->type == OBJ_HASH) {
|
||||
sds val = dictGetVal(de);
|
||||
value = createStringObject(val, sdslen(val));
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
double *val = (double *)dictGetVal(de);
|
||||
value = createStringObjectFromLongDouble(*val, 0);
|
||||
} else {
|
||||
serverPanic("unexpected object type");
|
||||
}
|
||||
@ -11114,12 +11112,24 @@ static void moduleScanKeyDictCallback(void *privdata, const dictEntry *de) {
|
||||
static void moduleScanKeyHashtableCallback(void *privdata, void *entry) {
|
||||
ScanKeyCBData *data = privdata;
|
||||
robj *o = data->key->value;
|
||||
serverAssert(o->type == OBJ_SET);
|
||||
sds key = entry;
|
||||
robj *value = NULL;
|
||||
sds key = NULL;
|
||||
|
||||
if (o->type == OBJ_SET) {
|
||||
key = entry;
|
||||
/* no value */
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
zskiplistNode *node = (zskiplistNode *)entry;
|
||||
key = node->ele;
|
||||
value = createStringObjectFromLongDouble(node->score, 0);
|
||||
} else {
|
||||
serverPanic("unexpected object type");
|
||||
}
|
||||
robj *field = createStringObject(key, sdslen(key));
|
||||
|
||||
data->fn(data->key, field, NULL, data->user_data);
|
||||
data->fn(data->key, field, value, data->user_data);
|
||||
decrRefCount(field);
|
||||
if (value) decrRefCount(value);
|
||||
}
|
||||
|
||||
/* Scan api that allows a module to scan the elements in a hash, set or sorted set key
|
||||
@ -11183,7 +11193,7 @@ int VM_ScanKey(ValkeyModuleKey *key, ValkeyModuleScanCursor *cursor, ValkeyModul
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
if (o->encoding == OBJ_ENCODING_HT) d = o->ptr;
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST) d = ((zset *)o->ptr)->dict;
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST) ht = ((zset *)o->ptr)->ht;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
|
19
src/object.c
19
src/object.c
@ -461,7 +461,7 @@ robj *createZsetObject(void) {
|
||||
zset *zs = zmalloc(sizeof(*zs));
|
||||
robj *o;
|
||||
|
||||
zs->dict = dictCreate(&zsetDictType);
|
||||
zs->ht = hashtableCreate(&zsetHashtableType);
|
||||
zs->zsl = zslCreate();
|
||||
o = createObject(OBJ_ZSET, zs);
|
||||
o->encoding = OBJ_ENCODING_SKIPLIST;
|
||||
@ -519,7 +519,7 @@ void freeZsetObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_SKIPLIST:
|
||||
zs = o->ptr;
|
||||
dictRelease(zs->dict);
|
||||
hashtableRelease(zs->ht);
|
||||
zslFree(zs->zsl);
|
||||
zfree(zs);
|
||||
break;
|
||||
@ -665,10 +665,7 @@ void dismissZsetObject(robj *o, size_t size_hint) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dismiss hash table memory. */
|
||||
dict *d = zs->dict;
|
||||
dismissMemory(d->ht_table[0], DICTHT_SIZE(d->ht_size_exp[0]) * sizeof(dictEntry *));
|
||||
dismissMemory(d->ht_table[1], DICTHT_SIZE(d->ht_size_exp[1]) * sizeof(dictEntry *));
|
||||
dismissHashtable(zs->ht);
|
||||
} else if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
dismissMemory(o->ptr, lpBytes((unsigned char *)o->ptr));
|
||||
} else {
|
||||
@ -1187,18 +1184,18 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
asize = sizeof(*o) + zmalloc_size(o->ptr);
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
d = ((zset *)o->ptr)->dict;
|
||||
hashtable *ht = ((zset *)o->ptr)->ht;
|
||||
zskiplist *zsl = ((zset *)o->ptr)->zsl;
|
||||
zskiplistNode *znode = zsl->header->level[0].forward;
|
||||
asize = sizeof(*o) + sizeof(zset) + sizeof(zskiplist) + sizeof(dict) +
|
||||
(sizeof(struct dictEntry *) * dictBuckets(d)) + zmalloc_size(zsl->header);
|
||||
asize = sizeof(*o) + sizeof(zset) + sizeof(zskiplist) +
|
||||
hashtableMemUsage(ht) + zmalloc_size(zsl->header);
|
||||
while (znode != NULL && samples < sample_size) {
|
||||
elesize += sdsAllocSize(znode->ele);
|
||||
elesize += dictEntryMemUsage(NULL) + zmalloc_size(znode);
|
||||
elesize += zmalloc_size(znode);
|
||||
samples++;
|
||||
znode = znode->level[0].forward;
|
||||
}
|
||||
if (samples) asize += (double)elesize / samples * dictSize(d);
|
||||
if (samples) asize += (double)elesize / samples * hashtableSize(ht);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
@ -2005,7 +2005,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||
o = createZsetObject();
|
||||
zs = o->ptr;
|
||||
|
||||
if (zsetlen > DICT_HT_INITIAL_SIZE && dictTryExpand(zs->dict, zsetlen) != DICT_OK) {
|
||||
if (!hashtableTryExpand(zs->ht, zsetlen)) {
|
||||
rdbReportCorruptRDB("OOM in dictTryExpand %llu", (unsigned long long)zsetlen);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
@ -2048,7 +2048,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||
totelelen += sdslen(sdsele);
|
||||
|
||||
znode = zslInsert(zs->zsl, score, sdsele);
|
||||
if (dictAdd(zs->dict, sdsele, &znode->score) != DICT_OK) {
|
||||
if (!hashtableAdd(zs->ht, znode)) {
|
||||
rdbReportCorruptRDB("Duplicate zset fields detected");
|
||||
decrRefCount(o);
|
||||
/* no need to free 'sdsele', will be released by zslFree together with 'o' */
|
||||
|
16
src/server.c
16
src/server.c
@ -556,14 +556,16 @@ hashtableType setHashtableType = {
|
||||
.keyCompare = hashtableSdsKeyCompare,
|
||||
.entryDestructor = dictSdsDestructor};
|
||||
|
||||
const void *zsetHashtableGetKey(const void *element) {
|
||||
const zskiplistNode *node = element;
|
||||
return node->ele;
|
||||
}
|
||||
|
||||
/* Sorted sets hash (note: a skiplist is used in addition to the hash table) */
|
||||
dictType zsetDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
NULL, /* Note: SDS string shared & freed by skiplist */
|
||||
NULL, /* val destructor */
|
||||
NULL, /* allow to expand */
|
||||
hashtableType zsetHashtableType = {
|
||||
.hashFunction = dictSdsHash,
|
||||
.entryGetKey = zsetHashtableGetKey,
|
||||
.keyCompare = hashtableSdsKeyCompare,
|
||||
};
|
||||
|
||||
uint64_t hashtableSdsHash(const void *key) {
|
||||
|
11
src/server.h
11
src/server.h
@ -1323,7 +1323,7 @@ typedef struct zskiplist {
|
||||
} zskiplist;
|
||||
|
||||
typedef struct zset {
|
||||
dict *dict;
|
||||
hashtable *ht;
|
||||
zskiplist *zsl;
|
||||
} zset;
|
||||
|
||||
@ -2538,7 +2538,7 @@ extern dictType objectKeyPointerValueDictType;
|
||||
extern dictType objectKeyHeapPointerValueDictType;
|
||||
extern hashtableType setHashtableType;
|
||||
extern dictType BenchmarkDictType;
|
||||
extern dictType zsetDictType;
|
||||
extern hashtableType zsetHashtableType;
|
||||
extern hashtableType kvstoreKeysHashtableType;
|
||||
extern hashtableType kvstoreExpiresHashtableType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
@ -3084,8 +3084,6 @@ typedef struct {
|
||||
zskiplist *zslCreate(void);
|
||||
void zslFree(zskiplist *zsl);
|
||||
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele);
|
||||
unsigned char *zzlInsert(unsigned char *zl, sds ele, double score);
|
||||
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node);
|
||||
zskiplistNode *zslNthInRange(zskiplist *zsl, zrangespec *range, long n);
|
||||
double zzlGetScore(unsigned char *sptr);
|
||||
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
@ -3096,9 +3094,7 @@ unsigned long zsetLength(const robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
void zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen);
|
||||
int zsetScore(robj *zobj, sds member, double *score);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
|
||||
int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
|
||||
long zsetRank(robj *zobj, sds ele, int reverse, double *score);
|
||||
int zsetDel(robj *zobj, sds ele);
|
||||
robj *zsetDup(robj *o);
|
||||
void genericZpopCommand(client *c,
|
||||
@ -3565,10 +3561,11 @@ unsigned long LFUDecrAndReturn(robj *o);
|
||||
int performEvictions(void);
|
||||
void startEvictionTimeProc(void);
|
||||
|
||||
/* Keys hashing / comparison functions for dict.c hash tables. */
|
||||
/* Keys hashing/comparison functions for dict.c and hashtable.c hash tables. */
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
uint64_t dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCompare(const void *key1, const void *key2);
|
||||
int hashtableSdsKeyCompare(const void *key1, const void *key2);
|
||||
int dictSdsKeyCaseCompare(const void *key1, const void *key2);
|
||||
void dictSdsDestructor(void *val);
|
||||
void dictListDestructor(void *val);
|
||||
|
21
src/sort.c
21
src/sort.c
@ -330,7 +330,7 @@ void sortCommandGeneric(client *c, int readonly) {
|
||||
switch (sortval->type) {
|
||||
case OBJ_LIST: vectorlen = listTypeLength(sortval); break;
|
||||
case OBJ_SET: vectorlen = setTypeSize(sortval); break;
|
||||
case OBJ_ZSET: vectorlen = dictSize(((zset *)sortval->ptr)->dict); break;
|
||||
case OBJ_ZSET: vectorlen = hashtableSize(((zset *)sortval->ptr)->ht); break;
|
||||
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
|
||||
}
|
||||
|
||||
@ -423,7 +423,7 @@ void sortCommandGeneric(client *c, int readonly) {
|
||||
|
||||
/* Check if starting point is trivial, before doing log(N) lookup. */
|
||||
if (desc) {
|
||||
long zsetlen = dictSize(((zset *)sortval->ptr)->dict);
|
||||
long zsetlen = hashtableSize(((zset *)sortval->ptr)->ht);
|
||||
|
||||
ln = zsl->tail;
|
||||
if (start > 0) ln = zslGetElementByRank(zsl, zsetlen - start);
|
||||
@ -445,19 +445,18 @@ void sortCommandGeneric(client *c, int readonly) {
|
||||
end -= start;
|
||||
start = 0;
|
||||
} else if (sortval->type == OBJ_ZSET) {
|
||||
dict *set = ((zset *)sortval->ptr)->dict;
|
||||
dictIterator *di;
|
||||
dictEntry *setele;
|
||||
sds sdsele;
|
||||
di = dictGetIterator(set);
|
||||
while ((setele = dictNext(di)) != NULL) {
|
||||
sdsele = dictGetKey(setele);
|
||||
vector[j].obj = createStringObject(sdsele, sdslen(sdsele));
|
||||
hashtable *ht = ((zset *)sortval->ptr)->ht;
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, ht);
|
||||
void *next;
|
||||
while (hashtableNext(&iter, &next)) {
|
||||
zskiplistNode *node = next;
|
||||
vector[j].obj = createStringObject(node->ele, sdslen(node->ele));
|
||||
vector[j].u.score = 0;
|
||||
vector[j].u.cmpobj = NULL;
|
||||
j++;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
hashtableResetIterator(&iter);
|
||||
} else {
|
||||
serverPanic("Unknown type");
|
||||
}
|
||||
|
593
src/t_zset.c
593
src/t_zset.c
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user