@@ -7,16 +7,23 @@ package kvstorage
77
88import (
99 "context"
10+ "math"
1011 "testing"
1112
13+ "github.com/cockroachdb/cockroach/pkg/keys"
14+ "github.com/cockroachdb/cockroach/pkg/kv/kvpb"
1215 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
1316 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvstorage/wag"
1417 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvstorage/wag/wagpb"
18+ "github.com/cockroachdb/cockroach/pkg/kv/kvserver/logstore"
1519 "github.com/cockroachdb/cockroach/pkg/roachpb"
20+ "github.com/cockroachdb/cockroach/pkg/settings/cluster"
1621 "github.com/cockroachdb/cockroach/pkg/storage"
22+ "github.com/cockroachdb/cockroach/pkg/storage/fs"
1723 "github.com/cockroachdb/cockroach/pkg/util/leaktest"
1824 "github.com/cockroachdb/cockroach/pkg/util/log"
1925 "github.com/stretchr/testify/require"
26+ "golang.org/x/time/rate"
2027)
2128
2229// testEngines holds the separated log and state engines used by truncator
@@ -45,25 +52,44 @@ func (e *testEngines) writeWAGNode(t *testing.T, event wagpb.Event) {
4552 require .NoError (t , wag .Write (e .LogEngine (), index , wagpb.Node {Events : []wagpb.Event {event }}))
4653}
4754
48- // truncateWAGNodes repeatedly truncates applied WAG nodes until none remain
49- // applied.
50- func (e * testEngines ) truncateWAGNodes (t * testing.T ) {
55+ // writeRaftLogEntry writes a dummy raft log entry to the log engine.
56+ func (e * testEngines ) writeRaftLogEntry (
57+ t * testing.T , rangeID roachpb.RangeID , index kvpb.RaftIndex ,
58+ ) {
5159 t .Helper ()
52- ctx := context .Background ()
60+ sl := MakeStateLoader (rangeID )
61+ require .NoError (t , e .LogEngine ().PutUnversioned (sl .RaftLogKey (index ), []byte ("entry" )))
62+ }
63+
64+ // raftLogIndices returns the indices of all raft log entries for the given range
65+ // in the log engine.
66+ func (e * testEngines ) raftLogIndices (t * testing.T , rangeID roachpb.RangeID ) []kvpb.RaftIndex {
67+ t .Helper ()
68+ sl := MakeStateLoader (rangeID )
69+ prefix := sl .RaftLogPrefix ()
70+ var indices []kvpb.RaftIndex
71+ iter , err := e .LogEngine ().NewMVCCIterator (
72+ context .Background (), storage .MVCCKeyIterKind , storage.IterOptions {
73+ LowerBound : prefix ,
74+ UpperBound : prefix .PrefixEnd (),
75+ ReadCategory : fs .ReplicationReadCategory ,
76+ })
77+ require .NoError (t , err )
78+ defer iter .Close ()
79+ iter .SeekGE (storage .MakeMVCCMetadataKey (prefix ))
5380 for {
54- b := e .LogEngine ().NewWriteBatch ()
55- truncated , err := truncateAppliedNode (
56- ctx , Raft {RO : e .LogEngine (), WO : b }, e .StateEngine (),
57- )
58- if err == nil && truncated {
59- err = b .Commit (false /* sync */ )
60- }
61- b .Close ()
62- require .NoError (t , err )
63- if ! truncated {
64- return
81+ ok , iterErr := iter .Valid ()
82+ require .NoError (t , iterErr )
83+ if ! ok {
84+ break
6585 }
86+ suffix := iter .UnsafeKey ().Key [len (prefix ):]
87+ index , decErr := keys .DecodeRaftLogKeyFromSuffix (suffix )
88+ require .NoError (t , decErr )
89+ indices = append (indices , index )
90+ iter .Next ()
6691 }
92+ return indices
6793}
6894
6995// listWAGNodes returns the indices of all WAG nodes in the log engine.
@@ -78,6 +104,39 @@ func (e *testEngines) listWAGNodes(t *testing.T) []uint64 {
78104 return indices
79105}
80106
107+ // makeSideloadClearer returns a SideloadClearer that truncates sideloaded
108+ // files for a given range using disk-based sideload storage.
109+ func (e * testEngines ) makeSideloadClearer (
110+ st * cluster.Settings , baseDir string , limiter * rate.Limiter , env * fs.Env ,
111+ ) SideloadClearer {
112+ return func (ctx context.Context , rangeID roachpb.RangeID , lastIndex kvpb.RaftIndex ) error {
113+ ss := logstore .NewDiskSideloadStorage (st , rangeID , baseDir , limiter , env )
114+ if err := ss .TruncateTo (ctx , lastIndex ); err != nil {
115+ return err
116+ }
117+ return ss .Sync ()
118+ }
119+ }
120+
121+ // truncateWAGNodes repeatedly calls truncateAppliedWAGNodeAndClearRaftState
122+ // until no more nodes can be deleted.
123+ func (e * testEngines ) truncateWAGNodes (t * testing.T , clearSideloaded SideloadClearer ) {
124+ t .Helper ()
125+ require .NoError (t , e .StateEngine ().Flush ())
126+ stateReader := e .StateEngine ().NewReader (storage .GuaranteedDurability )
127+ defer stateReader .Close ()
128+ for {
129+ b := e .LogEngine ().NewBatch ()
130+ ok , err := truncateAppliedWAGNodeAndClearRaftState (context .Background (),
131+ Raft {RO : e .LogEngine (), WO : b }, stateReader , clearSideloaded )
132+ require .NoError (t , err )
133+ require .NoError (t , b .Commit (false /* sync */ ))
134+ if ! ok {
135+ return
136+ }
137+ }
138+ }
139+
81140func TestTruncateApplied (t * testing.T ) {
82141 defer leaktest .AfterTest (t )()
83142 defer log .Scope (t ).Close (t )
@@ -215,8 +274,88 @@ func TestTruncateApplied(t *testing.T) {
215274 e := makeTestEngines ()
216275 defer e .Close ()
217276 tc .setup (t , & e )
218- e .truncateWAGNodes (t )
277+ e .truncateWAGNodes (t , nil /* clearSideloaded */ )
219278 require .Equal (t , tc .wantWAGIndices , e .listWAGNodes (t ))
220279 })
221280 }
222281}
282+
283+ // TestTruncateAndClearRaftState verifies that
284+ // truncateAppliedWAGNodeAndClearRaftState only clears raft log entries and
285+ // sideloaded files up to the destroyed/subsumed replica's last index. Entries
286+ // and files beyond that index may belong to a newer replica and must be
287+ // preserved.
288+ func TestTruncateAndClearRaftState (t * testing.T ) {
289+ defer leaktest .AfterTest (t )()
290+ defer log .Scope (t ).Close (t )
291+
292+ r1 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 1 }
293+ r2 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 2 }
294+ sl := MakeStateLoader (1 /* rangeID */ )
295+
296+ for _ , eventType := range []wagpb.EventType {wagpb .EventDestroy , wagpb .EventSubsume } {
297+ t .Run (eventType .String (), func (t * testing.T ) {
298+ e := makeTestEngines ()
299+ defer e .Close ()
300+ ctx := context .Background ()
301+
302+ // Write WAG nodes: init then destroy/subsume at index 20.
303+ e .writeWAGNode (t , wagpb.Event {
304+ Addr : wagpb .MakeAddr (r1 , 10 ), Type : wagpb .EventInit ,
305+ })
306+ e .writeWAGNode (t , wagpb.Event {
307+ Addr : wagpb .MakeAddr (r1 , 20 ), Type : eventType ,
308+ })
309+
310+ // Create a WAG node for a newer replica for the same range.
311+ e .writeWAGNode (t , wagpb.Event {
312+ Addr : wagpb .MakeAddr (r2 , 0 ), Type : wagpb .EventCreate ,
313+ })
314+
315+ // Tombstone confirms destruction/subsumption.
316+ require .NoError (t , sl .SetRangeTombstone (ctx , e .StateEngine (),
317+ kvserverpb.RangeTombstone {NextReplicaID : 2 }))
318+
319+ // Raft log entries at indices 10-25. (Indices at or below lastIndex=20)
320+ // belong to the old replica. Entries at 21-25 (above lastIndex=20)
321+ // belong to a newer replica reusing the same RangeID.
322+ for idx := 10 ; idx <= 25 ; idx ++ {
323+ e .writeRaftLogEntry (t , 1 /* rangeID */ , kvpb .RaftIndex (idx ))
324+ }
325+
326+ // Create sideloaded files using the log engine's VFS.
327+ st := cluster .MakeTestingClusterSettings ()
328+ baseDir := e .LogEngine ().GetAuxiliaryDir ()
329+ limiter := rate .NewLimiter (rate .Inf , math .MaxInt64 )
330+ env := e .LogEngine ().Env ()
331+ ss := logstore .NewDiskSideloadStorage (st , 1 , baseDir , limiter , env )
332+ for _ , idx := range []kvpb.RaftIndex {10 , 15 , 20 , 21 , 25 } {
333+ require .NoError (t , ss .Put (ctx , idx , 1 /* term */ , []byte ("sst-data" )))
334+ }
335+ clearer := e .makeSideloadClearer (st , baseDir , limiter , env )
336+
337+ e .truncateWAGNodes (t , clearer )
338+ // Raft log entries 10-20 (at or below lastIndex=20) must be deleted. The
339+ // rest shouldn't be deleted by the WAG truncator.
340+ require .Equal (t ,
341+ []kvpb.RaftIndex {21 , 22 , 23 , 24 , 25 },
342+ e .raftLogIndices (t , 1 /* rangeID */ ),
343+ )
344+ // Sideloaded files at indices 10, 15, and 20 (at or below lastIndex=20)
345+ // must be deleted.
346+ for _ , idx := range []kvpb.RaftIndex {10 , 15 , 20 } {
347+ _ , err := ss .Get (ctx , idx , 1 )
348+ require .Errorf (t , err , "sideloaded file at index %d should have been deleted" , idx )
349+ }
350+ // Sideloaded files at indices 21 and 25 (above lastIndex=20) must be
351+ // preserved.
352+ for _ , idx := range []kvpb.RaftIndex {21 , 25 } {
353+ data , err := ss .Get (ctx , idx , 1 )
354+ require .NoErrorf (t , err , "sideloaded file at index %d should be preserved" , idx )
355+ require .Equal (t , []byte ("sst-data" ), data )
356+ }
357+ // Only the WAG node for the newer replica should be left.
358+ require .Equal (t , []uint64 {3 }, e .listWAGNodes (t ))
359+ })
360+ }
361+ }
0 commit comments