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  }