@@ -1034,33 +1034,33 @@ func (s *spannerCoordinator) garbageCollect(ctx context.Context, treeSize uint64
10341034 return nil
10351035 }
10361036
1037- n := uint (0 )
1037+ d := uint (0 )
10381038 eg := errgroup.Group {}
10391039 // GC the tree in "vertical" chunks defined by entry bundles.
10401040 for ri := range layout .Range (fromSize , treeSize - fromSize , treeSize ) {
10411041 // Only known-full bundles are in-scope for for GC, so exit if the current bundle is partial or
10421042 // we've reached our limit of chunks.
1043- if ri .Partial > 0 || n > maxBundles {
1043+ if ri .Partial > 0 || d > maxBundles {
10441044 break
10451045 }
10461046
1047- // GC any partial versions of the entry bundle itself.
1047+ // GC any partial versions of the entry bundle itself and the tile which sits immediately above it .
10481048 eg .Go (func () error { return deleteWithPrefix (ctx , layout .EntriesPath (ri .Index , 0 )+ ".p/" ) })
1049+ eg .Go (func () error { return deleteWithPrefix (ctx , layout .TilePath (0 , ri .Index , 0 )+ ".p/" ) })
10491050 fromSize += uint64 (ri .N )
1050- n ++
1051+ d ++
10511052
10521053 // Now consider (only) the part of the tree which sits above the bundle.
1053- // We'll walk up, layer by layer, until we find a tile which is non-full (and therefore ineligible
1054- // for GC), at which point we can stop since there cannot be a full tile above a partial tile.
1055- for lvl , idx := uint64 (0 ), ri .Index ; ; lvl , idx = lvl + 1 , idx >> layout .TileHeight {
1056- // GC any partial versions of the tile.
1057- eg .Go (func () error { return deleteWithPrefix (ctx , layout .TilePath (lvl , idx , 0 )+ ".p/" ) })
1058-
1059- // The tile above is full IFF this tile rolls up as the last element in that tile.
1060- // If it's not full, then neither it, nor anything above it, needs GC yet so we're done.
1061- if idx % layout .TileWidth != layout .TileWidth - 1 {
1062- break
1063- }
1054+ // We'll walk up the parent tiles for as a long as we're tracing the right-hand
1055+ // edge of a perfect subtree.
1056+ // This gives the property we'll only visit each parent tile once, rather than up to 256 times.
1057+ pL , pIdx := uint64 (0 ), ri .Index
1058+ for isLastLeafInParent (pIdx ) {
1059+ // Move our coordinates up to the parent
1060+ pL , pIdx = pL + 1 , pIdx >> layout .TileHeight
1061+ // GC any partial versions of the parent tile.
1062+ eg .Go (func () error { return deleteWithPrefix (ctx , layout .TilePath (pL , pIdx , 0 )+ ".p/" ) })
1063+
10641064 }
10651065 }
10661066 if err := eg .Wait (); err != nil {
@@ -1076,6 +1076,12 @@ func (s *spannerCoordinator) garbageCollect(ctx context.Context, treeSize uint64
10761076 return err
10771077}
10781078
1079+ // isLastLeafInParent returns true if a tile with the provided index is the final child node of a
1080+ // (hypothetical) full parent tile.
1081+ func isLastLeafInParent (i uint64 ) bool {
1082+ return i % layout .TileWidth == layout .TileWidth - 1
1083+ }
1084+
10791085// gcsStorage knows how to store and retrieve objects from GCS.
10801086type gcsStorage struct {
10811087 bucket string
0 commit comments