From d92fe84f3e79ae75f6ad6ac016f3dc65ba816a3e Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Thu, 12 May 2022 10:09:12 +0800 Subject: [PATCH 01/13] bucket tree Signed-off-by: bufferflies <1045931706@qq.com> --- server/statistics/buckets/bucket_tree.go | 97 +++++++++++ .../statistics/buckets/bucket_treee_test.go | 151 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 server/statistics/buckets/bucket_tree.go create mode 100644 server/statistics/buckets/bucket_treee_test.go diff --git a/server/statistics/buckets/bucket_tree.go b/server/statistics/buckets/bucket_tree.go new file mode 100644 index 00000000000..765f10b025b --- /dev/null +++ b/server/statistics/buckets/bucket_tree.go @@ -0,0 +1,97 @@ +// Copyright 2022 TiKV Project Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buckets + +import ( + "bytes" + + "github.com/tikv/pd/pkg/btree" +) + +// Ring is a buffer, the key range must be continuous. +type Ring struct { + tree *btree.BTree +} + +// NewRing creates a new ring buffer. +func NewRing(degree int) *Ring { + return &Ring{ + tree: btree.New(degree), + } +} + +// BucketItem is a ring item. +type BucketItem interface { + Less(than btree.Item) bool + StartKey() []byte + EndKey() []byte + // Debris returns the debris after replacing the key range. + Debris(startKey, endKey []byte) []BucketItem + String() string +} + +// Len returns the length of the ring. +func (r *Ring) Len() int { + return r.tree.Len() +} + +// GetRange returns the items that belong the key range. +// cache key range: |001-----100|100-----200| +// request key range: |005-----120| +// return items: |001-----100|100-----200| +func (r *Ring) GetRange(item BucketItem) []BucketItem { + var res []BucketItem + + var first BucketItem + r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { + first = i.(BucketItem) + return false + }) + + // find the first item that contains the start key. if not found, + // it will use the param. + if first == nil || !(bytes.Compare(first.StartKey(), item.StartKey()) <= 0 && + bytes.Compare(item.StartKey(), first.EndKey()) < 0) { + first = item + } + + // find the next item util the item greater than end key. + r.tree.AscendGreaterOrEqual(first, func(i btree.Item) bool { + ringItem := i.(BucketItem) + if len(item.EndKey()) > 0 && bytes.Compare(ringItem.StartKey(), item.EndKey()) >= 0 { + return false + } + res = append(res, ringItem) + return true + }) + return res +} + +// Put puts a new item into the ring. +func (r *Ring) Put(item BucketItem) { + overlaps := r.GetRange(item) + for _, overlap := range overlaps { + r.tree.Delete(overlap) + others := overlap.Debris(item.StartKey(), item.EndKey()) + for _, other := range others { + if bytes.Equal(other.StartKey(), other.EndKey()) { + r.tree.Delete(other) + } else { + r.tree.ReplaceOrInsert(other) + } + } + } + r.tree.ReplaceOrInsert(item) +} diff --git a/server/statistics/buckets/bucket_treee_test.go b/server/statistics/buckets/bucket_treee_test.go new file mode 100644 index 00000000000..93147d008bc --- /dev/null +++ b/server/statistics/buckets/bucket_treee_test.go @@ -0,0 +1,151 @@ +// Copyright 2022 TiKV Project Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buckets + +import ( + "bytes" + "fmt" + "testing" + + . "github.com/pingcap/check" + "github.com/tikv/pd/pkg/btree" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&testBucketSuite{}) + +type testBucketSuite struct { +} + +type simpleBucketItem struct { + startKey []byte + endKey []byte +} + +func newSimpleBucketItem(startKey, endKey []byte) *simpleBucketItem { + return &simpleBucketItem{ + startKey: startKey, + endKey: endKey, + } +} + +// String implements String. +func (s *simpleBucketItem) String() string { + return fmt.Sprintf("key-range: [%s, %s]", s.startKey, s.endKey) +} + +// Less returns true if the start key of the item is less than the start key of the argument. +func (s *simpleBucketItem) Less(than btree.Item) bool { + return bytes.Compare(s.StartKey(), than.(BucketItem).StartKey()) < 0 +} + +// Debris returns the debris of the item. +// details: https://leetcode.cn/problems/interval-list-intersections/ +func (s simpleBucketItem) Debris(startKey, endKey []byte) []BucketItem { + var res []BucketItem + + left := maxKey(startKey, s.startKey) + right := minKey(endKey, s.endKey) + // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. + if bytes.Compare(left, right) >= 0 { + return nil + } + // the left has oen intersection like |010 - 100| and |020 - 100|. + if !bytes.Equal(s.startKey, left) { + res = append(res, newSimpleBucketItem(s.startKey, left)) + } + // the right has oen intersection like |010 - 100| and |010 - 099|. + if !bytes.Equal(right, s.endKey) { + res = append(res, newSimpleBucketItem(right, s.endKey)) + } + return res +} + +// EndKey returns the end key of the item. +func (s *simpleBucketItem) EndKey() []byte { + return s.endKey +} + +// StartKey returns the start key of the item. +func (s *simpleBucketItem) StartKey() []byte { + return s.startKey +} + +func minKey(a, b []byte) []byte { + if bytes.Compare(a, b) < 0 { + return a + } + return b +} + +func maxKey(a, b []byte) []byte { + if bytes.Compare(a, b) > 0 { + return a + } + return b +} + +func (bs *testBucketSuite) TestRingPutItem(c *C) { + ring := NewRing(2) + ring.Put(newSimpleBucketItem([]byte("002"), []byte("100"))) + c.Assert(ring.tree.Len(), Equals, 1) + ring.Put(newSimpleBucketItem([]byte("100"), []byte("200"))) + c.Assert(ring.tree.Len(), Equals, 2) + + // init key range: [002,100], [100,200] + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) + + // test1: insert one key range, the old overlaps will retain like split buckets. + // key range: [002,010],[010,090],[090,100],[100,200] + ring.Put(newSimpleBucketItem([]byte("010"), []byte("090"))) + c.Assert(ring.tree.Len(), Equals, 4) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + + // test2: insert one key range, the old overlaps will retain like merge . + // key range: [001,080], [080,090],[090,100],[100,200] + ring.Put(newSimpleBucketItem([]byte("001"), []byte("080"))) + c.Assert(ring.tree.Len(), Equals, 4) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) + + // test2: insert one keyrange, the old overlaps will retain like merge . + // key range: [001,120],[120,200] + ring.Put(newSimpleBucketItem([]byte("001"), []byte("120"))) + c.Assert(ring.tree.Len(), Equals, 2) + c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) +} + +func (bs *testBucketSuite) TestDebris(c *C) { + ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) + var overlaps []BucketItem + overlaps = ringItem.Debris([]byte("000"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("000"), []byte("080")) + c.Assert(overlaps, HasLen, 1) + overlaps = ringItem.Debris([]byte("020"), []byte("080")) + c.Assert(overlaps, HasLen, 2) + overlaps = ringItem.Debris([]byte("010"), []byte("090")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("010"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("100"), []byte("200")) + c.Assert(overlaps, HasLen, 0) +} From aa783081ac1b78fbf4f52aa20c5b6a2703cfeb09 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Thu, 12 May 2022 11:35:05 +0800 Subject: [PATCH 02/13] ring to bucket tree Signed-off-by: bufferflies <1045931706@qq.com> --- server/statistics/buckets/bucket_tree.go | 26 ++++++------- .../statistics/buckets/bucket_treee_test.go | 38 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/server/statistics/buckets/bucket_tree.go b/server/statistics/buckets/bucket_tree.go index 765f10b025b..03f6cf60f90 100644 --- a/server/statistics/buckets/bucket_tree.go +++ b/server/statistics/buckets/bucket_tree.go @@ -20,19 +20,19 @@ import ( "github.com/tikv/pd/pkg/btree" ) -// Ring is a buffer, the key range must be continuous. -type Ring struct { +// BucketTree is a buffer, the key range must be continuous. +type BucketTree struct { tree *btree.BTree } -// NewRing creates a new ring buffer. -func NewRing(degree int) *Ring { - return &Ring{ +// NewBucketTree creates a new bucket tree. +func NewBucketTree(degree int) *BucketTree { + return &BucketTree{ tree: btree.New(degree), } } -// BucketItem is a ring item. +// BucketItem is bucket tree item. type BucketItem interface { Less(than btree.Item) bool StartKey() []byte @@ -42,16 +42,16 @@ type BucketItem interface { String() string } -// Len returns the length of the ring. -func (r *Ring) Len() int { +// Len returns the length of the bucket tree. +func (r *BucketTree) Len() int { return r.tree.Len() } // GetRange returns the items that belong the key range. -// cache key range: |001-----100|100-----200| -// request key range: |005-----120| +// cache key range: |001-----100|100-----200| +// request key range: |005-----120| // return items: |001-----100|100-----200| -func (r *Ring) GetRange(item BucketItem) []BucketItem { +func (r *BucketTree) GetRange(item BucketItem) []BucketItem { var res []BucketItem var first BucketItem @@ -79,8 +79,8 @@ func (r *Ring) GetRange(item BucketItem) []BucketItem { return res } -// Put puts a new item into the ring. -func (r *Ring) Put(item BucketItem) { +// Put puts a new item into the bucket tree. +func (r *BucketTree) Put(item BucketItem) { overlaps := r.GetRange(item) for _, overlap := range overlaps { r.tree.Delete(overlap) diff --git a/server/statistics/buckets/bucket_treee_test.go b/server/statistics/buckets/bucket_treee_test.go index 93147d008bc..c6482615155 100644 --- a/server/statistics/buckets/bucket_treee_test.go +++ b/server/statistics/buckets/bucket_treee_test.go @@ -101,36 +101,36 @@ func maxKey(a, b []byte) []byte { } func (bs *testBucketSuite) TestRingPutItem(c *C) { - ring := NewRing(2) - ring.Put(newSimpleBucketItem([]byte("002"), []byte("100"))) - c.Assert(ring.tree.Len(), Equals, 1) - ring.Put(newSimpleBucketItem([]byte("100"), []byte("200"))) - c.Assert(ring.tree.Len(), Equals, 2) + bucketTree := NewBucketTree(2) + bucketTree.Put(newSimpleBucketItem([]byte("002"), []byte("100"))) + c.Assert(bucketTree.tree.Len(), Equals, 1) + bucketTree.Put(newSimpleBucketItem([]byte("100"), []byte("200"))) + c.Assert(bucketTree.tree.Len(), Equals, 2) // init key range: [002,100], [100,200] - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) // test1: insert one key range, the old overlaps will retain like split buckets. // key range: [002,010],[010,090],[090,100],[100,200] - ring.Put(newSimpleBucketItem([]byte("010"), []byte("090"))) - c.Assert(ring.tree.Len(), Equals, 4) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + bucketTree.Put(newSimpleBucketItem([]byte("010"), []byte("090"))) + c.Assert(bucketTree.tree.Len(), Equals, 4) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) // test2: insert one key range, the old overlaps will retain like merge . // key range: [001,080], [080,090],[090,100],[100,200] - ring.Put(newSimpleBucketItem([]byte("001"), []byte("080"))) - c.Assert(ring.tree.Len(), Equals, 4) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) + bucketTree.Put(newSimpleBucketItem([]byte("001"), []byte("080"))) + c.Assert(bucketTree.tree.Len(), Equals, 4) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) // test2: insert one keyrange, the old overlaps will retain like merge . // key range: [001,120],[120,200] - ring.Put(newSimpleBucketItem([]byte("001"), []byte("120"))) - c.Assert(ring.tree.Len(), Equals, 2) - c.Assert(ring.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + bucketTree.Put(newSimpleBucketItem([]byte("001"), []byte("120"))) + c.Assert(bucketTree.tree.Len(), Equals, 2) + c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) } func (bs *testBucketSuite) TestDebris(c *C) { From 31fdfe1a4ed05cf7af5650d448676385550b884b Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 16:37:02 +0800 Subject: [PATCH 03/13] exract rgion tree Signed-off-by: bufferflies <1045931706@qq.com> --- server/core/region_tree.go | 219 ++++++++++++++++++++++++++++--------- 1 file changed, 165 insertions(+), 54 deletions(-) diff --git a/server/core/region_tree.go b/server/core/region_tree.go index c0f26d41ba0..2dad8378460 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -26,12 +26,144 @@ import ( "go.uber.org/zap" ) +// RegionItem is region tree item. +type RegionItem interface { + btree.Item + GetStartKey() []byte + GetEndKey() []byte + // Debris returns the debris after replacing the key range. + Debris(startKey, endKey []byte) []RegionItem +} + +type RegionTree struct { + tree *btree.BTree +} + +func NewRegionTree(degree int) *RegionTree { + return &RegionTree{ + tree: btree.New(degree), + } +} + var _ btree.Item = ®ionItem{} type regionItem struct { region *RegionInfo } +func (r *regionItem) GetStartKey() []byte { + return r.region.GetStartKey() +} + +func (r *regionItem) GetEndKey() []byte { + return r.region.GetEndKey() +} + +func (r *regionItem) Debris(_, _ []byte) []RegionItem { + return nil +} + +func (r *RegionTree) Update(item RegionItem) []RegionItem { + overlaps := r.GetOverlaps(item) + for _, old := range overlaps { + r.tree.Delete(old) + deris := old.Debris(item.GetStartKey(), item.GetEndKey()) + for _, child := range deris { + r.tree.ReplaceOrInsert(child) + } + } + r.tree.ReplaceOrInsert(item) + return overlaps +} + +func (r *RegionTree) GetOverlaps(item RegionItem) []RegionItem { + // note that find() gets the last item that is less or equal than the region. + // in the case: |_______a_______|_____b_____|___c___| + // new region is |______d______| + // find() will return regionItem of region_a + // and both startKey of region_a and region_b are less than endKey of region_d, + // thus they are regarded as overlapped regions. + result := r.find(item) + if result == nil { + result = item + } + + var overlaps []RegionItem + r.tree.AscendGreaterOrEqual(result, func(i btree.Item) bool { + over := i.(RegionItem) + if len(item.GetEndKey()) > 0 && bytes.Compare(item.GetEndKey(), over.GetStartKey()) <= 0 { + return false + } + overlaps = append(overlaps, over) + return true + }) + return overlaps +} + +func (r *RegionTree) find(item RegionItem) RegionItem { + var result *regionItem + r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { + result = i.(*regionItem) + return false + }) + + if result == nil || !result.Contains(item.GetStartKey()) { + return nil + } + + return result +} + +func (r *RegionTree) remove(item RegionItem) RegionItem { + return r.tree.Delete(item).(RegionItem) +} + +func (r *RegionTree) Len() int { + return r.tree.Len() +} + +func (r *RegionTree) scanRange(region RegionItem, f func(item2 RegionItem) bool) { + // find if there is a region with key range [s, d), s < startKey < d + startItem := r.find(region) + if startItem == nil { + startItem = region + } + r.tree.AscendGreaterOrEqual(startItem, func(item btree.Item) bool { + return f(item.(RegionItem)) + }) +} + +func (r *RegionTree) getAdjacentRegions(item RegionItem) (RegionItem, RegionItem) { + var prev, next RegionItem + r.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { + if bytes.Equal(item.GetStartKey(), i.(RegionItem).GetStartKey()) { + return true + } + next = i.(*regionItem) + return false + }) + r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { + if bytes.Equal(item.GetStartKey(), i.(RegionItem).GetStartKey()) { + return true + } + prev = i.(RegionItem) + return false + }) + return prev, next +} + +func (r *RegionTree) GetAt(index int) RegionItem { + return r.tree.GetAt(index).(RegionItem) +} + +func (r *RegionTree) GetWithIndex(item RegionItem) (RegionItem, int) { + rst, index := r.tree.GetWithIndex(item) + if rst == nil { + return nil, index + } + return rst.(RegionItem), index +} + // Less returns true if the region start key is less than the other. func (r *regionItem) Less(other btree.Item) bool { left := r.region.GetStartKey() @@ -49,7 +181,7 @@ const ( ) type regionTree struct { - tree *btree.BTree + tree *RegionTree // Statistics totalSize int64 totalWriteBytesRate float64 @@ -58,7 +190,7 @@ type regionTree struct { func newRegionTree() *regionTree { return ®ionTree{ - tree: btree.New(defaultBTreeDegree), + tree: NewRegionTree(defaultBTreeDegree), totalSize: 0, totalWriteBytesRate: 0, totalWriteKeysRate: 0, @@ -82,20 +214,11 @@ func (t *regionTree) getOverlaps(region *RegionInfo) []*RegionInfo { // find() will return regionItem of region_a // and both startKey of region_a and region_b are less than endKey of region_d, // thus they are regarded as overlapped regions. - result := t.find(region) - if result == nil { - result = item + result := t.tree.GetOverlaps(item) + overlaps := make([]*RegionInfo, len(result)) + for i, r := range result { + overlaps[i] = r.(*regionItem).region } - - var overlaps []*RegionInfo - t.tree.AscendGreaterOrEqual(result, func(i btree.Item) bool { - over := i.(*regionItem) - if len(region.GetEndKey()) > 0 && bytes.Compare(region.GetEndKey(), over.region.GetStartKey()) <= 0 { - return false - } - overlaps = append(overlaps, over.region) - return true - }) return overlaps } @@ -109,21 +232,22 @@ func (t *regionTree) update(item *regionItem) []*RegionInfo { t.totalWriteBytesRate += regionWriteBytesRate t.totalWriteKeysRate += regionWriteKeysRate - overlaps := t.getOverlaps(region) - for _, old := range overlaps { + overlaps := t.tree.Update(item) + result := make([]*RegionInfo, len(overlaps)) + for i, overlap := range overlaps { + old := overlap.(*regionItem).region + result[i] = old log.Debug("overlapping region", zap.Uint64("region-id", old.GetID()), logutil.ZapRedactStringer("delete-region", RegionToHexMeta(old.GetMeta())), logutil.ZapRedactStringer("update-region", RegionToHexMeta(region.GetMeta()))) - t.tree.Delete(®ionItem{old}) t.totalSize -= old.approximateSize regionWriteBytesRate, regionWriteKeysRate = old.GetWriteRate() t.totalWriteBytesRate -= regionWriteBytesRate t.totalWriteKeysRate -= regionWriteKeysRate } - t.tree.ReplaceOrInsert(item) - return overlaps + return result } // updateStat is used to update statistics when regionItem.region is directly replaced. @@ -146,8 +270,9 @@ func (t *regionTree) remove(region *RegionInfo) { if t.length() == 0 { return } - result := t.find(region) - if result == nil || result.region.GetID() != region.GetID() { + item := ®ionItem{region: region} + result := t.tree.find(item) + if result == nil || result.(*regionItem).region.GetID() != region.GetID() { return } @@ -155,7 +280,7 @@ func (t *regionTree) remove(region *RegionInfo) { regionWriteBytesRate, regionWriteKeysRate := region.GetWriteRate() t.totalWriteBytesRate -= regionWriteBytesRate t.totalWriteKeysRate -= regionWriteKeysRate - t.tree.Delete(result) + t.tree.remove(result) } // search returns a region that contains the key. @@ -188,19 +313,14 @@ func (t *regionTree) searchPrev(regionKey []byte) *RegionInfo { // find is a helper function to find an item that contains the regions start // key. func (t *regionTree) find(region *RegionInfo) *regionItem { - item := ®ionItem{region: region} - - var result *regionItem - t.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - result = i.(*regionItem) - return false - }) - - if result == nil || !result.Contains(region.GetStartKey()) { + item := t.tree.find(®ionItem{region: region}) + if item == nil { return nil } - - return result + if result, ok := item.(*regionItem); ok && result.Contains(region.GetStartKey()) { + return result + } + return nil } // scanRage scans from the first region containing or behind the start key @@ -208,13 +328,11 @@ func (t *regionTree) find(region *RegionInfo) *regionItem { func (t *regionTree) scanRange(startKey []byte, f func(*RegionInfo) bool) { region := &RegionInfo{meta: &metapb.Region{StartKey: startKey}} // find if there is a region with key range [s, d), s < startKey < d - startItem := t.find(region) - if startItem == nil { - startItem = ®ionItem{region: &RegionInfo{meta: &metapb.Region{StartKey: startKey}}} + fn := func(item RegionItem) bool { + r := item.(*regionItem) + return f(r.region) } - t.tree.AscendGreaterOrEqual(startItem, func(item btree.Item) bool { - return f(item.(*regionItem).region) - }) + t.tree.scanRange(®ionItem{region: region}, fn) } func (t *regionTree) scanRanges() []*RegionInfo { @@ -231,21 +349,14 @@ func (t *regionTree) scanRanges() []*RegionInfo { func (t *regionTree) getAdjacentRegions(region *RegionInfo) (*regionItem, *regionItem) { item := ®ionItem{region: &RegionInfo{meta: &metapb.Region{StartKey: region.GetStartKey()}}} + prevItem, nextItem := t.tree.getAdjacentRegions(item) var prev, next *regionItem - t.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { - if bytes.Equal(item.region.GetStartKey(), i.(*regionItem).region.GetStartKey()) { - return true - } - next = i.(*regionItem) - return false - }) - t.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - if bytes.Equal(item.region.GetStartKey(), i.(*regionItem).region.GetStartKey()) { - return true - } - prev = i.(*regionItem) - return false - }) + if prevItem != nil { + prev = prevItem.(*regionItem) + } + if nextItem != nil { + next = nextItem.(*regionItem) + } return prev, next } From bf0f2631b130d6c49f11be67bbc35030410dc1ea Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 16:40:38 +0800 Subject: [PATCH 04/13] remove some region Signed-off-by: bufferflies <1045931706@qq.com> --- server/core/region_tree.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/core/region_tree.go b/server/core/region_tree.go index 2dad8378460..f5c20e54f91 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -67,9 +67,11 @@ func (r *RegionTree) Update(item RegionItem) []RegionItem { overlaps := r.GetOverlaps(item) for _, old := range overlaps { r.tree.Delete(old) - deris := old.Debris(item.GetStartKey(), item.GetEndKey()) - for _, child := range deris { - r.tree.ReplaceOrInsert(child) + debris := old.Debris(item.GetStartKey(), item.GetEndKey()) + for _, child := range debris { + if bytes.Compare(child.GetStartKey(), child.GetEndKey()) < 0 { + r.tree.ReplaceOrInsert(child) + } } } r.tree.ReplaceOrInsert(item) From 4e7a3dc0d7b9d6ac5196d3fa1a21b2875e97cbf2 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 16:51:41 +0800 Subject: [PATCH 05/13] refactor Signed-off-by: bufferflies <1045931706@qq.com> --- server/core/region_tree.go | 11 ++- server/statistics/buckets/bucket_tree.go | 97 ------------------- .../statistics/buckets/bucket_treee_test.go | 59 +++++------ 3 files changed, 38 insertions(+), 129 deletions(-) delete mode 100644 server/statistics/buckets/bucket_tree.go diff --git a/server/core/region_tree.go b/server/core/region_tree.go index f5c20e54f91..0bc3640ed38 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -103,19 +103,24 @@ func (r *RegionTree) GetOverlaps(item RegionItem) []RegionItem { } func (r *RegionTree) find(item RegionItem) RegionItem { - var result *regionItem + var result RegionItem r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - result = i.(*regionItem) + result = i.(RegionItem) return false }) - if result == nil || !result.Contains(item.GetStartKey()) { + if result == nil || !Contains(result, item.GetStartKey()) { return nil } return result } +func Contains(item RegionItem, key []byte) bool { + start, end := item.GetStartKey(), item.GetEndKey() + return bytes.Compare(key, start) >= 0 && (len(end) == 0 || bytes.Compare(key, end) < 0) +} + func (r *RegionTree) remove(item RegionItem) RegionItem { return r.tree.Delete(item).(RegionItem) } diff --git a/server/statistics/buckets/bucket_tree.go b/server/statistics/buckets/bucket_tree.go deleted file mode 100644 index 03f6cf60f90..00000000000 --- a/server/statistics/buckets/bucket_tree.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 TiKV Project Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buckets - -import ( - "bytes" - - "github.com/tikv/pd/pkg/btree" -) - -// BucketTree is a buffer, the key range must be continuous. -type BucketTree struct { - tree *btree.BTree -} - -// NewBucketTree creates a new bucket tree. -func NewBucketTree(degree int) *BucketTree { - return &BucketTree{ - tree: btree.New(degree), - } -} - -// BucketItem is bucket tree item. -type BucketItem interface { - Less(than btree.Item) bool - StartKey() []byte - EndKey() []byte - // Debris returns the debris after replacing the key range. - Debris(startKey, endKey []byte) []BucketItem - String() string -} - -// Len returns the length of the bucket tree. -func (r *BucketTree) Len() int { - return r.tree.Len() -} - -// GetRange returns the items that belong the key range. -// cache key range: |001-----100|100-----200| -// request key range: |005-----120| -// return items: |001-----100|100-----200| -func (r *BucketTree) GetRange(item BucketItem) []BucketItem { - var res []BucketItem - - var first BucketItem - r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - first = i.(BucketItem) - return false - }) - - // find the first item that contains the start key. if not found, - // it will use the param. - if first == nil || !(bytes.Compare(first.StartKey(), item.StartKey()) <= 0 && - bytes.Compare(item.StartKey(), first.EndKey()) < 0) { - first = item - } - - // find the next item util the item greater than end key. - r.tree.AscendGreaterOrEqual(first, func(i btree.Item) bool { - ringItem := i.(BucketItem) - if len(item.EndKey()) > 0 && bytes.Compare(ringItem.StartKey(), item.EndKey()) >= 0 { - return false - } - res = append(res, ringItem) - return true - }) - return res -} - -// Put puts a new item into the bucket tree. -func (r *BucketTree) Put(item BucketItem) { - overlaps := r.GetRange(item) - for _, overlap := range overlaps { - r.tree.Delete(overlap) - others := overlap.Debris(item.StartKey(), item.EndKey()) - for _, other := range others { - if bytes.Equal(other.StartKey(), other.EndKey()) { - r.tree.Delete(other) - } else { - r.tree.ReplaceOrInsert(other) - } - } - } - r.tree.ReplaceOrInsert(item) -} diff --git a/server/statistics/buckets/bucket_treee_test.go b/server/statistics/buckets/bucket_treee_test.go index c6482615155..2630fc79108 100644 --- a/server/statistics/buckets/bucket_treee_test.go +++ b/server/statistics/buckets/bucket_treee_test.go @@ -17,6 +17,7 @@ package buckets import ( "bytes" "fmt" + "github.com/tikv/pd/server/core" "testing" . "github.com/pingcap/check" @@ -51,13 +52,13 @@ func (s *simpleBucketItem) String() string { // Less returns true if the start key of the item is less than the start key of the argument. func (s *simpleBucketItem) Less(than btree.Item) bool { - return bytes.Compare(s.StartKey(), than.(BucketItem).StartKey()) < 0 + return bytes.Compare(s.GetStartKey(), than.(core.RegionItem).GetStartKey()) < 0 } // Debris returns the debris of the item. // details: https://leetcode.cn/problems/interval-list-intersections/ -func (s simpleBucketItem) Debris(startKey, endKey []byte) []BucketItem { - var res []BucketItem +func (s simpleBucketItem) Debris(startKey, endKey []byte) []core.RegionItem { + var res []core.RegionItem left := maxKey(startKey, s.startKey) right := minKey(endKey, s.endKey) @@ -76,16 +77,16 @@ func (s simpleBucketItem) Debris(startKey, endKey []byte) []BucketItem { return res } -// EndKey returns the end key of the item. -func (s *simpleBucketItem) EndKey() []byte { - return s.endKey -} - // StartKey returns the start key of the item. -func (s *simpleBucketItem) StartKey() []byte { +func (s *simpleBucketItem) GetStartKey() []byte { return s.startKey } +// EndKey returns the end key of the item. +func (s *simpleBucketItem) GetEndKey() []byte { + return s.endKey +} + func minKey(a, b []byte) []byte { if bytes.Compare(a, b) < 0 { return a @@ -101,41 +102,41 @@ func maxKey(a, b []byte) []byte { } func (bs *testBucketSuite) TestRingPutItem(c *C) { - bucketTree := NewBucketTree(2) - bucketTree.Put(newSimpleBucketItem([]byte("002"), []byte("100"))) - c.Assert(bucketTree.tree.Len(), Equals, 1) - bucketTree.Put(newSimpleBucketItem([]byte("100"), []byte("200"))) - c.Assert(bucketTree.tree.Len(), Equals, 2) + bucketTree := core.NewRegionTree(2) + bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) + c.Assert(bucketTree.Len(), Equals, 1) + bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) + c.Assert(bucketTree.Len(), Equals, 2) // init key range: [002,100], [100,200] - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) // test1: insert one key range, the old overlaps will retain like split buckets. // key range: [002,010],[010,090],[090,100],[100,200] - bucketTree.Put(newSimpleBucketItem([]byte("010"), []byte("090"))) - c.Assert(bucketTree.tree.Len(), Equals, 4) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + bucketTree.Update(newSimpleBucketItem([]byte("010"), []byte("090"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) // test2: insert one key range, the old overlaps will retain like merge . // key range: [001,080], [080,090],[090,100],[100,200] - bucketTree.Put(newSimpleBucketItem([]byte("001"), []byte("080"))) - c.Assert(bucketTree.tree.Len(), Equals, 4) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("080"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) // test2: insert one keyrange, the old overlaps will retain like merge . // key range: [001,120],[120,200] - bucketTree.Put(newSimpleBucketItem([]byte("001"), []byte("120"))) - c.Assert(bucketTree.tree.Len(), Equals, 2) - c.Assert(bucketTree.GetRange(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("120"))) + c.Assert(bucketTree.Len(), Equals, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) } func (bs *testBucketSuite) TestDebris(c *C) { ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) - var overlaps []BucketItem + var overlaps []core.RegionItem overlaps = ringItem.Debris([]byte("000"), []byte("100")) c.Assert(overlaps, HasLen, 0) overlaps = ringItem.Debris([]byte("000"), []byte("080")) From 511690e5e21e8d4dbc8d37afa7037cbf05535a5e Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 17:00:24 +0800 Subject: [PATCH 06/13] ring Signed-off-by: bufferflies <1045931706@qq.com> --- server/core/region_tree_test.go | 115 +++++++++++++ .../statistics/buckets/bucket_treee_test.go | 152 ------------------ 2 files changed, 115 insertions(+), 152 deletions(-) delete mode 100644 server/statistics/buckets/bucket_treee_test.go diff --git a/server/core/region_tree_test.go b/server/core/region_tree_test.go index 92c26744abf..c6de03c9815 100644 --- a/server/core/region_tree_test.go +++ b/server/core/region_tree_test.go @@ -15,6 +15,7 @@ package core import ( + "bytes" "fmt" "math/rand" "testing" @@ -22,6 +23,7 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" + "github.com/tikv/pd/pkg/btree" ) var _ = Suite(&testRegionSuite{}) @@ -350,6 +352,56 @@ func (s *testRegionSuite) TestRandomRegionDiscontinuous(c *C) { checkRandomRegion(c, tree, []*RegionInfo{regionA, regionB, regionC, regionD}, []KeyRange{NewKeyRange("", "")}) } +func (bs *testRegionSuite) TestRingPutItem(c *C) { + bucketTree := NewRegionTree(2) + bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) + c.Assert(bucketTree.Len(), Equals, 1) + bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) + c.Assert(bucketTree.Len(), Equals, 2) + + // init key range: [002,100], [100,200] + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) + + // test1: insert one key range, the old overlaps will retain like split buckets. + // key range: [002,010],[010,090],[090,100],[100,200] + bucketTree.Update(newSimpleBucketItem([]byte("010"), []byte("090"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + + // test2: insert one key range, the old overlaps will retain like merge . + // key range: [001,080], [080,090],[090,100],[100,200] + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("080"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) + + // test2: insert one keyrange, the old overlaps will retain like merge . + // key range: [001,120],[120,200] + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("120"))) + c.Assert(bucketTree.Len(), Equals, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) +} + +func (bs *testRegionSuite) TestDebris(c *C) { + ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) + var overlaps []RegionItem + overlaps = ringItem.Debris([]byte("000"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("000"), []byte("080")) + c.Assert(overlaps, HasLen, 1) + overlaps = ringItem.Debris([]byte("020"), []byte("080")) + c.Assert(overlaps, HasLen, 2) + overlaps = ringItem.Debris([]byte("010"), []byte("090")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("010"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("100"), []byte("200")) + c.Assert(overlaps, HasLen, 0) +} + func updateNewItem(tree *regionTree, region *RegionInfo) { item := ®ionItem{region: region} tree.update(item) @@ -406,6 +458,69 @@ func mock1MRegionTree() *mockRegionTreeData { return data } +type simpleBucketItem struct { + startKey []byte + endKey []byte +} + +func newSimpleBucketItem(startKey, endKey []byte) *simpleBucketItem { + return &simpleBucketItem{ + startKey: startKey, + endKey: endKey, + } +} + +// Less returns true if the start key of the item is less than the start key of the argument. +func (s *simpleBucketItem) Less(than btree.Item) bool { + return bytes.Compare(s.GetStartKey(), than.(RegionItem).GetStartKey()) < 0 +} + +// StartKey returns the start key of the item. +func (s *simpleBucketItem) GetStartKey() []byte { + return s.startKey +} + +// EndKey returns the end key of the item. +func (s *simpleBucketItem) GetEndKey() []byte { + return s.endKey +} + +func minKey(a, b []byte) []byte { + if bytes.Compare(a, b) < 0 { + return a + } + return b +} + +func maxKey(a, b []byte) []byte { + if bytes.Compare(a, b) > 0 { + return a + } + return b +} + +// Debris returns the debris of the item. +// details: https://leetcode.cn/problems/interval-list-intersections/ +func (s *simpleBucketItem) Debris(startKey, endKey []byte) []RegionItem { + var res []RegionItem + + left := maxKey(startKey, s.startKey) + right := minKey(endKey, s.endKey) + // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. + if bytes.Compare(left, right) >= 0 { + return nil + } + // the left has oen intersection like |010 - 100| and |020 - 100|. + if !bytes.Equal(s.startKey, left) { + res = append(res, newSimpleBucketItem(s.startKey, left)) + } + // the right has oen intersection like |010 - 100| and |010 - 099|. + if !bytes.Equal(right, s.endKey) { + res = append(res, newSimpleBucketItem(right, s.endKey)) + } + return res +} + const MaxCount = 1_000_000 func BenchmarkRegionTreeSequentialInsert(b *testing.B) { diff --git a/server/statistics/buckets/bucket_treee_test.go b/server/statistics/buckets/bucket_treee_test.go deleted file mode 100644 index 2630fc79108..00000000000 --- a/server/statistics/buckets/bucket_treee_test.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2022 TiKV Project Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buckets - -import ( - "bytes" - "fmt" - "github.com/tikv/pd/server/core" - "testing" - - . "github.com/pingcap/check" - "github.com/tikv/pd/pkg/btree" -) - -func Test(t *testing.T) { - TestingT(t) -} - -var _ = Suite(&testBucketSuite{}) - -type testBucketSuite struct { -} - -type simpleBucketItem struct { - startKey []byte - endKey []byte -} - -func newSimpleBucketItem(startKey, endKey []byte) *simpleBucketItem { - return &simpleBucketItem{ - startKey: startKey, - endKey: endKey, - } -} - -// String implements String. -func (s *simpleBucketItem) String() string { - return fmt.Sprintf("key-range: [%s, %s]", s.startKey, s.endKey) -} - -// Less returns true if the start key of the item is less than the start key of the argument. -func (s *simpleBucketItem) Less(than btree.Item) bool { - return bytes.Compare(s.GetStartKey(), than.(core.RegionItem).GetStartKey()) < 0 -} - -// Debris returns the debris of the item. -// details: https://leetcode.cn/problems/interval-list-intersections/ -func (s simpleBucketItem) Debris(startKey, endKey []byte) []core.RegionItem { - var res []core.RegionItem - - left := maxKey(startKey, s.startKey) - right := minKey(endKey, s.endKey) - // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. - if bytes.Compare(left, right) >= 0 { - return nil - } - // the left has oen intersection like |010 - 100| and |020 - 100|. - if !bytes.Equal(s.startKey, left) { - res = append(res, newSimpleBucketItem(s.startKey, left)) - } - // the right has oen intersection like |010 - 100| and |010 - 099|. - if !bytes.Equal(right, s.endKey) { - res = append(res, newSimpleBucketItem(right, s.endKey)) - } - return res -} - -// StartKey returns the start key of the item. -func (s *simpleBucketItem) GetStartKey() []byte { - return s.startKey -} - -// EndKey returns the end key of the item. -func (s *simpleBucketItem) GetEndKey() []byte { - return s.endKey -} - -func minKey(a, b []byte) []byte { - if bytes.Compare(a, b) < 0 { - return a - } - return b -} - -func maxKey(a, b []byte) []byte { - if bytes.Compare(a, b) > 0 { - return a - } - return b -} - -func (bs *testBucketSuite) TestRingPutItem(c *C) { - bucketTree := core.NewRegionTree(2) - bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) - c.Assert(bucketTree.Len(), Equals, 1) - bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) - c.Assert(bucketTree.Len(), Equals, 2) - - // init key range: [002,100], [100,200] - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) - - // test1: insert one key range, the old overlaps will retain like split buckets. - // key range: [002,010],[010,090],[090,100],[100,200] - bucketTree.Update(newSimpleBucketItem([]byte("010"), []byte("090"))) - c.Assert(bucketTree.Len(), Equals, 4) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - - // test2: insert one key range, the old overlaps will retain like merge . - // key range: [001,080], [080,090],[090,100],[100,200] - bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("080"))) - c.Assert(bucketTree.Len(), Equals, 4) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) - - // test2: insert one keyrange, the old overlaps will retain like merge . - // key range: [001,120],[120,200] - bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("120"))) - c.Assert(bucketTree.Len(), Equals, 2) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) -} - -func (bs *testBucketSuite) TestDebris(c *C) { - ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) - var overlaps []core.RegionItem - overlaps = ringItem.Debris([]byte("000"), []byte("100")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("000"), []byte("080")) - c.Assert(overlaps, HasLen, 1) - overlaps = ringItem.Debris([]byte("020"), []byte("080")) - c.Assert(overlaps, HasLen, 2) - overlaps = ringItem.Debris([]byte("010"), []byte("090")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("010"), []byte("100")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("100"), []byte("200")) - c.Assert(overlaps, HasLen, 0) -} From b983e3d97af719fff4f70e9bae86806859ea44b8 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 17:26:42 +0800 Subject: [PATCH 07/13] new range tree Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 138 ++++++++++++++++++++++++++ pkg/rangetree/range_tree_test.go | 131 +++++++++++++++++++++++++ server/core/region_tree.go | 160 +++---------------------------- server/core/region_tree_test.go | 115 ---------------------- 4 files changed, 284 insertions(+), 260 deletions(-) create mode 100644 pkg/rangetree/range_tree.go create mode 100644 pkg/rangetree/range_tree_test.go diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go new file mode 100644 index 00000000000..54fe013dfb6 --- /dev/null +++ b/pkg/rangetree/range_tree.go @@ -0,0 +1,138 @@ +package rangetree + +import ( + "bytes" + + "github.com/tikv/pd/pkg/btree" +) + +// RangeItem is one key range tree item. +type RangeItem interface { + btree.Item + GetStartKey() []byte + GetEndKey() []byte + // Debris returns the debris after replacing the key range. + Debris(startKey, endKey []byte) []RangeItem +} + +// RangeTree is the tree contains RangeItems. +type RangeTree struct { + tree *btree.BTree +} + +// NewRangeTree is the constructor of the range tree. +func NewRangeTree(degree int) *RangeTree { + return &RangeTree{ + tree: btree.New(degree), + } +} + +// Update insert the item and delete overlaps. +func (r *RangeTree) Update(item RangeItem) []RangeItem { + overlaps := r.GetOverlaps(item) + for _, old := range overlaps { + r.tree.Delete(old) + debris := old.Debris(item.GetStartKey(), item.GetEndKey()) + for _, child := range debris { + if bytes.Compare(child.GetStartKey(), child.GetEndKey()) < 0 { + r.tree.ReplaceOrInsert(child) + } + } + } + r.tree.ReplaceOrInsert(item) + return overlaps +} + +// GetOverlaps returns the range items that has some intersections with the given items. +func (r *RangeTree) GetOverlaps(item RangeItem) []RangeItem { + // note that Find() gets the last item that is less or equal than the region. + // in the case: |_______a_______|_____b_____|___c___| + // new region is |______d______| + // Find() will return RangeItem of region_a + // and both startKey of region_a and region_b are less than endKey of region_d, + // thus they are regarded as overlapped regions. + result := r.Find(item) + if result == nil { + result = item + } + + var overlaps []RangeItem + r.tree.AscendGreaterOrEqual(result, func(i btree.Item) bool { + over := i.(RangeItem) + if len(item.GetEndKey()) > 0 && bytes.Compare(item.GetEndKey(), over.GetStartKey()) <= 0 { + return false + } + overlaps = append(overlaps, over) + return true + }) + return overlaps +} + +func (r *RangeTree) Find(item RangeItem) RangeItem { + var result RangeItem + r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { + result = i.(RangeItem) + return false + }) + + if result == nil || !Contains(result, item.GetStartKey()) { + return nil + } + + return result +} + +func Contains(item RangeItem, key []byte) bool { + start, end := item.GetStartKey(), item.GetEndKey() + return bytes.Compare(key, start) >= 0 && (len(end) == 0 || bytes.Compare(key, end) < 0) +} + +func (r *RangeTree) Remove(item RangeItem) RangeItem { + return r.tree.Delete(item).(RangeItem) +} + +func (r *RangeTree) Len() int { + return r.tree.Len() +} + +func (r *RangeTree) ScanRange(region RangeItem, f func(item2 RangeItem) bool) { + // Find if there is a region with key range [s, d), s < startKey < d + startItem := r.Find(region) + if startItem == nil { + startItem = region + } + r.tree.AscendGreaterOrEqual(startItem, func(item btree.Item) bool { + return f(item.(RangeItem)) + }) +} + +func (r *RangeTree) GetAdjacentRegions(item RangeItem) (RangeItem, RangeItem) { + var prev, next RangeItem + r.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { + if bytes.Equal(item.GetStartKey(), i.(RangeItem).GetStartKey()) { + return true + } + next = i.(RangeItem) + return false + }) + r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { + if bytes.Equal(item.GetStartKey(), i.(RangeItem).GetStartKey()) { + return true + } + prev = i.(RangeItem) + return false + }) + return prev, next +} + +func (r *RangeTree) GetAt(index int) RangeItem { + return r.tree.GetAt(index).(RangeItem) +} + +func (r *RangeTree) GetWithIndex(item RangeItem) (RangeItem, int) { + rst, index := r.tree.GetWithIndex(item) + if rst == nil { + return nil, index + } + return rst.(RangeItem), index +} diff --git a/pkg/rangetree/range_tree_test.go b/pkg/rangetree/range_tree_test.go new file mode 100644 index 00000000000..586814a4dba --- /dev/null +++ b/pkg/rangetree/range_tree_test.go @@ -0,0 +1,131 @@ +package rangetree + +import ( + "bytes" + "testing" + + . "github.com/pingcap/check" + "github.com/tikv/pd/pkg/btree" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&testRangeTreeSuite{}) + +type testRangeTreeSuite struct { +} + +type simpleBucketItem struct { + startKey []byte + endKey []byte +} + +func newSimpleBucketItem(startKey, endKey []byte) *simpleBucketItem { + return &simpleBucketItem{ + startKey: startKey, + endKey: endKey, + } +} + +// Less returns true if the start key of the item is less than the start key of the argument. +func (s *simpleBucketItem) Less(than btree.Item) bool { + return bytes.Compare(s.GetStartKey(), than.(RangeItem).GetStartKey()) < 0 +} + +// StartKey returns the start key of the item. +func (s *simpleBucketItem) GetStartKey() []byte { + return s.startKey +} + +// EndKey returns the end key of the item. +func (s *simpleBucketItem) GetEndKey() []byte { + return s.endKey +} + +func minKey(a, b []byte) []byte { + if bytes.Compare(a, b) < 0 { + return a + } + return b +} + +func maxKey(a, b []byte) []byte { + if bytes.Compare(a, b) > 0 { + return a + } + return b +} + +// Debris returns the debris of the item. +// details: https://leetcode.cn/problems/interval-list-intersections/ +func (s *simpleBucketItem) Debris(startKey, endKey []byte) []RangeItem { + var res []RangeItem + + left := maxKey(startKey, s.startKey) + right := minKey(endKey, s.endKey) + // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. + if bytes.Compare(left, right) >= 0 { + return nil + } + // the left has oen intersection like |010 - 100| and |020 - 100|. + if !bytes.Equal(s.startKey, left) { + res = append(res, newSimpleBucketItem(s.startKey, left)) + } + // the right has oen intersection like |010 - 100| and |010 - 099|. + if !bytes.Equal(right, s.endKey) { + res = append(res, newSimpleBucketItem(right, s.endKey)) + } + return res +} + +func (bs *testRangeTreeSuite) TestRingPutItem(c *C) { + bucketTree := NewRangeTree(2) + bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) + c.Assert(bucketTree.Len(), Equals, 1) + bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) + c.Assert(bucketTree.Len(), Equals, 2) + + // init key range: [002,100], [100,200] + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) + + // test1: insert one key range, the old overlaps will retain like split buckets. + // key range: [002,010],[010,090],[090,100],[100,200] + bucketTree.Update(newSimpleBucketItem([]byte("010"), []byte("090"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) + + // test2: insert one key range, the old overlaps will retain like merge . + // key range: [001,080], [080,090],[090,100],[100,200] + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("080"))) + c.Assert(bucketTree.Len(), Equals, 4) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) + + // test2: insert one keyrange, the old overlaps will retain like merge . + // key range: [001,120],[120,200] + bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("120"))) + c.Assert(bucketTree.Len(), Equals, 2) + c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) +} + +func (bs *testRangeTreeSuite) TestDebris(c *C) { + ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) + var overlaps []RangeItem + overlaps = ringItem.Debris([]byte("000"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("000"), []byte("080")) + c.Assert(overlaps, HasLen, 1) + overlaps = ringItem.Debris([]byte("020"), []byte("080")) + c.Assert(overlaps, HasLen, 2) + overlaps = ringItem.Debris([]byte("010"), []byte("090")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("010"), []byte("100")) + c.Assert(overlaps, HasLen, 0) + overlaps = ringItem.Debris([]byte("100"), []byte("200")) + c.Assert(overlaps, HasLen, 0) +} diff --git a/server/core/region_tree.go b/server/core/region_tree.go index 0bc3640ed38..5efac71cdd8 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -23,158 +23,35 @@ import ( "github.com/tikv/pd/pkg/btree" "github.com/tikv/pd/pkg/errs" "github.com/tikv/pd/pkg/logutil" + "github.com/tikv/pd/pkg/rangetree" "go.uber.org/zap" ) -// RegionItem is region tree item. -type RegionItem interface { - btree.Item - GetStartKey() []byte - GetEndKey() []byte - // Debris returns the debris after replacing the key range. - Debris(startKey, endKey []byte) []RegionItem -} - -type RegionTree struct { - tree *btree.BTree -} - -func NewRegionTree(degree int) *RegionTree { - return &RegionTree{ - tree: btree.New(degree), - } -} - -var _ btree.Item = ®ionItem{} +var _ rangetree.RangeItem = ®ionItem{} type regionItem struct { region *RegionInfo } +// GetStartKey returns the start key of the region. func (r *regionItem) GetStartKey() []byte { return r.region.GetStartKey() } +// GetEndKey returns the end key of the region. func (r *regionItem) GetEndKey() []byte { return r.region.GetEndKey() } -func (r *regionItem) Debris(_, _ []byte) []RegionItem { +// Debris returns nil, don't need to save debris. +func (r *regionItem) Debris(_, _ []byte) []rangetree.RangeItem { return nil } -func (r *RegionTree) Update(item RegionItem) []RegionItem { - overlaps := r.GetOverlaps(item) - for _, old := range overlaps { - r.tree.Delete(old) - debris := old.Debris(item.GetStartKey(), item.GetEndKey()) - for _, child := range debris { - if bytes.Compare(child.GetStartKey(), child.GetEndKey()) < 0 { - r.tree.ReplaceOrInsert(child) - } - } - } - r.tree.ReplaceOrInsert(item) - return overlaps -} - -func (r *RegionTree) GetOverlaps(item RegionItem) []RegionItem { - // note that find() gets the last item that is less or equal than the region. - // in the case: |_______a_______|_____b_____|___c___| - // new region is |______d______| - // find() will return regionItem of region_a - // and both startKey of region_a and region_b are less than endKey of region_d, - // thus they are regarded as overlapped regions. - result := r.find(item) - if result == nil { - result = item - } - - var overlaps []RegionItem - r.tree.AscendGreaterOrEqual(result, func(i btree.Item) bool { - over := i.(RegionItem) - if len(item.GetEndKey()) > 0 && bytes.Compare(item.GetEndKey(), over.GetStartKey()) <= 0 { - return false - } - overlaps = append(overlaps, over) - return true - }) - return overlaps -} - -func (r *RegionTree) find(item RegionItem) RegionItem { - var result RegionItem - r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - result = i.(RegionItem) - return false - }) - - if result == nil || !Contains(result, item.GetStartKey()) { - return nil - } - - return result -} - -func Contains(item RegionItem, key []byte) bool { - start, end := item.GetStartKey(), item.GetEndKey() - return bytes.Compare(key, start) >= 0 && (len(end) == 0 || bytes.Compare(key, end) < 0) -} - -func (r *RegionTree) remove(item RegionItem) RegionItem { - return r.tree.Delete(item).(RegionItem) -} - -func (r *RegionTree) Len() int { - return r.tree.Len() -} - -func (r *RegionTree) scanRange(region RegionItem, f func(item2 RegionItem) bool) { - // find if there is a region with key range [s, d), s < startKey < d - startItem := r.find(region) - if startItem == nil { - startItem = region - } - r.tree.AscendGreaterOrEqual(startItem, func(item btree.Item) bool { - return f(item.(RegionItem)) - }) -} - -func (r *RegionTree) getAdjacentRegions(item RegionItem) (RegionItem, RegionItem) { - var prev, next RegionItem - r.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { - if bytes.Equal(item.GetStartKey(), i.(RegionItem).GetStartKey()) { - return true - } - next = i.(*regionItem) - return false - }) - r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { - if bytes.Equal(item.GetStartKey(), i.(RegionItem).GetStartKey()) { - return true - } - prev = i.(RegionItem) - return false - }) - return prev, next -} - -func (r *RegionTree) GetAt(index int) RegionItem { - return r.tree.GetAt(index).(RegionItem) -} - -func (r *RegionTree) GetWithIndex(item RegionItem) (RegionItem, int) { - rst, index := r.tree.GetWithIndex(item) - if rst == nil { - return nil, index - } - return rst.(RegionItem), index -} - // Less returns true if the region start key is less than the other. func (r *regionItem) Less(other btree.Item) bool { left := r.region.GetStartKey() - right := other.(*regionItem).region.GetStartKey() + right := other.(rangetree.RangeItem).GetStartKey() return bytes.Compare(left, right) < 0 } @@ -188,7 +65,7 @@ const ( ) type regionTree struct { - tree *RegionTree + tree *rangetree.RangeTree // Statistics totalSize int64 totalWriteBytesRate float64 @@ -197,7 +74,7 @@ type regionTree struct { func newRegionTree() *regionTree { return ®ionTree{ - tree: NewRegionTree(defaultBTreeDegree), + tree: rangetree.NewRangeTree(defaultBTreeDegree), totalSize: 0, totalWriteBytesRate: 0, totalWriteKeysRate: 0, @@ -214,13 +91,6 @@ func (t *regionTree) length() int { // getOverlaps gets the regions which are overlapped with the specified region range. func (t *regionTree) getOverlaps(region *RegionInfo) []*RegionInfo { item := ®ionItem{region: region} - - // note that find() gets the last item that is less or equal than the region. - // in the case: |_______a_______|_____b_____|___c___| - // new region is |______d______| - // find() will return regionItem of region_a - // and both startKey of region_a and region_b are less than endKey of region_d, - // thus they are regarded as overlapped regions. result := t.tree.GetOverlaps(item) overlaps := make([]*RegionInfo, len(result)) for i, r := range result { @@ -278,7 +148,7 @@ func (t *regionTree) remove(region *RegionInfo) { return } item := ®ionItem{region: region} - result := t.tree.find(item) + result := t.tree.Find(item) if result == nil || result.(*regionItem).region.GetID() != region.GetID() { return } @@ -287,7 +157,7 @@ func (t *regionTree) remove(region *RegionInfo) { regionWriteBytesRate, regionWriteKeysRate := region.GetWriteRate() t.totalWriteBytesRate -= regionWriteBytesRate t.totalWriteKeysRate -= regionWriteKeysRate - t.tree.remove(result) + t.tree.Remove(result) } // search returns a region that contains the key. @@ -320,7 +190,7 @@ func (t *regionTree) searchPrev(regionKey []byte) *RegionInfo { // find is a helper function to find an item that contains the regions start // key. func (t *regionTree) find(region *RegionInfo) *regionItem { - item := t.tree.find(®ionItem{region: region}) + item := t.tree.Find(®ionItem{region: region}) if item == nil { return nil } @@ -335,11 +205,11 @@ func (t *regionTree) find(region *RegionInfo) *regionItem { func (t *regionTree) scanRange(startKey []byte, f func(*RegionInfo) bool) { region := &RegionInfo{meta: &metapb.Region{StartKey: startKey}} // find if there is a region with key range [s, d), s < startKey < d - fn := func(item RegionItem) bool { + fn := func(item rangetree.RangeItem) bool { r := item.(*regionItem) return f(r.region) } - t.tree.scanRange(®ionItem{region: region}, fn) + t.tree.ScanRange(®ionItem{region: region}, fn) } func (t *regionTree) scanRanges() []*RegionInfo { @@ -356,7 +226,7 @@ func (t *regionTree) scanRanges() []*RegionInfo { func (t *regionTree) getAdjacentRegions(region *RegionInfo) (*regionItem, *regionItem) { item := ®ionItem{region: &RegionInfo{meta: &metapb.Region{StartKey: region.GetStartKey()}}} - prevItem, nextItem := t.tree.getAdjacentRegions(item) + prevItem, nextItem := t.tree.GetAdjacentRegions(item) var prev, next *regionItem if prevItem != nil { prev = prevItem.(*regionItem) diff --git a/server/core/region_tree_test.go b/server/core/region_tree_test.go index c6de03c9815..92c26744abf 100644 --- a/server/core/region_tree_test.go +++ b/server/core/region_tree_test.go @@ -15,7 +15,6 @@ package core import ( - "bytes" "fmt" "math/rand" "testing" @@ -23,7 +22,6 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - "github.com/tikv/pd/pkg/btree" ) var _ = Suite(&testRegionSuite{}) @@ -352,56 +350,6 @@ func (s *testRegionSuite) TestRandomRegionDiscontinuous(c *C) { checkRandomRegion(c, tree, []*RegionInfo{regionA, regionB, regionC, regionD}, []KeyRange{NewKeyRange("", "")}) } -func (bs *testRegionSuite) TestRingPutItem(c *C) { - bucketTree := NewRegionTree(2) - bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) - c.Assert(bucketTree.Len(), Equals, 1) - bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) - c.Assert(bucketTree.Len(), Equals, 2) - - // init key range: [002,100], [100,200] - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("002"))), HasLen, 0) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("000"), []byte("009"))), HasLen, 1) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("110"))), HasLen, 2) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("200"), []byte("300"))), HasLen, 0) - - // test1: insert one key range, the old overlaps will retain like split buckets. - // key range: [002,010],[010,090],[090,100],[100,200] - bucketTree.Update(newSimpleBucketItem([]byte("010"), []byte("090"))) - c.Assert(bucketTree.Len(), Equals, 4) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) - - // test2: insert one key range, the old overlaps will retain like merge . - // key range: [001,080], [080,090],[090,100],[100,200] - bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("080"))) - c.Assert(bucketTree.Len(), Equals, 4) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 2) - - // test2: insert one keyrange, the old overlaps will retain like merge . - // key range: [001,120],[120,200] - bucketTree.Update(newSimpleBucketItem([]byte("001"), []byte("120"))) - c.Assert(bucketTree.Len(), Equals, 2) - c.Assert(bucketTree.GetOverlaps(newSimpleBucketItem([]byte("010"), []byte("090"))), HasLen, 1) -} - -func (bs *testRegionSuite) TestDebris(c *C) { - ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) - var overlaps []RegionItem - overlaps = ringItem.Debris([]byte("000"), []byte("100")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("000"), []byte("080")) - c.Assert(overlaps, HasLen, 1) - overlaps = ringItem.Debris([]byte("020"), []byte("080")) - c.Assert(overlaps, HasLen, 2) - overlaps = ringItem.Debris([]byte("010"), []byte("090")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("010"), []byte("100")) - c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("100"), []byte("200")) - c.Assert(overlaps, HasLen, 0) -} - func updateNewItem(tree *regionTree, region *RegionInfo) { item := ®ionItem{region: region} tree.update(item) @@ -458,69 +406,6 @@ func mock1MRegionTree() *mockRegionTreeData { return data } -type simpleBucketItem struct { - startKey []byte - endKey []byte -} - -func newSimpleBucketItem(startKey, endKey []byte) *simpleBucketItem { - return &simpleBucketItem{ - startKey: startKey, - endKey: endKey, - } -} - -// Less returns true if the start key of the item is less than the start key of the argument. -func (s *simpleBucketItem) Less(than btree.Item) bool { - return bytes.Compare(s.GetStartKey(), than.(RegionItem).GetStartKey()) < 0 -} - -// StartKey returns the start key of the item. -func (s *simpleBucketItem) GetStartKey() []byte { - return s.startKey -} - -// EndKey returns the end key of the item. -func (s *simpleBucketItem) GetEndKey() []byte { - return s.endKey -} - -func minKey(a, b []byte) []byte { - if bytes.Compare(a, b) < 0 { - return a - } - return b -} - -func maxKey(a, b []byte) []byte { - if bytes.Compare(a, b) > 0 { - return a - } - return b -} - -// Debris returns the debris of the item. -// details: https://leetcode.cn/problems/interval-list-intersections/ -func (s *simpleBucketItem) Debris(startKey, endKey []byte) []RegionItem { - var res []RegionItem - - left := maxKey(startKey, s.startKey) - right := minKey(endKey, s.endKey) - // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. - if bytes.Compare(left, right) >= 0 { - return nil - } - // the left has oen intersection like |010 - 100| and |020 - 100|. - if !bytes.Equal(s.startKey, left) { - res = append(res, newSimpleBucketItem(s.startKey, left)) - } - // the right has oen intersection like |010 - 100| and |010 - 099|. - if !bytes.Equal(right, s.endKey) { - res = append(res, newSimpleBucketItem(right, s.endKey)) - } - return res -} - const MaxCount = 1_000_000 func BenchmarkRegionTreeSequentialInsert(b *testing.B) { From a1359056a04867d30a28b61821c7387cf4d674dc Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 17:36:00 +0800 Subject: [PATCH 08/13] add some comment Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 34 ++++++++++++++++++++-------------- server/core/region_tree.go | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go index 54fe013dfb6..718c0dfa3f6 100644 --- a/pkg/rangetree/range_tree.go +++ b/pkg/rangetree/range_tree.go @@ -32,8 +32,8 @@ func (r *RangeTree) Update(item RangeItem) []RangeItem { overlaps := r.GetOverlaps(item) for _, old := range overlaps { r.tree.Delete(old) - debris := old.Debris(item.GetStartKey(), item.GetEndKey()) - for _, child := range debris { + children := old.Debris(item.GetStartKey(), item.GetEndKey()) + for _, child := range children { if bytes.Compare(child.GetStartKey(), child.GetEndKey()) < 0 { r.tree.ReplaceOrInsert(child) } @@ -45,12 +45,12 @@ func (r *RangeTree) Update(item RangeItem) []RangeItem { // GetOverlaps returns the range items that has some intersections with the given items. func (r *RangeTree) GetOverlaps(item RangeItem) []RangeItem { - // note that Find() gets the last item that is less or equal than the region. + // note that Find() gets the last item that is less or equal than the item. // in the case: |_______a_______|_____b_____|___c___| - // new region is |______d______| - // Find() will return RangeItem of region_a - // and both startKey of region_a and region_b are less than endKey of region_d, - // thus they are regarded as overlapped regions. + // new item is |______d______| + // Find() will return RangeItem of item_a + // and both startKey of item_a and item_b are less than endKey of item_d, + // thus they are regarded as overlapped items. result := r.Find(item) if result == nil { result = item @@ -68,6 +68,7 @@ func (r *RangeTree) GetOverlaps(item RangeItem) []RangeItem { return overlaps } +// Find returns the range item contains the start key. func (r *RangeTree) Find(item RangeItem) RangeItem { var result RangeItem r.tree.DescendLessOrEqual(item, func(i btree.Item) bool { @@ -75,14 +76,14 @@ func (r *RangeTree) Find(item RangeItem) RangeItem { return false }) - if result == nil || !Contains(result, item.GetStartKey()) { + if result == nil || !contains(result, item.GetStartKey()) { return nil } return result } -func Contains(item RangeItem, key []byte) bool { +func contains(item RangeItem, key []byte) bool { start, end := item.GetStartKey(), item.GetEndKey() return bytes.Compare(key, start) >= 0 && (len(end) == 0 || bytes.Compare(key, end) < 0) } @@ -91,22 +92,25 @@ func (r *RangeTree) Remove(item RangeItem) RangeItem { return r.tree.Delete(item).(RangeItem) } +// Len returns the count of the range tree. func (r *RangeTree) Len() int { return r.tree.Len() } -func (r *RangeTree) ScanRange(region RangeItem, f func(item2 RangeItem) bool) { - // Find if there is a region with key range [s, d), s < startKey < d - startItem := r.Find(region) +// ScanRange scan the start item util the result of the function is false. +func (r *RangeTree) ScanRange(start RangeItem, f func(_ RangeItem) bool) { + // Find if there is a item with key range [s, d), s < startKey < d + startItem := r.Find(start) if startItem == nil { - startItem = region + startItem = start } r.tree.AscendGreaterOrEqual(startItem, func(item btree.Item) bool { return f(item.(RangeItem)) }) } -func (r *RangeTree) GetAdjacentRegions(item RangeItem) (RangeItem, RangeItem) { +// GetAdjacentItem returns the adjacent range item. +func (r *RangeTree) GetAdjacentItem(item RangeItem) (RangeItem, RangeItem) { var prev, next RangeItem r.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { if bytes.Equal(item.GetStartKey(), i.(RangeItem).GetStartKey()) { @@ -125,10 +129,12 @@ func (r *RangeTree) GetAdjacentRegions(item RangeItem) (RangeItem, RangeItem) { return prev, next } +// GetAt returns the given index item. func (r *RangeTree) GetAt(index int) RangeItem { return r.tree.GetAt(index).(RangeItem) } +// GetWithIndex returns index and item for the given item. func (r *RangeTree) GetWithIndex(item RangeItem) (RangeItem, int) { rst, index := r.tree.GetWithIndex(item) if rst == nil { diff --git a/server/core/region_tree.go b/server/core/region_tree.go index 5efac71cdd8..166696a1d2f 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -226,7 +226,7 @@ func (t *regionTree) scanRanges() []*RegionInfo { func (t *regionTree) getAdjacentRegions(region *RegionInfo) (*regionItem, *regionItem) { item := ®ionItem{region: &RegionInfo{meta: &metapb.Region{StartKey: region.GetStartKey()}}} - prevItem, nextItem := t.tree.GetAdjacentRegions(item) + prevItem, nextItem := t.tree.GetAdjacentItem(item) var prev, next *regionItem if prevItem != nil { prev = prevItem.(*regionItem) From 6666d0e4ed1c3f7978606949b882ad89cc50cef9 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Fri, 13 May 2022 17:45:40 +0800 Subject: [PATCH 09/13] pass ut Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go index 718c0dfa3f6..a034b991259 100644 --- a/pkg/rangetree/range_tree.go +++ b/pkg/rangetree/range_tree.go @@ -88,6 +88,7 @@ func contains(item RangeItem, key []byte) bool { return bytes.Compare(key, start) >= 0 && (len(end) == 0 || bytes.Compare(key, end) < 0) } +// Remove removes the given item and return the deleted item. func (r *RangeTree) Remove(item RangeItem) RangeItem { return r.tree.Delete(item).(RangeItem) } @@ -110,8 +111,7 @@ func (r *RangeTree) ScanRange(start RangeItem, f func(_ RangeItem) bool) { } // GetAdjacentItem returns the adjacent range item. -func (r *RangeTree) GetAdjacentItem(item RangeItem) (RangeItem, RangeItem) { - var prev, next RangeItem +func (r *RangeTree) GetAdjacentItem(item RangeItem) (prev RangeItem, next RangeItem) { r.tree.AscendGreaterOrEqual(item, func(i btree.Item) bool { if bytes.Equal(item.GetStartKey(), i.(RangeItem).GetStartKey()) { return true From a3711ca7527012bc43dc5ba068f102b882dec67b Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Sun, 15 May 2022 10:48:50 +0800 Subject: [PATCH 10/13] add license Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 22 +++++++++++++++++++--- pkg/rangetree/range_tree_test.go | 14 ++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go index a034b991259..1f185955c63 100644 --- a/pkg/rangetree/range_tree.go +++ b/pkg/rangetree/range_tree.go @@ -1,8 +1,21 @@ +// Copyright 2022 TiKV Project Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package rangetree import ( "bytes" - "github.com/tikv/pd/pkg/btree" ) @@ -90,7 +103,10 @@ func contains(item RangeItem, key []byte) bool { // Remove removes the given item and return the deleted item. func (r *RangeTree) Remove(item RangeItem) RangeItem { - return r.tree.Delete(item).(RangeItem) + if r := r.tree.Delete(item); r != nil { + return r.(RangeItem) + } + return nil } // Len returns the count of the range tree. @@ -100,7 +116,7 @@ func (r *RangeTree) Len() int { // ScanRange scan the start item util the result of the function is false. func (r *RangeTree) ScanRange(start RangeItem, f func(_ RangeItem) bool) { - // Find if there is a item with key range [s, d), s < startKey < d + // Find if there is one item with key range [s, d), s < startKey < d startItem := r.Find(start) if startItem == nil { startItem = start diff --git a/pkg/rangetree/range_tree_test.go b/pkg/rangetree/range_tree_test.go index 586814a4dba..06253abb5f2 100644 --- a/pkg/rangetree/range_tree_test.go +++ b/pkg/rangetree/range_tree_test.go @@ -1,3 +1,17 @@ +// Copyright 2022 TiKV Project Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package rangetree import ( From 8aea8e5cea8d5fdd0c04b5b2f7f49a3ddd0c7fa1 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Mon, 16 May 2022 17:03:05 +0800 Subject: [PATCH 11/13] add DebrisFactory Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 14 ++++++++------ pkg/rangetree/range_tree_test.go | 28 ++++++++++++++-------------- server/core/region_tree.go | 5 ++++- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go index 1f185955c63..59ae3e5a26d 100644 --- a/pkg/rangetree/range_tree.go +++ b/pkg/rangetree/range_tree.go @@ -24,19 +24,21 @@ type RangeItem interface { btree.Item GetStartKey() []byte GetEndKey() []byte - // Debris returns the debris after replacing the key range. - Debris(startKey, endKey []byte) []RangeItem } +type DebrisFactory func(startKey, EndKey []byte, item RangeItem) []RangeItem + // RangeTree is the tree contains RangeItems. type RangeTree struct { - tree *btree.BTree + tree *btree.BTree + factory DebrisFactory } // NewRangeTree is the constructor of the range tree. -func NewRangeTree(degree int) *RangeTree { +func NewRangeTree(degree int, factory DebrisFactory) *RangeTree { return &RangeTree{ - tree: btree.New(degree), + tree: btree.New(degree), + factory: factory, } } @@ -45,7 +47,7 @@ func (r *RangeTree) Update(item RangeItem) []RangeItem { overlaps := r.GetOverlaps(item) for _, old := range overlaps { r.tree.Delete(old) - children := old.Debris(item.GetStartKey(), item.GetEndKey()) + children := r.factory(item.GetStartKey(), item.GetEndKey(), old) for _, child := range children { if bytes.Compare(child.GetStartKey(), child.GetEndKey()) < 0 { r.tree.ReplaceOrInsert(child) diff --git a/pkg/rangetree/range_tree_test.go b/pkg/rangetree/range_tree_test.go index 06253abb5f2..d1e9cd79de5 100644 --- a/pkg/rangetree/range_tree_test.go +++ b/pkg/rangetree/range_tree_test.go @@ -74,28 +74,28 @@ func maxKey(a, b []byte) []byte { // Debris returns the debris of the item. // details: https://leetcode.cn/problems/interval-list-intersections/ -func (s *simpleBucketItem) Debris(startKey, endKey []byte) []RangeItem { +func bucketDebrisFactory(startKey, endKey []byte, item RangeItem) []RangeItem { var res []RangeItem - left := maxKey(startKey, s.startKey) - right := minKey(endKey, s.endKey) + left := maxKey(startKey, item.GetStartKey()) + right := minKey(endKey, item.GetEndKey()) // they have no intersection if they are neighbour like |010 - 100| and |100 - 200|. if bytes.Compare(left, right) >= 0 { return nil } // the left has oen intersection like |010 - 100| and |020 - 100|. - if !bytes.Equal(s.startKey, left) { - res = append(res, newSimpleBucketItem(s.startKey, left)) + if !bytes.Equal(item.GetStartKey(), left) { + res = append(res, newSimpleBucketItem(item.GetStartKey(), left)) } // the right has oen intersection like |010 - 100| and |010 - 099|. - if !bytes.Equal(right, s.endKey) { - res = append(res, newSimpleBucketItem(right, s.endKey)) + if !bytes.Equal(right, item.GetEndKey()) { + res = append(res, newSimpleBucketItem(right, item.GetEndKey())) } return res } func (bs *testRangeTreeSuite) TestRingPutItem(c *C) { - bucketTree := NewRangeTree(2) + bucketTree := NewRangeTree(2, bucketDebrisFactory) bucketTree.Update(newSimpleBucketItem([]byte("002"), []byte("100"))) c.Assert(bucketTree.Len(), Equals, 1) bucketTree.Update(newSimpleBucketItem([]byte("100"), []byte("200"))) @@ -130,16 +130,16 @@ func (bs *testRangeTreeSuite) TestRingPutItem(c *C) { func (bs *testRangeTreeSuite) TestDebris(c *C) { ringItem := newSimpleBucketItem([]byte("010"), []byte("090")) var overlaps []RangeItem - overlaps = ringItem.Debris([]byte("000"), []byte("100")) + overlaps = bucketDebrisFactory([]byte("000"), []byte("100"), ringItem) c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("000"), []byte("080")) + overlaps = bucketDebrisFactory([]byte("000"), []byte("080"), ringItem) c.Assert(overlaps, HasLen, 1) - overlaps = ringItem.Debris([]byte("020"), []byte("080")) + overlaps = bucketDebrisFactory([]byte("020"), []byte("080"), ringItem) c.Assert(overlaps, HasLen, 2) - overlaps = ringItem.Debris([]byte("010"), []byte("090")) + overlaps = bucketDebrisFactory([]byte("010"), []byte("090"), ringItem) c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("010"), []byte("100")) + overlaps = bucketDebrisFactory([]byte("010"), []byte("100"), ringItem) c.Assert(overlaps, HasLen, 0) - overlaps = ringItem.Debris([]byte("100"), []byte("200")) + overlaps = bucketDebrisFactory([]byte("100"), []byte("200"), ringItem) c.Assert(overlaps, HasLen, 0) } diff --git a/server/core/region_tree.go b/server/core/region_tree.go index 166696a1d2f..a1251cb9f92 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -73,8 +73,11 @@ type regionTree struct { } func newRegionTree() *regionTree { + factory := func(_, _ []byte, _ rangetree.RangeItem) []rangetree.RangeItem { + return nil + } return ®ionTree{ - tree: rangetree.NewRangeTree(defaultBTreeDegree), + tree: rangetree.NewRangeTree(defaultBTreeDegree, factory), totalSize: 0, totalWriteBytesRate: 0, totalWriteKeysRate: 0, From 804be9cf6509dc38f456f5e08f599a072ff10f8b Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Mon, 16 May 2022 17:26:54 +0800 Subject: [PATCH 12/13] address comment Signed-off-by: bufferflies <1045931706@qq.com> --- server/core/region_tree.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/core/region_tree.go b/server/core/region_tree.go index a1251cb9f92..4bb1dc9d0b9 100644 --- a/server/core/region_tree.go +++ b/server/core/region_tree.go @@ -43,11 +43,6 @@ func (r *regionItem) GetEndKey() []byte { return r.region.GetEndKey() } -// Debris returns nil, don't need to save debris. -func (r *regionItem) Debris(_, _ []byte) []rangetree.RangeItem { - return nil -} - // Less returns true if the region start key is less than the other. func (r *regionItem) Less(other btree.Item) bool { left := r.region.GetStartKey() From b8aed050071dcefbd33eeb0817c30a7848367028 Mon Sep 17 00:00:00 2001 From: bufferflies <1045931706@qq.com> Date: Mon, 16 May 2022 20:15:05 +0800 Subject: [PATCH 13/13] add comment for DebrisFactory Signed-off-by: bufferflies <1045931706@qq.com> --- pkg/rangetree/range_tree.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/rangetree/range_tree.go b/pkg/rangetree/range_tree.go index 4b55355bf78..d647e1e78d0 100644 --- a/pkg/rangetree/range_tree.go +++ b/pkg/rangetree/range_tree.go @@ -27,6 +27,7 @@ type RangeItem interface { GetEndKey() []byte } +// DebrisFactory is the factory that generates some debris when updating items. type DebrisFactory func(startKey, EndKey []byte, item RangeItem) []RangeItem // RangeTree is the tree contains RangeItems.