Some checks failed
Go Tests / Run Tests (1.24.2) (push) Has been cancelled
Adds a complete LSM-based storage engine with these features: - Single-writer based architecture for the storage engine - WAL for durability, and hey it's configurable - MemTable with skip list implementation for fast read/writes - SSTable with block-based structure for on-disk level-based storage - Background compaction with tiered strategy - ACID transactions - Good documentation (I hope)
156 lines
3.5 KiB
Go
156 lines
3.5 KiB
Go
package memtable
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/jer/kevo/pkg/wal"
|
|
)
|
|
|
|
// MemTable is an in-memory table that stores key-value pairs
|
|
// It is implemented using a skip list for efficient inserts and lookups
|
|
type MemTable struct {
|
|
skipList *SkipList
|
|
nextSeqNum uint64
|
|
creationTime time.Time
|
|
immutable atomic.Bool
|
|
size int64
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewMemTable creates a new memory table
|
|
func NewMemTable() *MemTable {
|
|
return &MemTable{
|
|
skipList: NewSkipList(),
|
|
creationTime: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Put adds a key-value pair to the MemTable
|
|
func (m *MemTable) Put(key, value []byte, seqNum uint64) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.immutable.Load() {
|
|
// Don't modify immutable memtables
|
|
return
|
|
}
|
|
|
|
e := newEntry(key, value, TypeValue, seqNum)
|
|
m.skipList.Insert(e)
|
|
|
|
// Update maximum sequence number
|
|
if seqNum > m.nextSeqNum {
|
|
m.nextSeqNum = seqNum + 1
|
|
}
|
|
}
|
|
|
|
// Delete marks a key as deleted in the MemTable
|
|
func (m *MemTable) Delete(key []byte, seqNum uint64) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.immutable.Load() {
|
|
// Don't modify immutable memtables
|
|
return
|
|
}
|
|
|
|
e := newEntry(key, nil, TypeDeletion, seqNum)
|
|
m.skipList.Insert(e)
|
|
|
|
// Update maximum sequence number
|
|
if seqNum > m.nextSeqNum {
|
|
m.nextSeqNum = seqNum + 1
|
|
}
|
|
}
|
|
|
|
// Get retrieves the value associated with the given key
|
|
// Returns (nil, true) if the key exists but has been deleted
|
|
// Returns (nil, false) if the key does not exist
|
|
// Returns (value, true) if the key exists and has a value
|
|
func (m *MemTable) Get(key []byte) ([]byte, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
e := m.skipList.Find(key)
|
|
if e == nil {
|
|
return nil, false
|
|
}
|
|
|
|
// Check if this is a deletion marker
|
|
if e.valueType == TypeDeletion {
|
|
return nil, true // Key exists but was deleted
|
|
}
|
|
|
|
return e.value, true
|
|
}
|
|
|
|
// Contains checks if the key exists in the MemTable
|
|
func (m *MemTable) Contains(key []byte) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
return m.skipList.Find(key) != nil
|
|
}
|
|
|
|
// ApproximateSize returns the approximate size of the MemTable in bytes
|
|
func (m *MemTable) ApproximateSize() int64 {
|
|
return m.skipList.ApproximateSize()
|
|
}
|
|
|
|
// SetImmutable marks the MemTable as immutable
|
|
// After this is called, no more modifications are allowed
|
|
func (m *MemTable) SetImmutable() {
|
|
m.immutable.Store(true)
|
|
}
|
|
|
|
// IsImmutable returns whether the MemTable is immutable
|
|
func (m *MemTable) IsImmutable() bool {
|
|
return m.immutable.Load()
|
|
}
|
|
|
|
// Age returns the age of the MemTable in seconds
|
|
func (m *MemTable) Age() float64 {
|
|
return time.Since(m.creationTime).Seconds()
|
|
}
|
|
|
|
// NewIterator returns an iterator for the MemTable
|
|
func (m *MemTable) NewIterator() *Iterator {
|
|
return m.skipList.NewIterator()
|
|
}
|
|
|
|
// GetNextSequenceNumber returns the next sequence number to use
|
|
func (m *MemTable) GetNextSequenceNumber() uint64 {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return m.nextSeqNum
|
|
}
|
|
|
|
// ProcessWALEntry processes a WAL entry and applies it to the MemTable
|
|
func (m *MemTable) ProcessWALEntry(entry *wal.Entry) error {
|
|
switch entry.Type {
|
|
case wal.OpTypePut:
|
|
m.Put(entry.Key, entry.Value, entry.SequenceNumber)
|
|
case wal.OpTypeDelete:
|
|
m.Delete(entry.Key, entry.SequenceNumber)
|
|
case wal.OpTypeBatch:
|
|
// Process batch operations
|
|
batch, err := wal.DecodeBatch(entry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, op := range batch.Operations {
|
|
seqNum := batch.Seq + uint64(i)
|
|
switch op.Type {
|
|
case wal.OpTypePut:
|
|
m.Put(op.Key, op.Value, seqNum)
|
|
case wal.OpTypeDelete:
|
|
m.Delete(op.Key, seqNum)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|