mirror of
https://github.com/luxfi/zapdb.git
synced 2026-10-30 15:12:20 +00:00
The module has been github.com/luxfi/zapdb for a while, but the package inside it still declared itself badger, so every consumer had to write zapdb "github.com/luxfi/zapdb" // package badger and the alias existed only to paper over the mismatch. 56 root files and the 134 qualified references inside the repo now say zapdb. Backward compatible in both directions: an existing consumer with the alias keeps compiling (the alias is now merely redundant), and one without it starts compiling instead of failing on an undefined zapdb. Directory badger/ and its cmd package are untouched — that is the CLI, and its binary is named badger. Build and vet clean; suite green except TestZRestore, which wants a /tmp/zb/src benchmark fixture and fails identically without this change. Co-authored-by: Hanzo Dev <[email protected]>
239 lines
5.9 KiB
Go
239 lines
5.9 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package zapdb
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/luxfi/zapdb/table"
|
|
"github.com/luxfi/zapdb/y"
|
|
)
|
|
|
|
type keyRange struct {
|
|
left []byte
|
|
right []byte
|
|
inf bool
|
|
size int64 // size is used for Key splits.
|
|
}
|
|
|
|
func (r keyRange) isEmpty() bool {
|
|
return len(r.left) == 0 && len(r.right) == 0 && !r.inf
|
|
}
|
|
|
|
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) extend(kr keyRange) {
|
|
// TODO(ibrahim): Is this needed?
|
|
if kr.isEmpty() {
|
|
return
|
|
}
|
|
if r.isEmpty() {
|
|
*r = kr
|
|
}
|
|
if len(r.left) == 0 || y.CompareKeys(kr.left, r.left) < 0 {
|
|
r.left = kr.left
|
|
}
|
|
if len(r.right) == 0 || y.CompareKeys(kr.right, r.right) > 0 {
|
|
r.right = kr.right
|
|
}
|
|
if kr.inf {
|
|
r.inf = true
|
|
}
|
|
}
|
|
|
|
func (r keyRange) overlapsWith(dst keyRange) bool {
|
|
// Empty keyRange always overlaps.
|
|
if r.isEmpty() {
|
|
return true
|
|
}
|
|
// TODO(ibrahim): Do you need this?
|
|
// Empty dst doesn't overlap with anything.
|
|
if dst.isEmpty() {
|
|
return false
|
|
}
|
|
if r.inf || dst.inf {
|
|
return true
|
|
}
|
|
|
|
// [dst.left, dst.right] ... [r.left, r.right]
|
|
// If my left is greater than dst right, we have no overlap.
|
|
if y.CompareKeys(r.left, dst.right) > 0 {
|
|
return false
|
|
}
|
|
// [r.left, r.right] ... [dst.left, dst.right]
|
|
// 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
|
|
}
|
|
|
|
// getKeyRange returns the smallest and the biggest in the list of tables.
|
|
// TODO(naman): Write a test for this. The smallest and the biggest should
|
|
// be the smallest of the leftmost table and the biggest of the right most table.
|
|
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
|
|
tables map[uint64]struct{}
|
|
}
|
|
|
|
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()
|
|
|
|
tl := cd.thisLevel.level
|
|
y.AssertTruef(tl < len(cs.levels), "Got level %d. Max levels: %d", tl, len(cs.levels))
|
|
thisLevel := cs.levels[cd.thisLevel.level]
|
|
nextLevel := cs.levels[cd.nextLevel.level]
|
|
|
|
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
|
|
for _, t := range append(cd.top, cd.bot...) {
|
|
cs.tables[t.ID()] = struct{}{}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (cs *compactStatus) delete(cd compactDef) {
|
|
cs.Lock()
|
|
defer cs.Unlock()
|
|
|
|
tl := cd.thisLevel.level
|
|
y.AssertTruef(tl < len(cs.levels), "Got level %d. Max levels: %d", tl, len(cs.levels))
|
|
|
|
thisLevel := cs.levels[cd.thisLevel.level]
|
|
nextLevel := cs.levels[cd.nextLevel.level]
|
|
|
|
thisLevel.delSize -= cd.thisSize
|
|
found := thisLevel.remove(cd.thisRange)
|
|
// The following check makes sense only if we're compacting more than one
|
|
// table. In case of the max level, we might rewrite a single table to
|
|
// remove stale data.
|
|
if cd.thisLevel != cd.nextLevel && !cd.nextRange.isEmpty() {
|
|
found = nextLevel.remove(cd.nextRange) && found
|
|
}
|
|
|
|
if !found {
|
|
this := cd.thisRange
|
|
next := cd.nextRange
|
|
fmt.Printf("Looking for: %s in this level %d.\n", this, tl)
|
|
fmt.Printf("This Level:\n%s\n", thisLevel.debug())
|
|
fmt.Println()
|
|
fmt.Printf("Looking for: %s in next level %d.\n", next, cd.nextLevel.level)
|
|
fmt.Printf("Next Level:\n%s\n", nextLevel.debug())
|
|
log.Fatal("keyRange not found")
|
|
}
|
|
for _, t := range append(cd.top, cd.bot...) {
|
|
_, ok := cs.tables[t.ID()]
|
|
y.AssertTrue(ok)
|
|
delete(cs.tables, t.ID())
|
|
}
|
|
}
|