Skip to content

Commit 6df7fd3

Browse files
murphyjacob4cherukum-Amazon
authored andcommitted
Fix incorrect kvstore size and BIT accounting after completed migration (valkey-io#2749)
When working on valkey-io#2635 I errorneously duplicated the setSlotImportingStateInAllDbs call for successful imports. This resulted in us doubling the key count in the kvstore. This results in DBSIZE reporting an incorrect sum, and also causes BIT corruption that can eventually result in a crash. The solution is: 1. Only call setSlotImportingStateInAllDbs once (in our finishSlotMigrationJob function) 2. Make setSlotImportingStateInAllDbs idempotent by checking if the delete from the kvstore importing hashtable is a no-op This also fixes a bug where the number of importing keys is not lowered after the migration, but this is less critical since it is only used when resizing the dictionary on RDB load. However, it could result in un-loadable RDBs if the importing key count gets large enough. Signed-off-by: Jacob Murphy <[email protected]> (cherry picked from commit 2a914aa)
1 parent 48e35d0 commit 6df7fd3

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

src/cluster_migrateslots.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -866,9 +866,6 @@ void performSlotImportJobFailover(slotMigrationJob *job) {
866866
/* 4) Pong all the other nodes so that they can update the state accordingly
867867
* and detect that we have taken over the slots. */
868868
clusterDoBeforeSleep(CLUSTER_TODO_BROADCAST_ALL);
869-
870-
/* 5) Mark all slots as stable in the kvstore (for SCAN/KEYS/RANDOMKEY) */
871-
setSlotImportingStateInAllDbs(job->slot_ranges, 0);
872869
}
873870

874871
bool clusterIsAnySlotImporting(void) {

src/kvstore.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,10 +949,10 @@ void kvstoreSetIsImporting(kvstore *kvs, int didx, int is_importing) {
949949
return;
950950
}
951951

952-
hashtableDelete(kvs->importing, (void *)(intptr_t)didx);
953952
/* Once we mark a hashtable as not importing, we need to begin tracking in
954953
* the kvstore metadata */
955-
if (ht && hashtableSize(ht) != 0) {
954+
if (hashtableDelete(kvs->importing, (void *)(intptr_t)didx) && ht && hashtableSize(ht) != 0) {
956955
cumulativeKeyCountAdd(kvs, didx, hashtableSize(ht));
956+
kvs->importing_key_count -= hashtableSize(ht);
957957
}
958958
}

tests/unit/cluster/cluster-migrateslots.tcl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,54 @@ start_cluster 3 3 {tags {logreqres:skip external:skip cluster} overrides {cluste
512512
}
513513
}
514514

515+
test "Slot migration DBSIZE after migration" {
516+
assert_does_not_resync {
517+
# Populate data before migration
518+
populate 1000 "$16381_slot_tag:" 1000 -2
519+
populate 1000 "$16382_slot_tag:" 1000 -2
520+
populate 1000 "$16383_slot_tag:" 1000 -2
521+
wait_for_countkeysinslot 5 16381 1000
522+
wait_for_countkeysinslot 5 16382 1000
523+
wait_for_countkeysinslot 5 16383 1000
524+
assert_equal "3000" [R 2 DBSIZE]
525+
assert_equal "0" [R 0 DBSIZE]
526+
assert_equal "3000" [R 5 DBSIZE]
527+
assert_equal "0" [R 3 DBSIZE]
528+
529+
foreach slot_to_migrate {16381 16382 16383} {
530+
# Perform one-shot migration
531+
assert_match "OK" [R 2 CLUSTER MIGRATESLOTS SLOTSRANGE $slot_to_migrate $slot_to_migrate NODE $node0_id]
532+
wait_for_migration 0 $slot_to_migrate
533+
534+
# Reflected in DBSIZE
535+
assert_match "1000" [R 0 CLUSTER COUNTKEYSINSLOT $slot_to_migrate]
536+
assert_match "0" [R 2 CLUSTER COUNTKEYSINSLOT $slot_to_migrate]
537+
assert_equal "2000" [R 2 DBSIZE]
538+
assert_equal "1000" [R 0 DBSIZE]
539+
wait_for_countkeysinslot 3 $slot_to_migrate 1000
540+
wait_for_countkeysinslot 5 $slot_to_migrate 0
541+
assert_equal "2000" [R 5 DBSIZE]
542+
assert_equal "1000" [R 3 DBSIZE]
543+
544+
# Perform migration back
545+
assert_match "OK" [R 0 CLUSTER MIGRATESLOTS SLOTSRANGE $slot_to_migrate $slot_to_migrate NODE $node2_id]
546+
set jobname [get_job_name 0 $slot_to_migrate]
547+
wait_for_migration 2 $slot_to_migrate
548+
549+
# Reflected in DBSIZE
550+
assert_equal "3000" [R 2 DBSIZE]
551+
assert_equal "0" [R 0 DBSIZE]
552+
wait_for_countkeysinslot 5 $slot_to_migrate 1000
553+
wait_for_countkeysinslot 3 $slot_to_migrate 0
554+
assert_equal "3000" [R 5 DBSIZE]
555+
assert_equal "0" [R 3 DBSIZE]
556+
}
557+
558+
# Cleanup for next test
559+
assert_match "OK" [R 2 FLUSHDB SYNC]
560+
}
561+
}
562+
515563
test "Single source import - two phase" {
516564
assert_does_not_resync {
517565
set_debug_prevent_pause 1
@@ -2006,27 +2054,53 @@ start_cluster 3 3 {tags {logreqres:skip external:skip cluster} overrides {cluste
20062054
set owning_repl 3
20072055
}
20082056

2057+
# Restart the node again to verify resync is capable. This would
2058+
# catch if the result of the above operation caused some kind of
2059+
# kvstore/RDB corruption.
2060+
if {$do_save} {
2061+
assert_match "OK" [R $idx_to_restart SAVE]
2062+
}
2063+
do_node_restart $idx_to_restart
2064+
2065+
# Wait for resync
2066+
wait_for_condition 50 1000 {
2067+
[status [srv -3 client] master_link_status] == "up"
2068+
} else {
2069+
fail "Node 3 is not synced"
2070+
}
2071+
wait_for_condition 50 1000 {
2072+
[status [srv -5 client] master_link_status] == "up"
2073+
} else {
2074+
fail "Node 5 is not synced"
2075+
}
2076+
20092077
if {$owning_prim == $idx_to_restart && ! $do_save} {
20102078
assert_match "0" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16379]
20112079
assert_match "0" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16381]
20122080
assert_match "0" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16383]
20132081
wait_for_countkeysinslot $owning_repl 16379 0
20142082
wait_for_countkeysinslot $owning_repl 16381 0
20152083
wait_for_countkeysinslot $owning_repl 16383 0
2084+
assert_equal "0" [R $owning_prim DBSIZE]
2085+
assert_equal "0" [R $owning_repl DBSIZE]
20162086
} else {
20172087
assert_match "333" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16379]
20182088
assert_match "333" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16381]
20192089
assert_match "334" [R $owning_prim CLUSTER COUNTKEYSINSLOT 16383]
20202090
wait_for_countkeysinslot $owning_repl 16379 333
20212091
wait_for_countkeysinslot $owning_repl 16381 333
20222092
wait_for_countkeysinslot $owning_repl 16383 334
2093+
assert_equal "1000" [R $owning_prim DBSIZE]
2094+
assert_equal "1000" [R $owning_repl DBSIZE]
20232095
}
20242096
assert_match "0" [R $not_owning_prim CLUSTER COUNTKEYSINSLOT 16379]
20252097
assert_match "0" [R $not_owning_prim CLUSTER COUNTKEYSINSLOT 16381]
20262098
assert_match "0" [R $not_owning_prim CLUSTER COUNTKEYSINSLOT 16383]
20272099
wait_for_countkeysinslot $not_owning_repl 16379 0
20282100
wait_for_countkeysinslot $not_owning_repl 16381 0
20292101
wait_for_countkeysinslot $not_owning_repl 16383 0
2102+
assert_equal "0" [R $not_owning_repl DBSIZE]
2103+
assert_equal "0" [R $not_owning_repl DBSIZE]
20302104

20312105
# Cleanup for the next test
20322106
assert_match "OK" [R $owning_prim FLUSHDB SYNC]

0 commit comments

Comments
 (0)