From 4e4b6450db47d9fcabc841f903c42c458ad78ee2 Mon Sep 17 00:00:00 2001 From: hulk Date: Mon, 7 Jun 2021 19:45:24 +0800 Subject: [PATCH] Fix zset add the same member with different scores (#298) 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. --- src/redis_zset.cc | 19 ++++++++++++++++++- tests/tcl/tests/unit/type/zset.tcl | 11 +++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/redis_zset.cc b/src/redis_zset.cc index dbf5bdbf236..4c43968362c 100644 --- a/src/redis_zset.cc +++ b/src/redis_zset.cc @@ -5,6 +5,7 @@ #include #include #include +#include namespace Redis { @@ -28,8 +29,24 @@ rocksdb::Status ZSet::Add(const Slice &user_key, uint8_t flags, std::vectorsize(); i++) { + std::set added_member_keys; + for (int i = static_cast(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); diff --git a/tests/tcl/tests/unit/type/zset.tcl b/tests/tcl/tests/unit/type/zset.tcl index 2b037f23642..61ef8e895e7 100644 --- a/tests/tcl/tests/unit/type/zset.tcl +++ b/tests/tcl/tests/unit/type/zset.tcl @@ -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} }