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 }