@@ -348,7 +348,7 @@ func (a *Appender) garbageCollectorJob(ctx context.Context, i time.Duration) {
348
348
defer t .Stop ()
349
349
350
350
// Entirely arbitrary number.
351
- maxDeletesPerRun := uint (1024 )
351
+ maxBundlesPerRun := uint (100 )
352
352
353
353
for {
354
354
select {
@@ -372,7 +372,7 @@ func (a *Appender) garbageCollectorJob(ctx context.Context, i time.Duration) {
372
372
klog .Warningf ("Failed to parse published checkpoint: %v" , err )
373
373
}
374
374
375
- if err := a .sequencer .garbageCollect (ctx , pubSize , maxDeletesPerRun , a .logStore .objStore .deleteObjectsWithPrefix ); err != nil {
375
+ if err := a .sequencer .garbageCollect (ctx , pubSize , maxBundlesPerRun , a .logStore .objStore .deleteObjectsWithPrefix ); err != nil {
376
376
klog .Warningf ("GarbageCollect failed: %v" , err )
377
377
}
378
378
}()
@@ -1008,13 +1008,12 @@ func (s *spannerCoordinator) publishTree(ctx context.Context, minAge time.Durati
1008
1008
return nil
1009
1009
}
1010
1010
1011
- // garbageCollect is a long running function which will identify unneeded partial tiles/entry bundles, and call the provided function to remove them.
1011
+ // garbageCollect will identify up to maxBundles unneeded partial entry bundles (and any unneeded partial tiles which sit above them in the tree) and
1012
+ // call the provided function to remove them.
1012
1013
//
1013
1014
// Uses the `GCCoord` table to ensure that only one binary is actively garbage collecting at any given time, and to track progress so that we don't
1014
1015
// needlessly attempt to GC over regions which have already been cleaned.
1015
- //
1016
- // Returns true if we've "caught up" with the current state of the tree.
1017
- func (s * spannerCoordinator ) garbageCollect (ctx context.Context , treeSize uint64 , maxDeletes uint , deleteWithPrefix func (ctx context.Context , prefix string ) error ) error {
1016
+ func (s * spannerCoordinator ) garbageCollect (ctx context.Context , treeSize uint64 , maxBundles uint , deleteWithPrefix func (ctx context.Context , prefix string ) error ) error {
1018
1017
_ , err := s .dbPool .ReadWriteTransaction (ctx , func (ctx context.Context , txn * spanner.ReadWriteTransaction ) error {
1019
1018
row , err := txn .ReadRowWithOptions (ctx , "GCCoord" , spanner.Key {0 }, []string {"fromSize" }, & spanner.ReadOptions {LockHint : spannerpb .ReadRequest_LOCK_HINT_EXCLUSIVE })
1020
1019
if err != nil {
@@ -1030,21 +1029,33 @@ func (s *spannerCoordinator) garbageCollect(ctx context.Context, treeSize uint64
1030
1029
return nil
1031
1030
}
1032
1031
1033
- d := uint (0 )
1032
+ n := uint (0 )
1034
1033
eg := errgroup.Group {}
1035
- done:
1036
- for l , f , x := uint64 (0 ), fromSize , treeSize ; x > 0 ; l , f , x = l + 1 , f >> layout .TileHeight , x >> layout .TileHeight {
1037
- for ri := range layout .Range (f , x - f , x ) {
1038
- if ri .Partial != 0 || d > maxDeletes {
1039
- break done
1040
- }
1041
- if l == 0 {
1042
- eg .Go (func () error { return deleteWithPrefix (ctx , layout .EntriesPath (ri .Index , 0 )+ ".p/" ) })
1043
- d ++
1044
- fromSize += uint64 (ri .N )
1034
+ // GC the tree in "vertical" chunks defined by entry bundles.
1035
+ for ri := range layout .Range (fromSize , treeSize - fromSize , treeSize ) {
1036
+ // Only known-full bundles are in-scope for for GC, so exit if the current bundle is partial or
1037
+ // we've reached our limit of chunks.
1038
+ if ri .Partial > 0 || n > maxBundles {
1039
+ break
1040
+ }
1041
+
1042
+ // GC any partial versions of the entry bundle itself.
1043
+ eg .Go (func () error { return deleteWithPrefix (ctx , layout .EntriesPath (ri .Index , 0 )+ ".p/" ) })
1044
+ fromSize += uint64 (ri .N )
1045
+ n ++
1046
+
1047
+ // Now consider (only) the part of the tree which sits above the bundle.
1048
+ // We'll walk up, layer by layer, until we find a tile which is non-full (and therefore ineligible
1049
+ // for GC), at which point we can stop since there cannot be a full tile above a partial tile.
1050
+ for lvl , idx := uint64 (0 ), ri .Index ; ; lvl , idx = lvl + 1 , idx >> layout .TileHeight {
1051
+ // GC any partial versions of the tile.
1052
+ eg .Go (func () error { return deleteWithPrefix (ctx , layout .TilePath (lvl , idx , 0 )+ ".p/" ) })
1053
+
1054
+ // The tile above is full IFF this tile rolls up as the last element in that tile.
1055
+ // If it's not full, then neither it, nor anything above it, needs GC yet so we're done.
1056
+ if idx % layout .TileWidth != layout .TileWidth - 1 {
1057
+ break
1045
1058
}
1046
- eg .Go (func () error { return deleteWithPrefix (ctx , layout .TilePath (l , ri .Index , 0 )+ ".p/" ) })
1047
- d ++
1048
1059
}
1049
1060
}
1050
1061
if err := eg .Wait (); err != nil {
0 commit comments