diff --git a/server/mvcc/hash_test.go b/server/mvcc/hash_test.go new file mode 100644 index 000000000..f0c6aecb9 --- /dev/null +++ b/server/mvcc/hash_test.go @@ -0,0 +1,166 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mvcc + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.etcd.io/etcd/pkg/v3/traceutil" + "go.etcd.io/etcd/server/v3/lease" + betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing" + "go.uber.org/zap/zaptest" +) + +const ( + // Use high prime + compactionCycle = 71 +) + +// Test HashByRevValue values to ensure we don't change the output which would +// have catastrophic consequences. Expected output is just hardcoded, so please +// regenerate it every time you change input parameters. +func TestHashByRevValue(t *testing.T) { + b, _ := betesting.NewDefaultTmpBackend(t) + s := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) + + var totalRevisions int64 = 1210 + assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions) + assert.Less(t, int64(compactionCycle*10), totalRevisions) + var rev int64 + var got []kvHash + for ; rev < totalRevisions; rev += compactionCycle { + putKVs(s, rev, compactionCycle) + hash := testHashByRev(t, s, rev+compactionCycle/2) + got = append(got, hash) + } + putKVs(s, rev, totalRevisions) + hash := testHashByRev(t, s, rev+totalRevisions/2) + got = append(got, hash) + assert.Equal(t, []kvHash{ + {4082599214, -1, 35}, + {2279933401, 35, 106}, + {3284231217, 106, 177}, + {126286495, 177, 248}, + {900108730, 248, 319}, + {2475485232, 319, 390}, + {1226296507, 390, 461}, + {2503661030, 461, 532}, + {4155130747, 532, 603}, + {106915399, 603, 674}, + {406914006, 674, 745}, + {1882211381, 745, 816}, + {806177088, 816, 887}, + {664311366, 887, 958}, + {1496914449, 958, 1029}, + {2434525091, 1029, 1100}, + {3988652253, 1100, 1171}, + {1122462288, 1171, 1242}, + {724436716, 1242, 1883}, + }, got) +} + +func TestHashByRevValueZero(t *testing.T) { + b, _ := betesting.NewDefaultTmpBackend(t) + s := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) + + var totalRevisions int64 = 1210 + assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions) + assert.Less(t, int64(compactionCycle*10), totalRevisions) + var rev int64 + var got []kvHash + for ; rev < totalRevisions; rev += compactionCycle { + putKVs(s, rev, compactionCycle) + hash := testHashByRev(t, s, 0) + got = append(got, hash) + } + putKVs(s, rev, totalRevisions) + hash := testHashByRev(t, s, 0) + got = append(got, hash) + assert.Equal(t, []kvHash{ + {1913897190, -1, 73}, + {224860069, 73, 145}, + {1565167519, 145, 217}, + {1566261620, 217, 289}, + {2037173024, 289, 361}, + {691659396, 361, 433}, + {2713730748, 433, 505}, + {3919322507, 505, 577}, + {769967540, 577, 649}, + {2909194793, 649, 721}, + {1576921157, 721, 793}, + {4067701532, 793, 865}, + {2226384237, 865, 937}, + {2923408134, 937, 1009}, + {2680329256, 1009, 1081}, + {1546717673, 1081, 1153}, + {2713657846, 1153, 1225}, + {1046575299, 1225, 1297}, + {2017735779, 1297, 2508}, + }, got) +} + +func putKVs(s *store, rev, count int64) { + for i := rev; i <= rev+count; i++ { + s.Put([]byte(pickKey(i)), []byte(fmt.Sprint(i)), 0) + } +} + +func testHashByRev(t *testing.T, s *store, rev int64) kvHash { + hash, currentRev, compactRev, err := s.HashByRev(rev) + assert.NoError(t, err, "error on rev %v", rev) + + if rev == 0 { + rev = currentRev + } + _, err = s.Compact(traceutil.TODO(), rev) + assert.NoError(t, err, "error on compact %v", rev) + return kvHash{hash: hash, compactRevision: compactRev, revision: rev} +} + +type kvHash struct { + hash uint32 + compactRevision int64 + revision int64 +} + +func pickKey(i int64) string { + if i%(compactionCycle*2) == 30 { + return "zenek" + } + if i%compactionCycle == 30 { + return "xavery" + } + // Use low prime number to ensure repeats without alignment + switch i % 7 { + case 0: + return "alice" + case 1: + return "bob" + case 2: + return "celine" + case 3: + return "dominik" + case 4: + return "eve" + case 5: + return "frederica" + case 6: + return "gorge" + default: + panic("Can't count") + } +}