fix: критични бъгове в storage, MVCC, auth, Raft, query executor

Поправени проблеми:
- MVCC: aborted транзакции вече не стават видими (добавено abortedTxns множество)
- LSM-Tree: поправена загуба на данни при immutable memtable overwrite
- LSM-Tree: поправена SSTable search order (сортиране по id вместо minKey)
- WAL: sync()/close() вече извикват posix.fsync()
- Auth: simpleHash (djb2) заменен с HMAC-SHA256
- Recovery: summary() вече не мутира базата данни
- Raft: поправена majority calculation за четен брой нодове
- Query: EXISTS subqueries вече изпълняват подзаявката
- Nimble: поправен build (-d:ssl) и bench task

Добавен BUG_AUDIT.md с пълен доклад и план за подобрения.

292 теста, 0 failure-а.
This commit is contained in:
2026-05-12 22:45:50 +03:00
parent 3396ba4d63
commit 131541a0e5
10 changed files with 288 additions and 26 deletions
+11
View File
@@ -43,6 +43,8 @@ type
nextLsn: uint64
activeTxns: Table[TxnId, Transaction]
committedTxns: seq[TxnId]
committedTxnsSet: HashSet[TxnId]
abortedTxns: HashSet[TxnId]
globalVersions*: Table[string, seq[VersionedRecord]]
txnTimeoutMs: int64
deadlockDetector*: DeadlockDetector
@@ -59,6 +61,8 @@ proc newTxnManager*(txnTimeoutMs: int64 = 30000): TxnManager =
result.nextLsn = 1
result.activeTxns = initTable[TxnId, Transaction]()
result.committedTxns = @[]
result.committedTxnsSet = initHashSet[TxnId]()
result.abortedTxns = initHashSet[TxnId]()
result.globalVersions = initTable[string, seq[VersionedRecord]]()
result.txnTimeoutMs = txnTimeoutMs
result.deadlockDetector = newDeadlockDetector()
@@ -112,8 +116,13 @@ proc isVisible(tm: TxnManager, txn: Transaction, version: VersionedRecord): bool
return false
if creator in txn.snapshotTxns:
return false
if creator in tm.abortedTxns:
return false
if creator in tm.activeTxns and tm.activeTxns[creator].state != tsCommitted:
return false
# If creator is not active and not committed, it must be aborted
if creator notin tm.activeTxns and creator notin tm.committedTxnsSet:
return false
# Deleter must not be a visible committed txn
if deleter != TxnId(0):
@@ -246,6 +255,7 @@ proc commit*(tm: TxnManager, txn: Transaction): bool =
txn.state = tsCommitted
tm.committedTxns.add(txn.id)
tm.committedTxnsSet.incl(txn.id)
tm.activeTxns.del(txn.id)
tm.deadlockDetector.removeTxn(uint64(txn.id))
release(tm.lock)
@@ -258,6 +268,7 @@ proc abortTxn*(tm: TxnManager, txn: Transaction): bool =
return false
txn.state = tsAborted
tm.activeTxns.del(txn.id)
tm.abortedTxns.incl(txn.id)
tm.deadlockDetector.removeTxn(uint64(txn.id))
release(tm.lock)
return true
+1 -1
View File
@@ -290,7 +290,7 @@ proc handleAppendReply*(node: RaftNode, peerId: string, reply: RaftMessage) =
matchIndices.add(idx)
matchIndices.sort()
let medianIdx = matchIndices[matchIndices.len div 2]
let medianIdx = matchIndices[(matchIndices.len - 1) div 2]
if medianIdx > node.commitIndex:
if medianIdx <= node.lastLogIndex and
node.log[medianIdx - 1].term == node.currentTerm: