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
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:
@@ -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,19 +89,21 @@ 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)
|
||||||
|
if inserted:
|
||||||
inc btree.size
|
inc btree.size
|
||||||
finally:
|
finally:
|
||||||
release(btree.lock)
|
release(btree.lock)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) =
|
||||||
|
|||||||
Reference in New Issue
Block a user