@@ -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,43 @@ 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 ()
53- 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 ()
60+ require .NoError (t , e .LogEngine ().PutUnversioned (
61+ keys .RaftLogKeyFromPrefix (keys .RaftLogPrefix (rangeID ), 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 ))
80+ for ; ; iter .Next () {
81+ ok , err := iter .Valid ()
6282 require .NoError (t , err )
63- if ! truncated {
64- return
83+ if ! ok {
84+ break
6585 }
86+ suffix := iter .UnsafeKey ().Key [len (prefix ):]
87+ index , err := keys .DecodeRaftLogKeyFromSuffix (suffix )
88+ require .NoError (t , err )
89+ indices = append (indices , index )
6690 }
91+ return indices
6792}
6893
6994// listWAGNodes returns the indices of all WAG nodes in the log engine.
@@ -78,6 +103,41 @@ func (e *testEngines) listWAGNodes(t *testing.T) []uint64 {
78103 return indices
79104}
80105
106+ // makeSideloadClearer returns a SideloadClearer that truncates sideloaded
107+ // files for a given range using disk-based sideload storage.
108+ func (e * testEngines ) makeSideloadClearer (
109+ st * cluster.Settings , baseDir string , env * fs.Env ,
110+ ) SideloadClearer {
111+ return func (ctx context.Context , rangeID roachpb.RangeID , lastIndex kvpb.RaftIndex ) error {
112+ limiter := rate .NewLimiter (rate .Inf , math .MaxInt64 )
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+ b .Close ()
135+ if ! ok {
136+ return
137+ }
138+ }
139+ }
140+
81141func TestTruncateApplied (t * testing.T ) {
82142 defer leaktest .AfterTest (t )()
83143 defer log .Scope (t ).Close (t )
@@ -110,7 +170,7 @@ func TestTruncateApplied(t *testing.T) {
110170 & kvserverpb.RangeAppliedState {RaftAppliedIndex : 9 }))
111171 },
112172 // No WAG node has been applied yet because the current
113- // replica id is < the event replica id .
173+ // replicaID is < the event replicaID .
114174 wantWAGIndices : []uint64 {1 , 2 , 3 },
115175 },
116176 {
@@ -189,7 +249,7 @@ func TestTruncateApplied(t *testing.T) {
189249 e .writeWAGNode (t , wagpb.Event {Addr : wagpb .MakeAddr (r1 , 0 ), Type : wagpb .EventCreate })
190250 e .writeWAGNode (t , wagpb.Event {Addr : wagpb .MakeAddr (r1 , 20 ), Type : wagpb .EventDestroy })
191251 require .NoError (t , sl .SetRaftReplicaID (ctx , e .StateEngine (), r1 .ReplicaID ))
192- // EventDestroy won't be deleted if the replica id still matches the
252+ // EventDestroy won't be deleted if the replicaID still matches the
193253 // WAG node's replica ID because it means that the event hasn't applied
194254 // yet. In this case, we won't even read the applied index.
195255 require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
@@ -202,7 +262,7 @@ func TestTruncateApplied(t *testing.T) {
202262 e .writeWAGNode (t , wagpb.Event {Addr : wagpb .MakeAddr (r1 , 0 ), Type : wagpb .EventCreate })
203263 e .writeWAGNode (t , wagpb.Event {Addr : wagpb .MakeAddr (r1 , 20 ), Type : wagpb .EventSubsume })
204264 require .NoError (t , sl .SetRaftReplicaID (ctx , e .StateEngine (), r1 .ReplicaID ))
205- // EventSubsume won't be deleted if the replica id still matches the
265+ // EventSubsume won't be deleted if the replicaID still matches the
206266 // WAG node's replica ID because it means that the event hasn't applied
207267 // yet. In this case, we won't even read the applied index.
208268 require .NoError (t , sl .SetRangeAppliedState (ctx , e .StateEngine (),
@@ -215,8 +275,86 @@ func TestTruncateApplied(t *testing.T) {
215275 e := makeTestEngines ()
216276 defer e .Close ()
217277 tc .setup (t , & e )
218- e .truncateWAGNodes (t )
278+ e .truncateWAGNodes (t , nil /* clearSideloaded */ )
219279 require .Equal (t , tc .wantWAGIndices , e .listWAGNodes (t ))
220280 })
221281 }
222282}
283+
284+ // TestTruncateAndClearRaftState verifies that
285+ // truncateAppliedWAGNodeAndClearRaftState only clears raft log entries and
286+ // sideloaded files up to the destroyed/subsumed replica's last index. Entries
287+ // and files beyond that index may belong to a newer replica and must be
288+ // preserved.
289+ func TestTruncateAndClearRaftState (t * testing.T ) {
290+ defer leaktest .AfterTest (t )()
291+ defer log .Scope (t ).Close (t )
292+
293+ r1 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 1 }
294+ r2 := roachpb.FullReplicaID {RangeID : 1 , ReplicaID : 2 }
295+ sl := MakeStateLoader (1 /* rangeID */ )
296+
297+ for _ , eventType := range []wagpb.EventType {wagpb .EventDestroy , wagpb .EventSubsume } {
298+ t .Run (eventType .String (), func (t * testing.T ) {
299+ e := makeTestEngines ()
300+ defer e .Close ()
301+ ctx := context .Background ()
302+
303+ // Write WAG nodes: init then destroy/subsume at index 20.
304+ e .writeWAGNode (t , wagpb.Event {
305+ Addr : wagpb .MakeAddr (r1 , 10 ), Type : wagpb .EventInit ,
306+ })
307+ e .writeWAGNode (t , wagpb.Event {
308+ Addr : wagpb .MakeAddr (r1 , 20 ), Type : eventType ,
309+ })
310+
311+ // Create a WAG node for a newer replica for the same range.
312+ e .writeWAGNode (t , wagpb.Event {
313+ Addr : wagpb .MakeAddr (r2 , 0 ), Type : wagpb .EventCreate ,
314+ })
315+
316+ // Tombstone confirms destruction/subsumption.
317+ require .NoError (t , sl .SetRangeTombstone (ctx , e .StateEngine (),
318+ kvserverpb.RangeTombstone {NextReplicaID : 2 }))
319+
320+ // Write raft log entries 10-25. Entries <= 20 belong to the old replica.
321+ // Entries >= 21 belong to the new replica using 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+ env := e .LogEngine ().Env ()
330+ limiter := rate .NewLimiter (rate .Inf , math .MaxInt64 )
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 , env )
336+
337+ e .truncateWAGNodes (t , clearer )
338+ // Raft entries <= 20 belong to the old replica and 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 entries 10, 15, and 20 belong to the old replica
345+ // and must be deleted. The rest shouldn't be deleted by the WAG
346+ // truncator.
347+ for _ , idx := range []kvpb.RaftIndex {10 , 15 , 20 } {
348+ _ , err := ss .Get (ctx , idx , 1 )
349+ require .Errorf (t , err , "sideloaded file at index %d should have been deleted" , idx )
350+ }
351+ for _ , idx := range []kvpb.RaftIndex {21 , 25 } {
352+ data , err := ss .Get (ctx , idx , 1 )
353+ require .NoErrorf (t , err , "sideloaded file at index %d should be preserved" , idx )
354+ require .Equal (t , []byte ("sst-data" ), data )
355+ }
356+ // Only the WAG node for the newer replica should be left.
357+ require .Equal (t , []uint64 {3 }, e .listWAGNodes (t ))
358+ })
359+ }
360+ }
0 commit comments