github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/puller/frontier/frontier.go (about) 1 // Copyright 2020 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 frontier 15 16 import ( 17 "bytes" 18 "encoding/hex" 19 "fmt" 20 "math" 21 "strings" 22 23 "github.com/pingcap/tiflow/cdc/processor/tablepb" 24 ) 25 26 // fakeRegionID when the frontier is initializing, there is no region ID 27 // use fakeRegionID ,so this span will be cached 28 const fakeRegionID = 0 29 30 // Frontier checks resolved event of spans and moves the global resolved ts ahead 31 type Frontier interface { 32 Forward(regionID uint64, span tablepb.Span, ts uint64) 33 Frontier() uint64 34 String() string 35 SpanString(span tablepb.Span) string 36 Entries(fn func(key []byte, ts uint64)) 37 } 38 39 // spanFrontier tracks the minimum timestamp of a set of spans. 40 type spanFrontier struct { 41 spanList skipList 42 minTsHeap fibonacciHeap 43 44 seekTempResult []*skipListNode 45 46 cachedRegions map[uint64]*skipListNode 47 } 48 49 // NewFrontier creates Frontier from the given spans. 50 // spanFrontier don't support use Nil as the maximum key of End range 51 // So we use set it as util.UpperBoundKey, the means the real use case *should not* have an 52 // End key bigger than util.UpperBoundKey 53 func NewFrontier(checkpointTs uint64, spans ...tablepb.Span) Frontier { 54 s := &spanFrontier{ 55 spanList: *newSpanList(), 56 seekTempResult: make(seekResult, maxHeight), 57 cachedRegions: map[uint64]*skipListNode{}, 58 } 59 60 firstSpan := true 61 for _, span := range spans { 62 if firstSpan { 63 s.spanList.Insert(span.StartKey, s.minTsHeap.Insert(checkpointTs)) 64 s.spanList.Insert(span.EndKey, s.minTsHeap.Insert(math.MaxUint64)) 65 firstSpan = false 66 continue 67 } 68 s.insert(fakeRegionID, span, checkpointTs) 69 } 70 71 return s 72 } 73 74 // Frontier return the minimum timestamp. 75 func (s *spanFrontier) Frontier() uint64 { 76 return s.minTsHeap.GetMinKey() 77 } 78 79 // Forward advances the timestamp for a span. 80 func (s *spanFrontier) Forward(regionID uint64, span tablepb.Span, ts uint64) { 81 // it's the fast part to detect if the region is split or merged, 82 // if not we can update the minTsHeap with use new ts directly 83 if n, ok := s.cachedRegions[regionID]; ok && n.regionID == regionID && n.end != nil { 84 if bytes.Equal(n.Key(), span.StartKey) && bytes.Equal(n.End(), span.EndKey) { 85 s.minTsHeap.UpdateKey(n.Value(), ts) 86 return 87 } 88 } 89 s.insert(regionID, span, ts) 90 } 91 92 func (s *spanFrontier) insert(regionID uint64, span tablepb.Span, ts uint64) { 93 // clear the seek result 94 for i := 0; i < len(s.seekTempResult); i++ { 95 s.seekTempResult[i] = nil 96 } 97 seekRes := s.spanList.Seek(span.StartKey, s.seekTempResult) 98 // if there is no change in the region span 99 // We just need to update the ts corresponding to the span in list 100 next := seekRes.Node().Next() 101 // next is nil means the span.StartKey is larger than all the spans in list 102 if next != nil { 103 if bytes.Equal(seekRes.Node().Key(), span.StartKey) && 104 bytes.Equal(next.Key(), span.EndKey) { 105 s.minTsHeap.UpdateKey(seekRes.Node().Value(), ts) 106 delete(s.cachedRegions, seekRes.Node().regionID) 107 if regionID != fakeRegionID { 108 s.cachedRegions[regionID] = seekRes.Node() 109 s.cachedRegions[regionID].regionID = regionID 110 s.cachedRegions[regionID].end = next.key 111 } 112 return 113 } 114 } 115 116 // regions are merged or split, overwrite span into list 117 node := seekRes.Node() 118 delete(s.cachedRegions, node.regionID) 119 lastNodeTs := uint64(math.MaxUint64) 120 lastRegionID := uint64(fakeRegionID) 121 shouldInsertStartNode := true 122 if node.Value() != nil { 123 lastNodeTs = node.Value().key 124 lastRegionID = node.regionID 125 } 126 for ; node != nil; node = node.Next() { 127 delete(s.cachedRegions, node.regionID) 128 cmpStart := bytes.Compare(node.Key(), span.StartKey) 129 if cmpStart < 0 { 130 continue 131 } 132 if bytes.Compare(node.Key(), span.EndKey) > 0 { 133 break 134 } 135 lastNodeTs = node.Value().key 136 lastRegionID = node.regionID 137 if cmpStart == 0 { 138 s.minTsHeap.UpdateKey(node.Value(), ts) 139 node.regionID = regionID 140 shouldInsertStartNode = false 141 } else { 142 s.spanList.Remove(seekRes, node) 143 s.minTsHeap.Remove(node.Value()) 144 } 145 } 146 if shouldInsertStartNode { 147 s.spanList.InsertNextToNode(seekRes, span.StartKey, s.minTsHeap.Insert(ts), regionID) 148 seekRes.Next() 149 } 150 s.spanList.InsertNextToNode(seekRes, span.EndKey, s.minTsHeap.Insert(lastNodeTs), lastRegionID) 151 } 152 153 // Entries visit all traced spans. 154 func (s *spanFrontier) Entries(fn func(key []byte, ts uint64)) { 155 s.spanList.Entries(func(n *skipListNode) bool { 156 fn(n.Key(), n.Value().key) 157 return true 158 }) 159 } 160 161 func (s *spanFrontier) String() string { 162 var buf strings.Builder 163 s.Entries(func(key []byte, ts uint64) { 164 if ts == math.MaxUint64 { 165 buf.WriteString(fmt.Sprintf("[%s @ Max] ", key)) 166 } else { 167 buf.WriteString(fmt.Sprintf("[%s @ %d] ", key, ts)) 168 } 169 }) 170 return buf.String() 171 } 172 173 func (s *spanFrontier) stringWtihRegionID() string { 174 var buf strings.Builder 175 s.spanList.Entries(func(n *skipListNode) bool { 176 if n.Value().key == math.MaxUint64 { 177 buf.WriteString(fmt.Sprintf("[%d:%s @ Max] ", n.regionID, hex.EncodeToString(n.Key()))) 178 } else { // the next span 179 buf.WriteString(fmt.Sprintf("[%d:%s @ %d] ", n.regionID, hex.EncodeToString(n.Key()), n.Value().key)) 180 } 181 return true 182 }) 183 return buf.String() 184 } 185 186 // SpanString returns the string of the span's frontier. 187 func (s *spanFrontier) SpanString(span tablepb.Span) string { 188 var buf strings.Builder 189 idx := 0 190 s.spanList.Entries(func(n *skipListNode) bool { 191 key := n.Key() 192 nextKey := []byte{} 193 if n.Next() != nil { 194 nextKey = n.Next().Key() 195 } 196 if n.Value().key == math.MaxUint64 { 197 buf.WriteString(fmt.Sprintf("[%d:%s @ Max] ", n.regionID, hex.EncodeToString(n.Key()))) 198 } else if idx == 0 || // head 199 bytes.Equal(key, span.StartKey) || // start key sapn 200 bytes.Equal(nextKey, span.StartKey) || // the previous sapn of start key 201 bytes.Equal(key, span.EndKey) { // the end key span 202 buf.WriteString(fmt.Sprintf("[%d:%s @ %d] ", n.regionID, 203 hex.EncodeToString(n.Key()), n.Value().key)) 204 } 205 idx++ 206 return true 207 }) 208 return buf.String() 209 }