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  }