zslUpdateScore equal-score fast path

This commit is contained in:
Vitah Lin 2026-05-22 23:39:29 +08:00
parent ba1a4b2c8f
commit 783d4e28d3
2 changed files with 44 additions and 6 deletions

View file

@ -389,17 +389,31 @@ static void zslDelete(zskiplist *zsl, zskiplistNode *node) {
zslFreeNode(zsl, node);
}
/* Returns true if node would still be strictly between its level-0 neighbors
* after changing its score. The sorted-set order is (score, element), so equal
* scores still need the lexicographic tie-breaker. */
static int zslNodeCanKeepPosition(zskiplistNode *node, double newscore) {
sds ele = zslGetNodeElement(node);
zskiplistNode *prev = node->backward;
zskiplistNode *next = node->level[0].forward;
if (prev != NULL && zslCompareWithNode(newscore, ele, prev) <= 0)
return 0;
if (next != NULL && zslCompareWithNode(newscore, ele, next) >= 0)
return 0;
return 1;
}
/* Update the score of an element inside the sorted set skiplist.
* If the new score would keep the node in its current position, updates in-place and returns NULL.
* Otherwise, unlinks the node, updates score, reinserts at correct position, and returns node.
* Anyway, the node pointer stays the same (no dict update needed). */
* If the new score would keep the node in its current position, update it
* in-place. Otherwise, unlink the node, update the score, and reinsert it at
* the correct position. The node pointer stays the same (no dict update
* needed). */
static void zslUpdateScore(zskiplist *zsl, zskiplistNode *node, double newscore) {
/* Fast path: if the node, after the score update, would be still exactly
* at the same position, we can just update the score without
* actually removing and re-inserting the element in the skiplist. */
if ((node->backward == NULL || node->backward->score < newscore) &&
(node->level[0].forward == NULL || node->level[0].forward->score > newscore))
{
if (zslNodeCanKeepPosition(node, newscore)) {
node->score = newscore;
return;
}

View file

@ -127,6 +127,30 @@ start_server {tags {"zset"}} {
assert_equal {y x z} [r zrange ztmp 0 -1]
}
test "ZSET score update with equal-score neighbor - $encoding" {
r del ztmp
r zadd ztmp 1 a 2 b 3 c
r zadd ztmp 3 b
assert_equal {a b c} [r zrange ztmp 0 -1]
assert_equal {a 1 b 3 c 3} [r zrange ztmp 0 -1 withscores]
r zadd ztmp 1 b
assert_equal {a b c} [r zrange ztmp 0 -1]
assert_equal {a 1 b 1 c 3} [r zrange ztmp 0 -1 withscores]
r del ztmp
r zadd ztmp 1 a 2 c 3 b
r zadd ztmp 3 c
assert_equal {a b c} [r zrange ztmp 0 -1]
assert_equal {a 1 b 3 c 3} [r zrange ztmp 0 -1 withscores]
r del ztmp
r zadd ztmp 1 b 2 a 3 c
r zadd ztmp 1 a
assert_equal {a b c} [r zrange ztmp 0 -1]
assert_equal {a 1 b 1 c 3} [r zrange ztmp 0 -1 withscores]
}
test "ZSET element can't be set to NaN with ZADD - $encoding" {
assert_error "*not*float*" {r zadd myzset nan abc}
}