This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 5ebde751f fix(hyperloglog): use namespace-prefixed key in PFMERGE 
GetMetadata (#3441)
5ebde751f is described below

commit 5ebde751f0e15058e9823978a02b062b3a20c115
Author: Songqing Zhang <[email protected]>
AuthorDate: Thu Apr 16 22:26:12 2026 +0800

    fix(hyperloglog): use namespace-prefixed key in PFMERGE GetMetadata (#3441)
    
    Merge called GetMetadata with the raw user key instead of the
    namespace-prefixed key, causing the destination's existing metadata to
    never be found and potentially orphaning old sub-keys.
    
    ---------
    
    Co-authored-by: 纪华裕 <[email protected]>
---
 src/types/redis_hyperloglog.cc                    |  2 +-
 tests/gocase/unit/hyperloglog/hyperloglog_test.go | 53 +++++++++++++++++++++++
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/src/types/redis_hyperloglog.cc b/src/types/redis_hyperloglog.cc
index 63ee0b2b9..952177265 100644
--- a/src/types/redis_hyperloglog.cc
+++ b/src/types/redis_hyperloglog.cc
@@ -240,7 +240,7 @@ rocksdb::Status HyperLogLog::Merge(engine::Context &ctx, 
const Slice &dest_user_
   std::vector<std::string> registers;
   HyperLogLogMetadata metadata;
 
-  rocksdb::Status s = GetMetadata(ctx, dest_user_key, &metadata);
+  rocksdb::Status s = GetMetadata(ctx, dest_key, &metadata);
   if (!s.ok() && !s.IsNotFound()) return s;
   {
     std::vector<Slice> all_user_keys;
diff --git a/tests/gocase/unit/hyperloglog/hyperloglog_test.go 
b/tests/gocase/unit/hyperloglog/hyperloglog_test.go
index 62fdff40d..28fc949cf 100644
--- a/tests/gocase/unit/hyperloglog/hyperloglog_test.go
+++ b/tests/gocase/unit/hyperloglog/hyperloglog_test.go
@@ -23,6 +23,7 @@ import (
        "context"
        "fmt"
        "testing"
+       "time"
 
        "github.com/apache/kvrocks/tests/gocase/util"
        "github.com/stretchr/testify/require"
@@ -213,4 +214,56 @@ func TestHyperLogLog(t *testing.T) {
                require.NoError(t, err)
                require.EqualValues(t, 10, card)
        })
+
+       t.Run("PFMERGE into existing dest preserves dest data", func(t 
*testing.T) {
+               require.NoError(t, rdb.Do(ctx, "DEL", "dest", "src1", 
"src2").Err())
+
+               _, err := rdb.PFAdd(ctx, "dest", "a", "b", "c").Result()
+               require.NoError(t, err)
+               _, err = rdb.PFAdd(ctx, "src1", "d", "e").Result()
+               require.NoError(t, err)
+               _, err = rdb.PFAdd(ctx, "src2", "f").Result()
+               require.NoError(t, err)
+
+               // Merge src1 and src2 into existing dest
+               _, err = rdb.PFMerge(ctx, "dest", "src1", "src2").Result()
+               require.NoError(t, err)
+
+               // dest should have all 6 elements
+               card, err := rdb.PFCount(ctx, "dest").Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 6, card)
+
+               // Merge again into dest to verify idempotency
+               _, err = rdb.PFMerge(ctx, "dest", "src1").Result()
+               require.NoError(t, err)
+               card, err = rdb.PFCount(ctx, "dest").Result()
+               require.NoError(t, err)
+               require.EqualValues(t, 6, card)
+       })
+
+       // PFMERGE must load dest metadata using the namespace-prefixed key 
(same as storage).
+       // If GetMetadata used the raw user key, it would miss the record, keep 
default Metadata (expire=0),
+       // and the following PFMERGE would rewrite metadata without preserving 
TTL (TTL becomes "no expiry").
+       t.Run("PFMERGE into existing dest preserves TTL", func(t *testing.T) {
+               require.NoError(t, rdb.Do(ctx, "DEL", "dest_ttl", 
"src_ttl").Err())
+
+               _, err := rdb.PFAdd(ctx, "dest_ttl", "a", "b").Result()
+               require.NoError(t, err)
+               ok, err := rdb.Expire(ctx, "dest_ttl", 
3600*time.Second).Result()
+               require.NoError(t, err)
+               require.True(t, ok)
+
+               ttlBefore := rdb.TTL(ctx, "dest_ttl").Val()
+               require.Greater(t, ttlBefore, time.Duration(0))
+
+               _, err = rdb.PFAdd(ctx, "src_ttl", "c").Result()
+               require.NoError(t, err)
+               _, err = rdb.PFMerge(ctx, "dest_ttl", "src_ttl").Result()
+               require.NoError(t, err)
+
+               ttlAfter := rdb.TTL(ctx, "dest_ttl").Val()
+               require.Greater(t, ttlAfter, time.Duration(0),
+                       "PFMERGE must keep dest TTL when dest already exists 
(metadata must be read with ns-prefixed key)")
+       })
 }

Reply via email to