github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/kv/regionlock/range_ts_map.go (about)

     1  // Copyright 2024 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 regionlock
    15  
    16  import (
    17  	"bytes"
    18  	"math"
    19  
    20  	"github.com/google/btree"
    21  	"github.com/pingcap/log"
    22  )
    23  
    24  // rangeTsMap represents a map from key range to a timestamp. It supports
    25  // range set and calculating min value among a specified range.
    26  type rangeTsMap struct {
    27  	m     *btree.BTreeG[rangeTsEntry]
    28  	start []byte
    29  	end   []byte
    30  }
    31  
    32  // newRangeTsMap creates a RangeTsMap.
    33  func newRangeTsMap(startKey, endKey []byte, startTs uint64) *rangeTsMap {
    34  	m := &rangeTsMap{
    35  		m:     btree.NewG(16, rangeTsEntryLess),
    36  		start: startKey,
    37  		end:   endKey,
    38  	}
    39  	m.set(startKey, endKey, startTs)
    40  	return m
    41  }
    42  
    43  func (m *rangeTsMap) clone() (res *rangeTsMap) {
    44  	res = &rangeTsMap{
    45  		m:     btree.NewG(16, rangeTsEntryLess),
    46  		start: m.start,
    47  		end:   m.end,
    48  	}
    49  	m.m.Ascend(func(i rangeTsEntry) bool {
    50  		res.m.ReplaceOrInsert(i)
    51  		return true
    52  	})
    53  	return
    54  }
    55  
    56  // set the timestamp of range [startKey, endKey) to ts.
    57  // RangeLock uses rangeTsMap to store unsubscribed regions.
    58  // So `set` should be called after a region gets unlocked from RangeLock.
    59  // Note: It leaves the timestamp of range [endKey, +∞) intact.
    60  func (m *rangeTsMap) set(startKey, endKey []byte, ts uint64) {
    61  	startEntry := rangeTsEntryWithKey(startKey)
    62  	endEntry := rangeTsEntryWithKey(endKey)
    63  
    64  	// Check and update the range overlapped with endKey is it exists.
    65  	if !bytes.Equal(m.end, endKey) && !m.m.Has(endEntry) {
    66  		var found bool
    67  		var endKeyOverlapped rangeTsEntry
    68  		m.m.DescendLessOrEqual(endEntry, func(i rangeTsEntry) bool {
    69  			found = true
    70  			endKeyOverlapped.ts = i.ts
    71  			endKeyOverlapped.isSet = i.isSet
    72  			return false
    73  		})
    74  		if found {
    75  			if endKeyOverlapped.isSet {
    76  				log.Panic("rangeTsMap double set")
    77  			}
    78  			endKeyOverlapped.startKey = endKey
    79  			m.m.ReplaceOrInsert(endKeyOverlapped)
    80  		}
    81  	}
    82  
    83  	// Check and update the range overlapped with startKey if it exists.
    84  	if !m.m.Has(startEntry) {
    85  		var found bool
    86  		var startKeyOverlapped rangeTsEntry
    87  		m.m.DescendLessOrEqual(startEntry, func(i rangeTsEntry) bool {
    88  			found = true
    89  			startKeyOverlapped.isSet = i.isSet
    90  			return false
    91  		})
    92  		if found && startKeyOverlapped.isSet {
    93  			log.Panic("rangeTsMap double set")
    94  		}
    95  	}
    96  
    97  	// Check and delete all covered ranges.
    98  	entriesToDelete := make([]rangeTsEntry, 0)
    99  	m.m.AscendRange(startEntry, endEntry, func(i rangeTsEntry) bool {
   100  		if i.isSet {
   101  			log.Panic("rangeTsMap double set")
   102  		}
   103  		entriesToDelete = append(entriesToDelete, i)
   104  		return true
   105  	})
   106  	for _, e := range entriesToDelete {
   107  		m.m.Delete(e)
   108  	}
   109  
   110  	m.m.ReplaceOrInsert(rangeTsEntry{startKey: startKey, ts: ts, isSet: true})
   111  }
   112  
   113  // RangeLock uses rangeTsMap to store unsubscribed regions.
   114  // So `unset` should be called after a region gets locked in RangeLock.
   115  func (m *rangeTsMap) unset(startKey, endKey []byte) {
   116  	var neighbor rangeTsEntry
   117  	var exist bool
   118  	startEntry := rangeTsEntryWithKey(startKey)
   119  	endEntry := rangeTsEntryWithKey(endKey)
   120  
   121  	// Check and update the range overlapped with endKey is it exists.
   122  	if !bytes.Equal(m.end, endKey) {
   123  		if neighbor, exist = m.m.Get(endEntry); !exist {
   124  			var found bool
   125  			var endKeyOverlapped rangeTsEntry
   126  			m.m.DescendLessOrEqual(endEntry, func(i rangeTsEntry) bool {
   127  				found = true
   128  				endKeyOverlapped.ts = i.ts
   129  				endKeyOverlapped.isSet = i.isSet
   130  				return false
   131  			})
   132  			if found {
   133  				if !endKeyOverlapped.isSet {
   134  					log.Panic("rangeTsMap double unset")
   135  				}
   136  				endKeyOverlapped.startKey = endKey
   137  				m.m.ReplaceOrInsert(endKeyOverlapped)
   138  			}
   139  		} else if !neighbor.isSet {
   140  			m.m.Delete(neighbor)
   141  		}
   142  	}
   143  
   144  	// Check and update the range overlapped with startKey if it exists.
   145  	exist = false
   146  	m.m.DescendLessOrEqual(startEntry, func(i rangeTsEntry) bool {
   147  		if bytes.Compare(i.startKey, startKey) < 0 {
   148  			neighbor = i
   149  			exist = true
   150  			return false
   151  		}
   152  		return true
   153  	})
   154  	shouldInsert := !exist || neighbor.isSet
   155  
   156  	// Check and delete all covered ranges.
   157  	entriesToDelete := make([]rangeTsEntry, 0)
   158  	m.m.AscendRange(startEntry, endEntry, func(i rangeTsEntry) bool {
   159  		if !i.isSet {
   160  			log.Panic("rangeTsMap double unset")
   161  		}
   162  		entriesToDelete = append(entriesToDelete, i)
   163  		return true
   164  	})
   165  	for _, e := range entriesToDelete {
   166  		m.m.Delete(e)
   167  	}
   168  
   169  	if shouldInsert {
   170  		m.m.ReplaceOrInsert(rangeTsEntry{startKey: startKey, isSet: false})
   171  	}
   172  }
   173  
   174  func (m *rangeTsMap) getMinTsInRange(startKey, endKey []byte) uint64 {
   175  	var ts uint64 = math.MaxUint64
   176  
   177  	startEntry := rangeTsEntryWithKey(startKey)
   178  	if _, ok := m.m.Get(startEntry); !ok {
   179  		m.m.DescendLessOrEqual(startEntry, func(i rangeTsEntry) bool {
   180  			if !i.isSet {
   181  				log.Panic("rangeTsMap get after unset")
   182  			}
   183  			ts = i.ts
   184  			return false
   185  		})
   186  	}
   187  
   188  	endEntry := rangeTsEntryWithKey(endKey)
   189  	m.m.AscendRange(startEntry, endEntry, func(i rangeTsEntry) bool {
   190  		if !i.isSet {
   191  			log.Panic("rangeTsMap get after unset")
   192  		}
   193  		if ts > i.ts {
   194  			ts = i.ts
   195  		}
   196  		return true
   197  	})
   198  
   199  	return ts
   200  }
   201  
   202  func (m *rangeTsMap) getMinTs() uint64 {
   203  	var ts uint64 = math.MaxUint64
   204  
   205  	m.m.Ascend(func(i rangeTsEntry) bool {
   206  		if i.isSet && ts > i.ts {
   207  			ts = i.ts
   208  		}
   209  		return true
   210  	})
   211  
   212  	return ts
   213  }
   214  
   215  // rangeTsEntry is the entry of rangeTsMap.
   216  type rangeTsEntry struct {
   217  	// Only startKey is necessary. End key can be inferred by the next item,
   218  	// since the map always keeps a continuous range.
   219  	startKey []byte
   220  	ts       uint64
   221  
   222  	// rangeTsEntry is used in rangeTsMap. rangeTsMap.set will associate a timestamp to a given key range,
   223  	// and rangeTsMap.unset will revoke the relationship. `isSet` indicates whether a key range is
   224  	// generated from a set or unset operation.
   225  	isSet bool
   226  }
   227  
   228  func rangeTsEntryWithKey(key []byte) rangeTsEntry {
   229  	return rangeTsEntry{startKey: key}
   230  }
   231  
   232  func rangeTsEntryLess(a, b rangeTsEntry) bool {
   233  	return bytes.Compare(a.startKey, b.startKey) < 0
   234  }