level_handler.go
level_handler.go - Overview
This file defines the levelHandler
struct and its associated methods for managing tables at a specific level in the LSM tree. It handles operations like adding, deleting, replacing, and retrieving tables, as well as managing their sizes and iterators.
Detailed Documentation
levelHandler
type levelHandler struct {
sync.RWMutex
tables []*table.Table
totalSize int64
totalStaleSize int64
level int
strLevel string
db *DB
}
The levelHandler
struct manages tables at a specific level.
sync.RWMutex
: A read-write mutex to protect access to the tables and size information.tables
: A slice oftable.Table
pointers representing the tables at this level.totalSize
: The total size in bytes of all tables at this level.totalStaleSize
: The total size of stale data in tables at this level.level
: The level number in the LSM tree.strLevel
: A string representation of the level (e.g., "l0", "l1").db
: A pointer to theDB
struct.
levelHandler.isLastLevel
func (s *levelHandler) isLastLevel() bool {
return s.level == s.db.opt.MaxLevels-1
}
isLastLevel
checks if the level handler is for the last level in the database.
- Returns:
true
if it's the last level,false
otherwise.
levelHandler.getTotalStaleSize
func (s *levelHandler) getTotalStaleSize() int64 {
s.RLock()
defer s.RUnlock()
return s.totalStaleSize
}
getTotalStaleSize
returns the total stale size of the level.
- Returns: The total stale size (int64).
levelHandler.getTotalSize
func (s *levelHandler) getTotalSize() int64 {
s.RLock()
defer s.RUnlock()
return s.totalSize
}
getTotalSize
returns the total size of the level.
- Returns: The total size (int64).
levelHandler.initTables
func (s *levelHandler) initTables(tables []*table.Table) {
s.Lock()
defer s.Unlock()
s.tables = tables
s.totalSize = 0
s.totalStaleSize = 0
for _, t := range tables {
s.addSize(t)
}
if s.level == 0 {
sort.Slice(s.tables, func(i, j int) bool {
return s.tables[i].ID() < s.tables[j].ID()
})
} else {
sort.Slice(s.tables, func(i, j int) bool {
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
})
}
}
initTables
initializes the level handler with the given tables. It replaces the existing tables, recalculates the total size, and sorts the tables based on the level.
- Parameters:
tables
: A slice oftable.Table
pointers to initialize the level with.
levelHandler.deleteTables
func (s *levelHandler) deleteTables(toDel []*table.Table) error {
s.Lock()
toDelMap := make(map[uint64]struct{})
for _, t := range toDel {
toDelMap[t.ID()] = struct{}{}
}
var newTables []*table.Table
for _, t := range s.tables {
_, found := toDelMap[t.ID()]
if !found {
newTables = append(newTables, t)
continue
}
s.subtractSize(t)
}
s.tables = newTables
s.Unlock()
return decrRefs(toDel)
}
deleteTables
removes the specified tables from the level.
- Parameters:
toDel
: A slice oftable.Table
pointers to delete.
- Returns: An error, if any.
levelHandler.replaceTables
func (s *levelHandler) replaceTables(toDel, toAdd []*table.Table) error {
s.Lock()
toDelMap := make(map[uint64]struct{})
for _, t := range toDel {
toDelMap[t.ID()] = struct{}{}
}
var newTables []*table.Table
for _, t := range s.tables {
_, found := toDelMap[t.ID()]
if !found {
newTables = append(newTables, t)
continue
}
s.subtractSize(t)
}
for _, t := range toAdd {
s.addSize(t)
t.IncrRef()
newTables = append(newTables, t)
}
s.tables = newTables
sort.Slice(s.tables, func(i, j int) bool {
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
})
s.Unlock()
return decrRefs(toDel)
}
replaceTables
replaces a set of tables with another set of tables.
- Parameters:
toDel
: A slice oftable.Table
pointers to delete.toAdd
: A slice oftable.Table
pointers to add.
- Returns: An error, if any.
levelHandler.addTable
func (s *levelHandler) addTable(t *table.Table) {
s.Lock()
defer s.Unlock()
s.addSize(t)
t.IncrRef()
s.tables = append(s.tables, t)
}
addTable
adds a table to the level.
- Parameters:
t
: A pointer to thetable.Table
to add.
levelHandler.sortTables
func (s *levelHandler) sortTables() {
s.Lock()
defer s.Unlock()
sort.Slice(s.tables, func(i, j int) bool {
return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0
})
}
sortTables
sorts the tables in the level based on their smallest keys.
decrRefs
func decrRefs(tables []*table.Table) error {
for _, table := range tables {
if err := table.DecrRef(); err != nil {
return err
}
}
return nil
}
decrRefs
decrements the reference count of each table in the given slice.
- Parameters:
tables
: A slice oftable.Table
pointers.
- Returns: An error, if any.
newLevelHandler
func newLevelHandler(db *DB, level int) *levelHandler {
return &levelHandler{
level: level,
strLevel: fmt.Sprintf("l%d", level),
db: db,
}
}
newLevelHandler
creates a new levelHandler
.
- Parameters:
db
: A pointer to theDB
struct.level
: The level number.
- Returns: A pointer to the new
levelHandler
.
levelHandler.tryAddLevel0Table
func (s *levelHandler) tryAddLevel0Table(t *table.Table) bool {
y.AssertTrue(s.level == 0)
s.Lock()
defer s.Unlock()
if len(s.tables) >= s.db.opt.NumLevelZeroTablesStall {
return false
}
s.tables = append(s.tables, t)
t.IncrRef()
s.addSize(t)
return true
}
tryAddLevel0Table
attempts to add a table to level 0. It checks if the number of tables at level 0 exceeds the stall threshold.
- Parameters:
t
: A pointer to thetable.Table
to add.
- Returns:
true
if the table was added,false
otherwise (if stalling).
levelHandler.addSize
func (s *levelHandler) addSize(t *table.Table) {
s.totalSize += t.Size()
s.totalStaleSize += int64(t.StaleDataSize())
}
addSize
adds the size of a table to the total size of the level. This should be called while holding the lock on the level.
- Parameters:
t
: A pointer to thetable.Table
.
levelHandler.subtractSize
func (s *levelHandler) subtractSize(t *table.Table) {
s.totalSize -= t.Size()
s.totalStaleSize -= int64(t.StaleDataSize())
}
subtractSize
subtracts the size of a table from the total size of the level. This should be called while holding the lock on the level.
- Parameters:
t
: A pointer to thetable.Table
.
levelHandler.numTables
func (s *levelHandler) numTables() int {
s.RLock()
defer s.RUnlock()
return len(s.tables)
}
numTables
returns the number of tables in the level.
- Returns: The number of tables (int).
levelHandler.close
func (s *levelHandler) close() error {
s.RLock()
defer s.RUnlock()
var err error
for _, t := range s.tables {
if closeErr := t.Close(-1); closeErr != nil && err == nil {
err = closeErr
}
}
return y.Wrap(err, "levelHandler.close")
}
close
closes all tables in the level.
- Returns: An error, if any.
levelHandler.getTableForKey
func (s *levelHandler) getTableForKey(key []byte) ([]*table.Table, func() error) {
s.RLock()
defer s.RUnlock()
if s.level == 0 {
out := make([]*table.Table, 0, len(s.tables))
for i := len(s.tables) - 1; i >= 0; i-- {
out = append(out, s.tables[i])
s.tables[i].IncrRef()
}
return out, func() error {
for _, t := range out {
if err := t.DecrRef(); err != nil {
return err
}
}
return nil
}
}
idx := sort.Search(len(s.tables), func(i int) bool {
return y.CompareKeys(s.tables[i].Biggest(), key) >= 0
})
if idx >= len(s.tables) {
return nil, func() error { return nil }
}
tbl := s.tables[idx]
tbl.IncrRef()
return []*table.Table{tbl}, tbl.DecrRef
}
getTableForKey
retrieves the tables that may contain the given key.
- Parameters:
key
: The key to search for.
- Returns: A slice of
table.Table
pointers and a function to decrement the reference count of the tables.
levelHandler.get
func (s *levelHandler) get(key []byte) (y.ValueStruct, error) {
tables, decr := s.getTableForKey(key)
keyNoTs := y.ParseKey(key)
hash := y.Hash(keyNoTs)
var maxVs y.ValueStruct
for _, th := range tables {
if th.DoesNotHave(hash) {
y.NumLSMBloomHitsAdd(s.db.opt.MetricsEnabled, s.strLevel, 1)
continue
}
it := th.NewIterator(0)
defer it.Close()
y.NumLSMGetsAdd(s.db.opt.MetricsEnabled, s.strLevel, 1)
it.Seek(key)
if !it.Valid() {
continue
}
if y.SameKey(key, it.Key()) {
if version := y.ParseTs(it.Key()); maxVs.Version < version {
maxVs = it.ValueCopy()
maxVs.Version = version
}
}
}
return maxVs, decr()
}
get
returns the value for a given key.
- Parameters:
key
: The key to search for.
- Returns: The
y.ValueStruct
and an error, if any.
levelHandler.appendIterators
func (s *levelHandler) appendIterators(iters []y.Iterator, opt *IteratorOptions) []y.Iterator {
s.RLock()
defer s.RUnlock()
var topt int
if opt.Reverse {
topt = table.REVERSED
}
if s.level == 0 {
var out []*table.Table
for _, t := range s.tables {
if opt.pickTable(t) {
out = append(out, t)
}
}
return appendIteratorsReversed(iters, out, topt)
}
tables := opt.pickTables(s.tables)
if len(tables) == 0 {
return iters
}
return append(iters, table.NewConcatIterator(tables, topt))
}
appendIterators
appends iterators for the tables in this level to the given slice of iterators.
- Parameters:
iters
: A slice ofy.Iterator
.opt
:IteratorOptions
for creating the iterators.
- Returns: The updated slice of
y.Iterator
.
levelHandlerRLocked
type levelHandlerRLocked struct{}
levelHandlerRLocked
is an empty struct used as a signal to indicate that a read lock is already held on the levelHandler
.
levelHandler.overlappingTables
func (s *levelHandler) overlappingTables(_ levelHandlerRLocked, kr keyRange) (int, int) {
if len(kr.left) == 0 || len(kr.right) == 0 {
return 0, 0
}
left := sort.Search(len(s.tables), func(i int) bool {
return y.CompareKeys(kr.left, s.tables[i].Biggest()) <= 0
})
right := sort.Search(len(s.tables), func(i int) bool {
return y.CompareKeys(kr.right, s.tables[i].Smallest()) < 0
})
return left, right
}
overlappingTables
returns the range of tables that overlap with the given key range. This function assumes a read lock is already held.
- Parameters:
_
: An emptylevelHandlerRLocked
struct to enforce that the read lock is held.kr
: ThekeyRange
to check for overlaps.
- Returns: The left and right indices of the overlapping tables (half-interval).
Code Examples
None