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:
Rain Valentine 2025-01-08 09:34:02 -08:00 committed by GitHub
parent 8af35a1712
commit ab627d6721
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 419 additions and 483 deletions

View File

@ -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");
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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");
}

View File

@ -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' */

View File

@ -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) {

View File

@ -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);

View File

@ -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");
}

File diff suppressed because it is too large Load Diff