github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/spanz/btree_map.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package spanz 15 16 import ( 17 "bytes" 18 "time" 19 20 "github.com/google/btree" 21 "github.com/pingcap/log" 22 "github.com/pingcap/tiflow/cdc/processor/tablepb" 23 "go.uber.org/zap" 24 ) 25 26 // spanItem is an btree item that wraps a span (key) and an item (value). 27 type spanItem[T any] struct { 28 tablepb.Span 29 Value T 30 } 31 32 // lessSpanItem compares two Spans, defines the order between spans. 33 func lessSpanItem[T any](a, b spanItem[T]) bool { 34 return a.Less(&b.Span) 35 } 36 37 // BtreeMap is a specialized btree map that map a Span to a value. 38 type BtreeMap[T any] struct { 39 tree *btree.BTreeG[spanItem[T]] 40 41 cache *struct { 42 coveredSpans, holes []tablepb.Span 43 lastGC time.Time 44 } 45 } 46 47 // NewBtreeMap returns a new BtreeMap. 48 func NewBtreeMap[T any]() *BtreeMap[T] { 49 const defaultDegree = 16 50 return NewBtreeMapWithDegree[T](defaultDegree) 51 } 52 53 // NewBtreeMapWithDegree returns a new BtreeMap with the given degree. 54 func NewBtreeMapWithDegree[T any](degree int) *BtreeMap[T] { 55 return &BtreeMap[T]{ 56 tree: btree.NewG(degree, lessSpanItem[T]), 57 } 58 } 59 60 // Len returns the number of items currently in the tree. 61 func (m *BtreeMap[T]) Len() int { 62 return m.tree.Len() 63 } 64 65 // Has returns true if the given key is in the tree. 66 func (m *BtreeMap[T]) Has(span tablepb.Span) bool { 67 return m.tree.Has(spanItem[T]{Span: span}) 68 } 69 70 // Get looks for the key item in the tree, returning it. 71 // It returns (zeroValue, false) if unable to find that item. 72 func (m *BtreeMap[T]) Get(span tablepb.Span) (T, bool) { 73 item, ok := m.tree.Get(spanItem[T]{Span: span}) 74 return item.Value, ok 75 } 76 77 // GetV looks for the key item in the tree, returning it. 78 // It returns zeroValue if unable to find that item. 79 func (m *BtreeMap[T]) GetV(span tablepb.Span) T { 80 item, _ := m.tree.Get(spanItem[T]{Span: span}) 81 return item.Value 82 } 83 84 // Delete removes an item equal to the passed in item from the tree, returning 85 // it. If no such item exists, returns (zeroValue, false). 86 func (m *BtreeMap[T]) Delete(span tablepb.Span) (T, bool) { 87 item, ok := m.tree.Delete(spanItem[T]{Span: span}) 88 return item.Value, ok 89 } 90 91 // ReplaceOrInsert adds the given item to the tree. If an item in the tree 92 // already equals the given one, it is removed from the tree and returned, 93 // and the second return value is true. Otherwise, (zeroValue, false) 94 // 95 // nil cannot be added to the tree (will panic). 96 func (m *BtreeMap[T]) ReplaceOrInsert(span tablepb.Span, value T) (T, bool) { 97 old, ok := m.tree.ReplaceOrInsert(spanItem[T]{Span: span, Value: value}) 98 return old.Value, ok 99 } 100 101 // ItemIterator allows callers of Ascend to iterate in-order over portions of 102 // the tree. Similar to btree.ItemIterator. 103 // Note: The span must not be mutated. 104 type ItemIterator[T any] func(span tablepb.Span, value T) bool 105 106 // Ascend calls the iterator for every value in the tree within the range 107 // [first, last], until iterator returns false. 108 func (m *BtreeMap[T]) Ascend(iterator ItemIterator[T]) { 109 m.tree.Ascend(func(item spanItem[T]) bool { 110 return iterator(item.Span, item.Value) 111 }) 112 } 113 114 // AscendRange calls the iterator for every value in the tree within the range 115 // [start, end), until iterator returns false. 116 func (m *BtreeMap[T]) AscendRange(start, end tablepb.Span, iterator ItemIterator[T]) { 117 m.tree.AscendRange(spanItem[T]{Span: start}, spanItem[T]{Span: end}, 118 func(item spanItem[T]) bool { 119 return iterator(item.Span, item.Value) 120 }) 121 } 122 123 // FindHoles returns an array of Span that are not covered in the range 124 // [start, end). 125 // Note: 126 // * Table ID is not set in returned holes. 127 // * Returned slice is read only and will be changed on next FindHoles. 128 func (m *BtreeMap[T]) FindHoles(start, end tablepb.Span) ([]tablepb.Span, []tablepb.Span) { 129 if bytes.Compare(start.StartKey, end.StartKey) >= 0 { 130 log.Panic("start must be larger than end", 131 zap.String("start", start.String()), 132 zap.String("end", end.String())) 133 } 134 if m.cache == nil || time.Since(m.cache.lastGC) > time.Minute { 135 m.cache = &struct { 136 coveredSpans []tablepb.Span 137 holes []tablepb.Span 138 lastGC time.Time 139 }{lastGC: time.Now()} 140 } 141 m.cache.coveredSpans = m.cache.coveredSpans[:0] 142 m.cache.holes = m.cache.holes[:0] 143 144 lastSpan := tablepb.Span{ 145 StartKey: start.StartKey, 146 EndKey: start.StartKey, 147 } 148 m.AscendRange(start, end, func(current tablepb.Span, _ T) bool { 149 ord := bytes.Compare(lastSpan.EndKey, current.StartKey) 150 if ord < 0 { 151 // Find a hole. 152 m.cache.holes = append(m.cache.holes, tablepb.Span{ 153 StartKey: lastSpan.EndKey, 154 EndKey: current.StartKey, 155 }) 156 } else if ord > 0 { 157 log.Panic("map is out of order", 158 zap.String("lastSpan", lastSpan.String()), 159 zap.String("current", current.String())) 160 } 161 162 lastSpan = current 163 m.cache.coveredSpans = append(m.cache.coveredSpans, current) 164 return true 165 }) 166 if len(m.cache.coveredSpans) == 0 { 167 // No such span in the map. 168 m.cache.holes = append(m.cache.holes, tablepb.Span{ 169 StartKey: start.StartKey, EndKey: end.StartKey, 170 }) 171 return m.cache.coveredSpans, m.cache.holes 172 } 173 // Check if there is a hole in the end. 174 if !bytes.Equal(lastSpan.EndKey, end.StartKey) { 175 m.cache.holes = append(m.cache.holes, tablepb.Span{ 176 StartKey: lastSpan.EndKey, 177 EndKey: end.StartKey, 178 }) 179 } 180 return m.cache.coveredSpans, m.cache.holes 181 }