mirror of
https://github.com/luxfi/zapdb.git
synced 2026-10-30 15:12:20 +00:00
During compaction we always used to pick files from current level(level on which compaction is running) in increasing order of file size. This PR adds change, where we pick files from current level in increasing order of their overlap with files on next level.
(cherry picked from commit afc0b45d35)
214 lines
5.3 KiB
Go
214 lines
5.3 KiB
Go
/*
|
|
* Copyright 2017 Dgraph Labs, Inc. and Contributors
|
|
*
|
|
* 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 badger
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"sync"
|
|
|
|
"golang.org/x/net/trace"
|
|
|
|
"github.com/dgraph-io/badger/table"
|
|
"github.com/dgraph-io/badger/y"
|
|
)
|
|
|
|
type keyRange struct {
|
|
left []byte
|
|
right []byte
|
|
inf bool
|
|
}
|
|
|
|
var infRange = keyRange{inf: true}
|
|
|
|
func (r keyRange) String() string {
|
|
return fmt.Sprintf("[left=%x, right=%x, inf=%v]", r.left, r.right, r.inf)
|
|
}
|
|
|
|
func (r keyRange) equals(dst keyRange) bool {
|
|
return bytes.Equal(r.left, dst.left) &&
|
|
bytes.Equal(r.right, dst.right) &&
|
|
r.inf == dst.inf
|
|
}
|
|
|
|
func (r keyRange) overlapsWith(dst keyRange) bool {
|
|
if r.inf || dst.inf {
|
|
return true
|
|
}
|
|
|
|
// If my left is greater than dst right, we have no overlap.
|
|
if y.CompareKeys(r.left, dst.right) > 0 {
|
|
return false
|
|
}
|
|
// If my right is less than dst left, we have no overlap.
|
|
if y.CompareKeys(r.right, dst.left) < 0 {
|
|
return false
|
|
}
|
|
// We have overlap.
|
|
return true
|
|
}
|
|
|
|
func getKeyRange(tables ...*table.Table) keyRange {
|
|
if len(tables) == 0 {
|
|
return keyRange{}
|
|
}
|
|
smallest := tables[0].Smallest()
|
|
biggest := tables[0].Biggest()
|
|
for i := 1; i < len(tables); i++ {
|
|
if y.CompareKeys(tables[i].Smallest(), smallest) < 0 {
|
|
smallest = tables[i].Smallest()
|
|
}
|
|
if y.CompareKeys(tables[i].Biggest(), biggest) > 0 {
|
|
biggest = tables[i].Biggest()
|
|
}
|
|
}
|
|
|
|
// We pick all the versions of the smallest and the biggest key. Note that version zero would
|
|
// be the rightmost key, considering versions are default sorted in descending order.
|
|
return keyRange{
|
|
left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64),
|
|
right: y.KeyWithTs(y.ParseKey(biggest), 0),
|
|
}
|
|
}
|
|
|
|
type levelCompactStatus struct {
|
|
ranges []keyRange
|
|
delSize int64
|
|
}
|
|
|
|
func (lcs *levelCompactStatus) debug() string {
|
|
var b bytes.Buffer
|
|
for _, r := range lcs.ranges {
|
|
b.WriteString(r.String())
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (lcs *levelCompactStatus) overlapsWith(dst keyRange) bool {
|
|
for _, r := range lcs.ranges {
|
|
if r.overlapsWith(dst) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (lcs *levelCompactStatus) remove(dst keyRange) bool {
|
|
final := lcs.ranges[:0]
|
|
var found bool
|
|
for _, r := range lcs.ranges {
|
|
if !r.equals(dst) {
|
|
final = append(final, r)
|
|
} else {
|
|
found = true
|
|
}
|
|
}
|
|
lcs.ranges = final
|
|
return found
|
|
}
|
|
|
|
type compactStatus struct {
|
|
sync.RWMutex
|
|
levels []*levelCompactStatus
|
|
}
|
|
|
|
func (cs *compactStatus) toLog(tr trace.Trace) {
|
|
cs.RLock()
|
|
defer cs.RUnlock()
|
|
|
|
tr.LazyPrintf("Compaction status:")
|
|
for i, l := range cs.levels {
|
|
if l.debug() == "" {
|
|
continue
|
|
}
|
|
tr.LazyPrintf("[%d] %s", i, l.debug())
|
|
}
|
|
}
|
|
|
|
func (cs *compactStatus) overlapsWith(level int, this keyRange) bool {
|
|
cs.RLock()
|
|
defer cs.RUnlock()
|
|
|
|
thisLevel := cs.levels[level]
|
|
return thisLevel.overlapsWith(this)
|
|
}
|
|
|
|
func (cs *compactStatus) delSize(l int) int64 {
|
|
cs.RLock()
|
|
defer cs.RUnlock()
|
|
return cs.levels[l].delSize
|
|
}
|
|
|
|
type thisAndNextLevelRLocked struct{}
|
|
|
|
// compareAndAdd will check whether we can run this compactDef. That it doesn't overlap with any
|
|
// other running compaction. If it can be run, it would store this run in the compactStatus state.
|
|
func (cs *compactStatus) compareAndAdd(_ thisAndNextLevelRLocked, cd compactDef) bool {
|
|
cs.Lock()
|
|
defer cs.Unlock()
|
|
|
|
level := cd.thisLevel.level
|
|
|
|
y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels))
|
|
thisLevel := cs.levels[level]
|
|
nextLevel := cs.levels[level+1]
|
|
|
|
if thisLevel.overlapsWith(cd.thisRange) {
|
|
return false
|
|
}
|
|
if nextLevel.overlapsWith(cd.nextRange) {
|
|
return false
|
|
}
|
|
// Check whether this level really needs compaction or not. Otherwise, we'll end up
|
|
// running parallel compactions for the same level.
|
|
// Update: We should not be checking size here. Compaction priority already did the size checks.
|
|
// Here we should just be executing the wish of others.
|
|
|
|
thisLevel.ranges = append(thisLevel.ranges, cd.thisRange)
|
|
nextLevel.ranges = append(nextLevel.ranges, cd.nextRange)
|
|
thisLevel.delSize += cd.thisSize
|
|
return true
|
|
}
|
|
|
|
func (cs *compactStatus) delete(cd compactDef) {
|
|
cs.Lock()
|
|
defer cs.Unlock()
|
|
|
|
level := cd.thisLevel.level
|
|
y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels))
|
|
|
|
thisLevel := cs.levels[level]
|
|
nextLevel := cs.levels[level+1]
|
|
|
|
thisLevel.delSize -= cd.thisSize
|
|
found := thisLevel.remove(cd.thisRange)
|
|
found = nextLevel.remove(cd.nextRange) && found
|
|
|
|
if !found {
|
|
this := cd.thisRange
|
|
next := cd.nextRange
|
|
fmt.Printf("Looking for: [%q, %q, %v] in this level.\n", this.left, this.right, this.inf)
|
|
fmt.Printf("This Level:\n%s\n", thisLevel.debug())
|
|
fmt.Println()
|
|
fmt.Printf("Looking for: [%q, %q, %v] in next level.\n", next.left, next.right, next.inf)
|
|
fmt.Printf("Next Level:\n%s\n", nextLevel.debug())
|
|
log.Fatal("keyRange not found")
|
|
}
|
|
}
|