220 lines
5.6 KiB
Go
220 lines
5.6 KiB
Go
// Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0.
|
|
|
|
package rtree
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/google/btree"
|
|
backuppb "github.com/pingcap/kvproto/pkg/brpb"
|
|
"github.com/pingcap/log"
|
|
"github.com/pingcap/tidb/br/pkg/logutil"
|
|
)
|
|
|
|
// Range represents a backup response.
|
|
type Range struct {
|
|
StartKey []byte
|
|
EndKey []byte
|
|
Files []*backuppb.File
|
|
}
|
|
|
|
// BytesAndKeys returns total bytes and keys in a range.
|
|
func (rg *Range) BytesAndKeys() (bytes, keys uint64) {
|
|
for _, f := range rg.Files {
|
|
bytes += f.TotalBytes
|
|
keys += f.TotalKvs
|
|
}
|
|
return
|
|
}
|
|
|
|
// Intersect returns intersect range in the tree.
|
|
func (rg *Range) Intersect(
|
|
start, end []byte,
|
|
) (subStart, subEnd []byte, isIntersect bool) {
|
|
// empty mean the max end key
|
|
if len(rg.EndKey) != 0 && bytes.Compare(start, rg.EndKey) >= 0 {
|
|
isIntersect = false
|
|
return
|
|
}
|
|
if len(end) != 0 && bytes.Compare(end, rg.StartKey) <= 0 {
|
|
isIntersect = false
|
|
return
|
|
}
|
|
isIntersect = true
|
|
if bytes.Compare(start, rg.StartKey) >= 0 {
|
|
subStart = start
|
|
} else {
|
|
subStart = rg.StartKey
|
|
}
|
|
switch {
|
|
case len(end) == 0:
|
|
subEnd = rg.EndKey
|
|
case len(rg.EndKey) == 0:
|
|
subEnd = end
|
|
case bytes.Compare(end, rg.EndKey) < 0:
|
|
subEnd = end
|
|
default:
|
|
subEnd = rg.EndKey
|
|
}
|
|
return
|
|
}
|
|
|
|
// Contains check if the range contains the given key, [start, end).
|
|
func (rg *Range) Contains(key []byte) bool {
|
|
start, end := rg.StartKey, rg.EndKey
|
|
return bytes.Compare(key, start) >= 0 &&
|
|
(len(end) == 0 || bytes.Compare(key, end) < 0)
|
|
}
|
|
|
|
// Less impls btree.Item.
|
|
func (rg *Range) Less(than btree.Item) bool {
|
|
// rg.StartKey < than.StartKey
|
|
ta := than.(*Range)
|
|
return bytes.Compare(rg.StartKey, ta.StartKey) < 0
|
|
}
|
|
|
|
var _ btree.Item = &Range{}
|
|
|
|
// RangeTree is sorted tree for Ranges.
|
|
// All the ranges it stored do not overlap.
|
|
type RangeTree struct {
|
|
*btree.BTree
|
|
}
|
|
|
|
// NewRangeTree returns an empty range tree.
|
|
func NewRangeTree() RangeTree {
|
|
return RangeTree{
|
|
BTree: btree.New(32),
|
|
}
|
|
}
|
|
|
|
// Find is a helper function to find an item that contains the range start
|
|
// key.
|
|
func (rangeTree *RangeTree) Find(rg *Range) *Range {
|
|
var ret *Range
|
|
rangeTree.DescendLessOrEqual(rg, func(i btree.Item) bool {
|
|
ret = i.(*Range)
|
|
return false
|
|
})
|
|
|
|
if ret == nil || !ret.Contains(rg.StartKey) {
|
|
return nil
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// getOverlaps gets the ranges which are overlapped with the specified range range.
|
|
func (rangeTree *RangeTree) getOverlaps(rg *Range) []*Range {
|
|
// note that find() gets the last item that is less or equal than the range.
|
|
// in the case: |_______a_______|_____b_____|___c___|
|
|
// new range is |______d______|
|
|
// find() will return Range of range_a
|
|
// and both startKey of range_a and range_b are less than endKey of range_d,
|
|
// thus they are regarded as overlapped ranges.
|
|
found := rangeTree.Find(rg)
|
|
if found == nil {
|
|
found = rg
|
|
}
|
|
|
|
var overlaps []*Range
|
|
rangeTree.AscendGreaterOrEqual(found, func(i btree.Item) bool {
|
|
over := i.(*Range)
|
|
if len(rg.EndKey) > 0 && bytes.Compare(rg.EndKey, over.StartKey) <= 0 {
|
|
return false
|
|
}
|
|
overlaps = append(overlaps, over)
|
|
return true
|
|
})
|
|
return overlaps
|
|
}
|
|
|
|
// Update inserts range into tree and delete overlapping ranges.
|
|
func (rangeTree *RangeTree) Update(rg Range) {
|
|
overlaps := rangeTree.getOverlaps(&rg)
|
|
// Range has backuped, overwrite overlapping range.
|
|
for _, item := range overlaps {
|
|
log.Info("delete overlapping range",
|
|
logutil.Key("startKey", item.StartKey),
|
|
logutil.Key("endKey", item.EndKey))
|
|
rangeTree.Delete(item)
|
|
}
|
|
rangeTree.ReplaceOrInsert(&rg)
|
|
}
|
|
|
|
// Put forms a range and inserts it into tree.
|
|
func (rangeTree *RangeTree) Put(
|
|
startKey, endKey []byte, files []*backuppb.File,
|
|
) {
|
|
rg := Range{
|
|
StartKey: startKey,
|
|
EndKey: endKey,
|
|
Files: files,
|
|
}
|
|
rangeTree.Update(rg)
|
|
}
|
|
|
|
// InsertRange inserts ranges into the range tree.
|
|
// It returns a non-nil range if there are soe overlapped ranges.
|
|
func (rangeTree *RangeTree) InsertRange(rg Range) *Range {
|
|
out := rangeTree.ReplaceOrInsert(&rg)
|
|
if out == nil {
|
|
return nil
|
|
}
|
|
return out.(*Range)
|
|
}
|
|
|
|
// GetSortedRanges collects and returns sorted ranges.
|
|
func (rangeTree *RangeTree) GetSortedRanges() []Range {
|
|
sortedRanges := make([]Range, 0, rangeTree.Len())
|
|
rangeTree.Ascend(func(rg btree.Item) bool {
|
|
if rg == nil {
|
|
return false
|
|
}
|
|
sortedRanges = append(sortedRanges, *rg.(*Range))
|
|
return true
|
|
})
|
|
return sortedRanges
|
|
}
|
|
|
|
// GetIncompleteRange returns missing range covered by startKey and endKey.
|
|
func (rangeTree *RangeTree) GetIncompleteRange(
|
|
startKey, endKey []byte,
|
|
) []Range {
|
|
if len(startKey) != 0 && bytes.Equal(startKey, endKey) {
|
|
return []Range{}
|
|
}
|
|
incomplete := make([]Range, 0, 64)
|
|
requsetRange := Range{StartKey: startKey, EndKey: endKey}
|
|
lastEndKey := startKey
|
|
pviot := &Range{StartKey: startKey}
|
|
if first := rangeTree.Find(pviot); first != nil {
|
|
pviot.StartKey = first.StartKey
|
|
}
|
|
rangeTree.AscendGreaterOrEqual(pviot, func(i btree.Item) bool {
|
|
rg := i.(*Range)
|
|
if bytes.Compare(lastEndKey, rg.StartKey) < 0 {
|
|
start, end, isIntersect :=
|
|
requsetRange.Intersect(lastEndKey, rg.StartKey)
|
|
if isIntersect {
|
|
// There is a gap between the last item and the current item.
|
|
incomplete =
|
|
append(incomplete, Range{StartKey: start, EndKey: end})
|
|
}
|
|
}
|
|
lastEndKey = rg.EndKey
|
|
return len(endKey) == 0 || bytes.Compare(rg.EndKey, endKey) < 0
|
|
})
|
|
|
|
// Check whether we need append the last range
|
|
if !bytes.Equal(lastEndKey, endKey) && len(lastEndKey) != 0 &&
|
|
(len(endKey) == 0 || bytes.Compare(lastEndKey, endKey) < 0) {
|
|
start, end, isIntersect := requsetRange.Intersect(lastEndKey, endKey)
|
|
if isIntersect {
|
|
incomplete =
|
|
append(incomplete, Range{StartKey: start, EndKey: end})
|
|
}
|
|
}
|
|
return incomplete
|
|
}
|