github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/condensable_span_set.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package kvcoord
    12  
    13  import (
    14  	"context"
    15  	"sort"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/keys"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/util/log"
    20  )
    21  
    22  // condensableSpanSet is a set of key spans that is condensable in order to
    23  // stay below some maximum byte limit. Condensing of the set happens in two
    24  // ways. Initially, overlapping spans are merged together to deduplicate
    25  // redundant keys. If that alone isn't sufficient to stay below the byte limit,
    26  // spans within the same Range will be merged together. This can cause the
    27  // "footprint" of the set to grow, so the set should be thought of as on
    28  // overestimate.
    29  type condensableSpanSet struct {
    30  	s     []roachpb.Span
    31  	bytes int64
    32  
    33  	// condensed is set if we ever condensed the spans. Meaning, if the set of
    34  	// spans currently tracked has lost fidelity compared to the spans inserted.
    35  	// Note that we might have otherwise mucked with the inserted spans to save
    36  	// memory without losing fidelity, in which case this flag would not be set
    37  	// (e.g. merging overlapping or adjacent spans).
    38  	condensed bool
    39  }
    40  
    41  // insert adds new spans to the condensable span set. No attempt to condense the
    42  // set or deduplicate the new span with existing spans is made.
    43  func (s *condensableSpanSet) insert(spans ...roachpb.Span) {
    44  	s.s = append(s.s, spans...)
    45  	for _, sp := range spans {
    46  		s.bytes += spanSize(sp)
    47  	}
    48  }
    49  
    50  // mergeAndSort merges all overlapping spans. Calling this method will not
    51  // increase the overall bounds of the span set, but will eliminate duplicated
    52  // spans and combine overlapping spans.
    53  //
    54  // The method has the side effect of sorting the stable write set.
    55  func (s *condensableSpanSet) mergeAndSort() {
    56  	oldLen := len(s.s)
    57  	s.s, _ = roachpb.MergeSpans(s.s)
    58  	// Recompute the size if anything has changed.
    59  	if oldLen != len(s.s) {
    60  		s.bytes = 0
    61  		for _, sp := range s.s {
    62  			s.bytes += spanSize(sp)
    63  		}
    64  	}
    65  }
    66  
    67  // maybeCondense is similar in spirit to mergeAndSort, but it only adjusts the
    68  // span set when the maximum byte limit is exceeded. However, when this limit is
    69  // exceeded, the method is more aggressive in its attempt to reduce the memory
    70  // footprint of the span set. Not only will it merge overlapping spans, but
    71  // spans within the same range boundaries are also condensed.
    72  //
    73  // Returns true if condensing was done. Note that, even if condensing was
    74  // performed, this doesn't guarantee that the size was reduced below the byte
    75  // limit. Condensing is only performed at the level of individual ranges, not
    76  // across ranges, so it's possible to not be able to condense as much as
    77  // desired.
    78  func (s *condensableSpanSet) maybeCondense(
    79  	ctx context.Context, riGen rangeIteratorFactory, maxBytes int64,
    80  ) bool {
    81  	if s.bytes < maxBytes {
    82  		return false
    83  	}
    84  
    85  	// Start by attempting to simply merge the spans within the set. This alone
    86  	// may bring us under the byte limit. Even if it doesn't, this step has the
    87  	// nice property that it sorts the spans by start key, which we rely on
    88  	// lower in this method.
    89  	s.mergeAndSort()
    90  	if s.bytes < maxBytes {
    91  		return false
    92  	}
    93  
    94  	ri := riGen.newRangeIterator()
    95  
    96  	// Divide spans by range boundaries and condense. Iterate over spans
    97  	// using a range iterator and add each to a bucket keyed by range
    98  	// ID. Local keys are kept in a new slice and not added to buckets.
    99  	type spanBucket struct {
   100  		rangeID roachpb.RangeID
   101  		bytes   int64
   102  		spans   []roachpb.Span
   103  	}
   104  	var buckets []spanBucket
   105  	var localSpans []roachpb.Span
   106  	for _, sp := range s.s {
   107  		if keys.IsLocal(sp.Key) {
   108  			localSpans = append(localSpans, sp)
   109  			continue
   110  		}
   111  		ri.Seek(ctx, roachpb.RKey(sp.Key), Ascending)
   112  		if !ri.Valid() {
   113  			// We haven't modified s.s yet, so it is safe to return.
   114  			log.Warningf(ctx, "failed to condense lock spans: %v", ri.Error())
   115  			return false
   116  		}
   117  		rangeID := ri.Desc().RangeID
   118  		if l := len(buckets); l > 0 && buckets[l-1].rangeID == rangeID {
   119  			buckets[l-1].spans = append(buckets[l-1].spans, sp)
   120  		} else {
   121  			buckets = append(buckets, spanBucket{
   122  				rangeID: rangeID, spans: []roachpb.Span{sp},
   123  			})
   124  		}
   125  		buckets[len(buckets)-1].bytes += spanSize(sp)
   126  	}
   127  
   128  	// Sort the buckets by size and collapse from largest to smallest
   129  	// until total size of uncondensed spans no longer exceeds threshold.
   130  	sort.Slice(buckets, func(i, j int) bool { return buckets[i].bytes > buckets[j].bytes })
   131  	s.s = localSpans // reset to hold just the local spans; will add newly condensed and remainder
   132  	for _, bucket := range buckets {
   133  		// Condense until we get to half the threshold.
   134  		if s.bytes <= maxBytes/2 {
   135  			// Collect remaining spans from each bucket into uncondensed slice.
   136  			s.s = append(s.s, bucket.spans...)
   137  			continue
   138  		}
   139  		s.bytes -= bucket.bytes
   140  		// TODO(spencer): consider further optimizations here to create
   141  		// more than one span out of a bucket to avoid overly broad span
   142  		// combinations.
   143  		cs := bucket.spans[0]
   144  		for _, s := range bucket.spans[1:] {
   145  			cs = cs.Combine(s)
   146  			if !cs.Valid() {
   147  				// If we didn't fatal here then we would need to ensure that the
   148  				// spans were restored or a transaction could lose part of its
   149  				// lock footprint.
   150  				log.Fatalf(ctx, "failed to condense lock spans: "+
   151  					"combining span %s yielded invalid result", s)
   152  			}
   153  		}
   154  		s.bytes += spanSize(cs)
   155  		s.s = append(s.s, cs)
   156  	}
   157  	s.condensed = true
   158  	return true
   159  }
   160  
   161  // asSlice returns the set as a slice of spans.
   162  func (s *condensableSpanSet) asSlice() []roachpb.Span {
   163  	l := len(s.s)
   164  	return s.s[:l:l] // immutable on append
   165  }
   166  
   167  // empty returns whether the set is empty or whether it contains spans.
   168  func (s *condensableSpanSet) empty() bool {
   169  	return len(s.s) == 0
   170  }
   171  
   172  func (s *condensableSpanSet) clear() {
   173  	*s = condensableSpanSet{}
   174  }
   175  
   176  func spanSize(sp roachpb.Span) int64 {
   177  	return int64(len(sp.Key) + len(sp.EndKey))
   178  }
   179  
   180  func keySize(k roachpb.Key) int64 {
   181  	return int64(len(k))
   182  }