Skip to content

Commit b78e205

Browse files
sarthakaggarwal97zuiderkwast
authored andcommitted
Configurable DB hash seed for SCAN family commands consistency (valkey-io#2608)
Introduce a new config `hash-seed` which can be set only at startup and controls the hash seed for the server. This includes all hash tables. This change makes it so that both primaries and replicas will return the same results for SCAN/HSCAN/ZSCAN/SSCAN cursors. This is useful in order to make sure SCAN behaves correctly after a failover. Resolves valkey-io#4 --------- Signed-off-by: Sarthak Aggarwal <[email protected]> Signed-off-by: Sarthak Aggarwal <[email protected]> Co-authored-by: Viktor Söderqvist <[email protected]>
1 parent f6e9441 commit b78e205

File tree

8 files changed

+109
-1
lines changed

8 files changed

+109
-1
lines changed

src/config.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3187,6 +3187,15 @@ static int applyClientMaxMemoryUsage(const char **err) {
31873187
return 1;
31883188
}
31893189

3190+
#define HASH_SEED_MAX_LEN 256
3191+
static int isValidDbHashSeed(sds val, const char **err) {
3192+
if (sdslen(val) > HASH_SEED_MAX_LEN) {
3193+
*err = "hash-seed must be less than or equal to " STRINGIFY(HASH_SEED_MAX_LEN) " characters";
3194+
return 0;
3195+
}
3196+
return 1;
3197+
}
3198+
31903199
standardConfig static_configs[] = {
31913200
/* Bool configs */
31923201
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
@@ -3277,6 +3286,7 @@ standardConfig static_configs[] = {
32773286
createSDSConfig("primaryauth", "masterauth", MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.primary_auth, NULL, NULL, NULL),
32783287
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.requirepass, NULL, NULL, updateRequirePass),
32793288
createSDSConfig("availability-zone", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.availability_zone, "", NULL, NULL),
3289+
createSDSConfig("hash-seed", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.hash_seed, NULL, isValidDbHashSeed, NULL),
32803290

32813291
/* Enum Configs */
32823292
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),

src/server.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7373,8 +7373,13 @@ __attribute__((weak)) int main(int argc, char **argv) {
73737373
sdsfree(options);
73747374
}
73757375
if (server.sentinel_mode) sentinelCheckConfigFile();
7376+
if (server.hash_seed != NULL) {
7377+
memset(hashseed, 0, sizeof(hashseed));
7378+
getHashSeedFromString(hashseed, sizeof(hashseed), server.hash_seed);
7379+
hashtableSetHashFunctionSeed(hashseed);
7380+
}
73767381

7377-
/* Do system checks */
7382+
/* Do system checks */
73787383
#ifdef __linux__
73797384
linuxMemoryWarnings();
73807385
sds err_msg = NULL;

src/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,7 @@ struct valkeyServer {
22142214
* dropping packets of a specific type */
22152215
unsigned long cluster_blacklist_ttl; /* Duration in seconds that a node is denied re-entry into
22162216
* the cluster after it is forgotten with CLUSTER FORGET. */
2217+
sds hash_seed; /* Configurable DB hash seed */
22172218
int cluster_slot_stats_enabled; /* Cluster slot usage statistics tracking enabled. */
22182219
mstime_t cluster_mf_timeout; /* Milliseconds to do a manual failover. */
22192220
unsigned long cluster_slot_migration_log_max_len; /* Maximum count of migrations to display in the

src/util.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,21 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
10371037
return 0;
10381038
}
10391039

1040+
/* Populate the provided seed array by hashing the provided string with SHA256
1041+
* and copying the first outlen bytes of the digest into the seed buffer. */
1042+
void getHashSeedFromString(unsigned char *seed_array, size_t outlen, const char *value) {
1043+
SHA256_CTX ctx;
1044+
unsigned char digest[SHA256_BLOCK_SIZE];
1045+
1046+
sha256_init(&ctx);
1047+
sha256_update(&ctx, (const BYTE *)value, strlen(value));
1048+
sha256_final(&ctx, digest);
1049+
1050+
if (outlen > SHA256_BLOCK_SIZE) outlen = SHA256_BLOCK_SIZE;
1051+
memcpy(seed_array, digest, outlen);
1052+
}
1053+
1054+
10401055
/* Parses a version string on the form "major.minor.patch" and returns an
10411056
* integer on the form 0xMMmmpp. Returns -1 on parse error. */
10421057
int version2num(const char *version) {

src/util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ int trimDoubleString(char *buf, size_t len);
9191
int d2string(char *buf, size_t len, double value);
9292
int fixedpoint_d2string(char *dst, size_t dstlen, double dvalue, int fractional_digits);
9393
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
94+
void getHashSeedFromString(unsigned char *seed_array, size_t len, const char *value);
9495
int double2ll(double d, long long *out);
9596
int version2num(const char *version);
9697
int yesnotoi(char *s);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
test {scan family consistency with configured hash seed} {
2+
start_server {tags {"external:skip"}} {
3+
4+
set fixed_seed "aabbccddeeffgghh"
5+
set shared_overrides [list appendonly no save "" hash-seed $fixed_seed activedefrag no hz 1]
6+
7+
start_server [list overrides $shared_overrides] {
8+
set primary_host [srv 0 host]
9+
set primary_port [srv 0 port]
10+
11+
start_server [list overrides $shared_overrides] {
12+
set primary [srv -1 client]
13+
set replica [srv 0 client]
14+
15+
$primary flushall
16+
$replica replicaof $primary_host $primary_port
17+
wait_for_sync $replica
18+
19+
set n 50
20+
for {set i 0} {$i < $n} {incr i} {
21+
$primary set "k:$i" x
22+
$primary hset h "f:$i" $i
23+
$primary sadd s "m:$i"
24+
$primary zadd z $i "m:$i"
25+
}
26+
27+
wait_for_condition 200 50 {
28+
[$replica dbsize] == [$primary dbsize]
29+
} else {
30+
fail "replica did not catch up dbsize (primary=[$primary dbsize], replica=[$replica dbsize])"
31+
}
32+
set cursor {{0} {}}
33+
while {1} {
34+
set primary_cursor_next [$primary scan [lindex $cursor 0]]
35+
set replica_cursor_next [$replica scan [lindex $cursor 0]]
36+
assert_equal $primary_cursor_next $replica_cursor_next
37+
if {[lindex $primary_cursor_next 0] eq "0"} {
38+
assert_equal "0" [lindex $replica_cursor_next 0]
39+
break
40+
}
41+
set cursor $primary_cursor_next
42+
}
43+
44+
foreach {cmd key} {hscan h sscan s zscan z} {
45+
set cursor {{0} {}}
46+
while {1} {
47+
set primary_cursor_next [$primary $cmd $key [lindex $cursor 0]]
48+
set replica_cursor_next [$replica $cmd $key [lindex $cursor 0]]
49+
assert_equal $primary_cursor_next $replica_cursor_next
50+
if {[lindex $primary_cursor_next 0] eq "0"} {
51+
assert_equal "0" [lindex $replica_cursor_next 0]
52+
break
53+
}
54+
set cursor $primary_cursor_next
55+
}
56+
}
57+
}
58+
}
59+
}
60+
}

tests/unit/introspection.tcl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@ start_server {tags {"introspection"}} {
12251225
disable-thp
12261226
aclfile
12271227
unixsocket
1228+
hash-seed
12281229
pidfile
12291230
syslog-ident
12301231
appendfilename
@@ -1956,3 +1957,11 @@ test {CONFIG REWRITE handles alias config properly} {
19561957
assert_equal [r config get hash-max-listpack-entries] {hash-max-listpack-entries 100}
19571958
}
19581959
} {} {external:skip}
1960+
1961+
test {CONFIG hash-seed is immutable and settable at startup} {
1962+
start_server {tags {"introspection"} overrides {hash-seed aabbccddeeffgghh}} {
1963+
assert_error "ERR CONFIG SET failed (possibly related to argument 'hash-seed') - can't set immutable config*" {
1964+
r config set hash-seed newseed
1965+
}
1966+
}
1967+
} {} {external:skip}

valkey.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,13 @@ locale-collate ""
527527
#
528528
# availability-zone "zone-name"
529529

530+
# Use a fixed hash seed for hashtable instead of a random one.
531+
# Setting this option makes commands like SCAN return keys in a consistent
532+
# order across restarts and failovers. The seed can be any string up to 256 characters.
533+
# The value is immutable and must be provided only at server startup.
534+
#
535+
# hash-seed example-seed-val
536+
530537
################################ SNAPSHOTTING ################################
531538

532539
# Save the DB to disk.

0 commit comments

Comments
 (0)