Storage hardening: btree size fix, WAL entryCount note, LSM put error handling
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

- btree.nim: insertNonFull returns bool; size only increments for new keys
- wal.nim: comment about entryCount reset on append
- lsm.nim: recovery/put/delete/putUnsafe raise IOError if put fails after flush
This commit is contained in:
2026-05-18 12:14:30 +03:00
parent 8596cea548
commit 3f29c519d4
3 changed files with 22 additions and 11 deletions
+10 -6
View File
@@ -69,16 +69,18 @@ proc splitChild[K, V](parent: BTreeNode[K, V], index: int, order: int) =
else: else:
child.keys.setLen(mid) child.keys.setLen(mid)
proc insertNonFull[K, V](node: BTreeNode[K, V], key: K, value: V, order: int) = proc insertNonFull[K, V](node: BTreeNode[K, V], key: K, value: V, order: int): bool =
## Returns true if a new key was inserted, false if existing key got a new value.
var i = node.keys.len - 1 var i = node.keys.len - 1
if node.isLeaf: if node.isLeaf:
while i >= 0 and key < node.keys[i]: while i >= 0 and key < node.keys[i]:
dec i dec i
if i >= 0 and key == node.keys[i]: if i >= 0 and key == node.keys[i]:
node.values[i].add(value) node.values[i].add(value)
return return false
node.keys.insert(key, i + 1) node.keys.insert(key, i + 1)
node.values.insert(@[value], i + 1) node.values.insert(@[value], i + 1)
return true
else: else:
while i >= 0 and key < node.keys[i]: while i >= 0 and key < node.keys[i]:
dec i dec i
@@ -87,20 +89,22 @@ proc insertNonFull[K, V](node: BTreeNode[K, V], key: K, value: V, order: int) =
splitChild(node, i, order) splitChild(node, i, order)
if key > node.keys[i]: if key > node.keys[i]:
inc i inc i
insertNonFull(node.children[i], key, value, order) return insertNonFull(node.children[i], key, value, order)
proc insert*[K, V](btree: var BTreeIndex[K, V], key: K, value: V) = proc insert*[K, V](btree: var BTreeIndex[K, V], key: K, value: V) =
acquire(btree.lock) acquire(btree.lock)
try: try:
var inserted = false
if btree.root.keys.len == btree.order - 1: if btree.root.keys.len == btree.order - 1:
var newRoot = newBTreeNode[K, V](isLeaf = false) var newRoot = newBTreeNode[K, V](isLeaf = false)
newRoot.children.add(btree.root) newRoot.children.add(btree.root)
splitChild(newRoot, 0, btree.order) splitChild(newRoot, 0, btree.order)
btree.root = newRoot btree.root = newRoot
insertNonFull(btree.root, key, value, btree.order) inserted = insertNonFull(btree.root, key, value, btree.order)
else: else:
insertNonFull(btree.root, key, value, btree.order) inserted = insertNonFull(btree.root, key, value, btree.order)
inc btree.size if inserted:
inc btree.size
finally: finally:
release(btree.lock) release(btree.lock)
+10 -5
View File
@@ -375,11 +375,13 @@ proc newLSMTree*(dir: string, memMaxSize: int = DefaultMemTableSize): LSMTree =
of wekPut: of wekPut:
if not result.memTable.put(key, value, timestamp): if not result.memTable.put(key, value, timestamp):
result.flushUnsafe() result.flushUnsafe()
discard result.memTable.put(key, value, timestamp) if not result.memTable.put(key, value, timestamp):
raise newException(IOError, "WAL recovery: failed to insert key into memtable")
of wekDelete: of wekDelete:
if not result.memTable.put(key, @[], timestamp, deleted = true): if not result.memTable.put(key, @[], timestamp, deleted = true):
result.flushUnsafe() result.flushUnsafe()
discard result.memTable.put(key, @[], timestamp, deleted = true) if not result.memTable.put(key, @[], timestamp, deleted = true):
raise newException(IOError, "WAL recovery: failed to insert delete tombstone into memtable")
of wekCommit: of wekCommit:
discard discard
of wekCheckpoint: of wekCheckpoint:
@@ -401,7 +403,8 @@ proc put*(db: LSMTree, key: string, value: seq[byte]) =
db.flushUnsafe() db.flushUnsafe()
db.immutableMem = db.memTable db.immutableMem = db.memTable
db.memTable = newMemTable(db.memMaxSize) db.memTable = newMemTable(db.memMaxSize)
discard db.memTable.put(key, value, ts) if not db.memTable.put(key, value, ts):
raise newException(IOError, "LSM put failed after flush")
proc delete*(db: LSMTree, key: string) = proc delete*(db: LSMTree, key: string) =
let ts = uint64(getMonoTime().ticks()) let ts = uint64(getMonoTime().ticks())
@@ -416,7 +419,8 @@ proc delete*(db: LSMTree, key: string) =
db.flushUnsafe() db.flushUnsafe()
db.immutableMem = db.memTable db.immutableMem = db.memTable
db.memTable = newMemTable(db.memMaxSize) db.memTable = newMemTable(db.memMaxSize)
discard db.memTable.put(key, @[], ts, deleted = true) if not db.memTable.put(key, @[], ts, deleted = true):
raise newException(IOError, "LSM delete failed after flush")
proc putUnsafe*(db: LSMTree, key: string, value: seq[byte], deleted: bool = false) = proc putUnsafe*(db: LSMTree, key: string, value: seq[byte], deleted: bool = false) =
## Direct LSM insert without WAL logging — used by recovery. ## Direct LSM insert without WAL logging — used by recovery.
@@ -428,7 +432,8 @@ proc putUnsafe*(db: LSMTree, key: string, value: seq[byte], deleted: bool = fals
db.flushUnsafe() db.flushUnsafe()
db.immutableMem = db.memTable db.immutableMem = db.memTable
db.memTable = newMemTable(db.memMaxSize) db.memTable = newMemTable(db.memMaxSize)
discard db.memTable.put(key, value, ts, deleted) if not db.memTable.put(key, value, ts, deleted):
raise newException(IOError, "LSM putUnsafe failed after flush")
proc deleteUnsafe*(db: LSMTree, key: string) = proc deleteUnsafe*(db: LSMTree, key: string) =
putUnsafe(db, key, @[], deleted = true) putUnsafe(db, key, @[], deleted = true)
+2
View File
@@ -38,6 +38,8 @@ proc newWriteAheadLog*(dir: string, syncOnWrite: bool = true): WriteAheadLog =
stream.write(WALMagic) stream.write(WALMagic)
stream.write(WALVersion) stream.write(WALVersion)
stream.flush() stream.flush()
# NOTE: entryCount is reset to 0 when appending to existing WAL.
# For an accurate count, call readEntries() after construction.
WriteAheadLog(path: path, stream: stream, entryCount: 0, syncOnWrite: syncOnWrite) WriteAheadLog(path: path, stream: stream, entryCount: 0, syncOnWrite: syncOnWrite)
proc writeEntry*(wal: var WriteAheadLog, entry: WalEntry) = proc writeEntry*(wal: var WriteAheadLog, entry: WalEntry) =