From 3f29c519d489478f32fcc734048d98046696ee27 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Mon, 18 May 2026 12:14:30 +0300 Subject: [PATCH] Storage hardening: btree size fix, WAL entryCount note, LSM put error handling - 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 --- src/barabadb/storage/btree.nim | 16 ++++++++++------ src/barabadb/storage/lsm.nim | 15 ++++++++++----- src/barabadb/storage/wal.nim | 2 ++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/barabadb/storage/btree.nim b/src/barabadb/storage/btree.nim index 72c72b4..5d35352 100644 --- a/src/barabadb/storage/btree.nim +++ b/src/barabadb/storage/btree.nim @@ -69,16 +69,18 @@ proc splitChild[K, V](parent: BTreeNode[K, V], index: int, order: int) = else: 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 if node.isLeaf: while i >= 0 and key < node.keys[i]: dec i if i >= 0 and key == node.keys[i]: node.values[i].add(value) - return + return false node.keys.insert(key, i + 1) node.values.insert(@[value], i + 1) + return true else: while i >= 0 and key < node.keys[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) if key > node.keys[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) = acquire(btree.lock) try: + var inserted = false if btree.root.keys.len == btree.order - 1: var newRoot = newBTreeNode[K, V](isLeaf = false) newRoot.children.add(btree.root) splitChild(newRoot, 0, btree.order) btree.root = newRoot - insertNonFull(btree.root, key, value, btree.order) + inserted = insertNonFull(btree.root, key, value, btree.order) else: - insertNonFull(btree.root, key, value, btree.order) - inc btree.size + inserted = insertNonFull(btree.root, key, value, btree.order) + if inserted: + inc btree.size finally: release(btree.lock) diff --git a/src/barabadb/storage/lsm.nim b/src/barabadb/storage/lsm.nim index e53741f..68c12e8 100644 --- a/src/barabadb/storage/lsm.nim +++ b/src/barabadb/storage/lsm.nim @@ -375,11 +375,13 @@ proc newLSMTree*(dir: string, memMaxSize: int = DefaultMemTableSize): LSMTree = of wekPut: if not result.memTable.put(key, value, timestamp): 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: if not result.memTable.put(key, @[], timestamp, deleted = true): 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: discard of wekCheckpoint: @@ -401,7 +403,8 @@ proc put*(db: LSMTree, key: string, value: seq[byte]) = db.flushUnsafe() db.immutableMem = db.memTable 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) = let ts = uint64(getMonoTime().ticks()) @@ -416,7 +419,8 @@ proc delete*(db: LSMTree, key: string) = db.flushUnsafe() db.immutableMem = db.memTable 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) = ## 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.immutableMem = db.memTable 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) = putUnsafe(db, key, @[], deleted = true) diff --git a/src/barabadb/storage/wal.nim b/src/barabadb/storage/wal.nim index dbe26bb..4f516c4 100644 --- a/src/barabadb/storage/wal.nim +++ b/src/barabadb/storage/wal.nim @@ -38,6 +38,8 @@ proc newWriteAheadLog*(dir: string, syncOnWrite: bool = true): WriteAheadLog = stream.write(WALMagic) stream.write(WALVersion) 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) proc writeEntry*(wal: var WriteAheadLog, entry: WalEntry) =