Skip to content

Commit

Permalink
Fix zset add the same member with different scores (#298)
Browse files Browse the repository at this point in the history
Fix the corner case that adds the same member which may add the score
column family many times and cause problems in the ZRANGE command.

For example, we add members with `ZADD mykey 1 a 2 a` and `ZRANGE mykey 0 1`
return only one member(`a`) was expected but got the member `a` twice now.

The root cause of this issue was the score key was composed by member and score,
so the last one can't overwrite the previous one when the score was different.
A simple workaround was to add those members with reversed orders and skip the member if has added.
  • Loading branch information
git-hulk authored and ShooterIT committed Jul 19, 2021
1 parent d6bca85 commit 4e4b645
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 1 deletion.
19 changes: 18 additions & 1 deletion src/redis_zset.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <limits>
#include <cmath>
#include <memory>
#include <set>

namespace Redis {

Expand All @@ -28,8 +29,24 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t flags, std::vector<Memb
WriteBatchLogData log_data(kRedisZSet);
batch.PutLogData(log_data.Encode());
std::string member_key;
for (size_t i = 0; i < mscores->size(); i++) {
std::set<std::string> added_member_keys;
for (int i = static_cast<int>(mscores->size()-1); i >= 0; i--) {
InternalKey(ns_key, (*mscores)[i].member, metadata.version).Encode(&member_key);

// Fix the corner case that adds the same member which may add the score
// column family many times and cause problems in the ZRANGE command.
//
// For example, we add members with `ZADD mykey 1 a 2 a` and `ZRANGE mykey 0 1`
// return only one member(`a`) was expected but got the member `a` twice now.
//
// The root cause of this issue was the score key was composed by member and score,
// so the last one can't overwrite the previous when the score was different.
// A simple workaround was add those members with reversed order and skip the member if has added.
if (added_member_keys.find(member_key) != added_member_keys.end()) {
continue;
}
added_member_keys.insert(member_key);

if (metadata.size > 0) {
std::string old_score_bytes;
s = db_->Get(rocksdb::ReadOptions(), member_key, &old_score_bytes);
Expand Down
11 changes: 11 additions & 0 deletions tests/tcl/tests/unit/type/zset.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ start_server {tags {"zset"}} {
assert_equal {y x z} [r zrange ztmp 0 -1]
}

test "ZSET basic ZADD the same member with different scores - $encoding" {
r del ztmp
assert_equal 1 [r zadd ztmp 10 x 20 x]
assert_equal {x} [r zrange ztmp 0 -1]
assert_equal 20 [r zscore ztmp x]

assert_equal 2 [r zadd ztmp 30 x 40 y 50 z]
assert_equal {x y z} [r zrange ztmp 0 -1]
assert_equal 30 [r zscore ztmp x]
}

test "ZSET element can't be set to NaN with ZADD - $encoding" {
assert_error "*float*" {r zadd myzset nan abc}
}
Expand Down

0 comments on commit 4e4b645

Please sign in to comment.