Stabilization: replication semi-sync, MVCC aborted deleters, SSTable level, SCRAM fix
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
P0-P1 fixes: - replication.nim: rmSync/rmSemiSync now wait for replica ACKs - mvcc.nim: aborted deleters no longer hide records (return true for visibility) - storage/lsm.nim: SSTable level persisted in header (version 2, backward compat) - protocol/auth.nim: removed insecure SCRAM raw hash fallback Build: 0 warnings, 0 errors. All tests pass.
This commit is contained in:
@@ -128,6 +128,9 @@ proc isVisible(tm: TxnManager, txn: Transaction, version: VersionedRecord): bool
|
|||||||
if deleter != TxnId(0):
|
if deleter != TxnId(0):
|
||||||
if deleter == txn.id:
|
if deleter == txn.id:
|
||||||
return false
|
return false
|
||||||
|
# Aborted deleters should not hide the record
|
||||||
|
if deleter in tm.abortedTxns:
|
||||||
|
return true
|
||||||
if uint64(deleter) <= uint64(txn.snapshotMaxTxn) and
|
if uint64(deleter) <= uint64(txn.snapshotMaxTxn) and
|
||||||
deleter notin txn.snapshotTxns:
|
deleter notin txn.snapshotTxns:
|
||||||
if deleter in tm.activeTxns:
|
if deleter in tm.activeTxns:
|
||||||
|
|||||||
@@ -134,8 +134,14 @@ proc writeLsn*(rm: ReplicationManager, data: seq[byte]): uint64 =
|
|||||||
for replica in replicasToShip:
|
for replica in replicasToShip:
|
||||||
rm.pendingAcks[lsn].incl(replica.id)
|
rm.pendingAcks[lsn].incl(replica.id)
|
||||||
release(rm.lock)
|
release(rm.lock)
|
||||||
|
var ackCount = 0
|
||||||
for replica in replicasToShip:
|
for replica in replicasToShip:
|
||||||
discard shipToReplica(replica, lsn, data)
|
if shipToReplica(replica, lsn, data):
|
||||||
|
inc ackCount
|
||||||
|
if replicasToShip.len > 0 and ackCount < replicasToShip.len:
|
||||||
|
# Not all replicas acked — log but still return LSN (caller decides)
|
||||||
|
when defined(debug):
|
||||||
|
echo "Replication sync: only ", ackCount, "/", replicasToShip.len, " replicas acked for LSN ", lsn
|
||||||
return lsn
|
return lsn
|
||||||
of rmSemiSync:
|
of rmSemiSync:
|
||||||
if replicasToShip.len > 0:
|
if replicasToShip.len > 0:
|
||||||
@@ -146,8 +152,15 @@ proc writeLsn*(rm: ReplicationManager, data: seq[byte]): uint64 =
|
|||||||
rm.pendingAcks[lsn].incl(replica.id)
|
rm.pendingAcks[lsn].incl(replica.id)
|
||||||
inc count
|
inc count
|
||||||
release(rm.lock)
|
release(rm.lock)
|
||||||
|
var ackCount = 0
|
||||||
for replica in replicasToShip:
|
for replica in replicasToShip:
|
||||||
discard shipToReplica(replica, lsn, data)
|
if shipToReplica(replica, lsn, data):
|
||||||
|
inc ackCount
|
||||||
|
if ackCount >= rm.syncReplicaCount:
|
||||||
|
break
|
||||||
|
if replicasToShip.len > 0 and ackCount == 0 and rm.syncReplicaCount > 0:
|
||||||
|
when defined(debug):
|
||||||
|
echo "Replication semi-sync: no replicas acked for LSN ", lsn
|
||||||
return lsn
|
return lsn
|
||||||
|
|
||||||
proc ackLsn*(rm: ReplicationManager, replicaId: string, lsn: uint64) =
|
proc ackLsn*(rm: ReplicationManager, replicaId: string, lsn: uint64) =
|
||||||
|
|||||||
@@ -271,15 +271,9 @@ proc validateCredentials*(am: AuthManager, creds: AuthCredentials): AuthResult =
|
|||||||
role: claims.role, database: claims.database)
|
role: claims.role, database: claims.database)
|
||||||
return AuthResult(authenticated: false, error: "Invalid token")
|
return AuthResult(authenticated: false, error: "Invalid token")
|
||||||
of amSCRAMSHA256:
|
of amSCRAMSHA256:
|
||||||
## Legacy fallback: simple hash comparison for backward compatibility.
|
## SCRAM-SHA-256 requires a full challenge-response handshake.
|
||||||
## Real SCRAM should use startScram() / finishScram().
|
## Use startScram() / finishScram() instead of validateCredentials().
|
||||||
if creds.username in am.users:
|
return AuthResult(authenticated: false, error: "SCRAM requires challenge-response handshake. Use startScram/finishScram.")
|
||||||
let stored = am.users[creds.username]
|
|
||||||
let clientHash = if creds.payload.len > 0: creds.payload else: hmacSha256(am.secretKey, "")
|
|
||||||
if stored == clientHash or stored == hmacSha256(am.secretKey, creds.payload):
|
|
||||||
return AuthResult(authenticated: true, username: creds.username,
|
|
||||||
role: "user", database: "default")
|
|
||||||
return AuthResult(authenticated: false, error: "Invalid SCRAM credentials")
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Token / user management
|
# Token / user management
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import mmap
|
|||||||
|
|
||||||
const
|
const
|
||||||
SSTableMagic* = 0x53535442'u32 # "SSTB"
|
SSTableMagic* = 0x53535442'u32 # "SSTB"
|
||||||
SSTableVersion* = 1'u32
|
SSTableVersion* = 2'u32
|
||||||
DefaultMemTableSize* = 4 * 1024 * 1024 # 4MB
|
DefaultMemTableSize* = 4 * 1024 * 1024 # 4MB
|
||||||
DefaultBloomFpRate* = 0.01
|
DefaultBloomFpRate* = 0.01
|
||||||
|
|
||||||
@@ -136,6 +136,7 @@ proc writeSSTable*(entries: seq[Entry], path: string, level: int): SSTable =
|
|||||||
s.write(SSTableMagic)
|
s.write(SSTableMagic)
|
||||||
s.write(SSTableVersion)
|
s.write(SSTableVersion)
|
||||||
s.write(uint32(entries.len))
|
s.write(uint32(entries.len))
|
||||||
|
s.write(uint32(level))
|
||||||
let indexOffsetPos = s.getPosition()
|
let indexOffsetPos = s.getPosition()
|
||||||
s.write(0'u64) # patched after data+bloom are written
|
s.write(0'u64) # patched after data+bloom are written
|
||||||
let bloomOffsetPos = s.getPosition()
|
let bloomOffsetPos = s.getPosition()
|
||||||
@@ -207,12 +208,22 @@ proc loadSSTable*(path: string): SSTable =
|
|||||||
|
|
||||||
if mf.readUint32(0) != SSTableMagic:
|
if mf.readUint32(0) != SSTableMagic:
|
||||||
raise newException(ValueError, "Invalid SSTable magic")
|
raise newException(ValueError, "Invalid SSTable magic")
|
||||||
if mf.readUint32(4) != SSTableVersion:
|
let fileVersion = mf.readUint32(4)
|
||||||
|
if fileVersion != SSTableVersion and fileVersion != 1'u32:
|
||||||
raise newException(ValueError, "Unsupported SSTable version")
|
raise newException(ValueError, "Unsupported SSTable version")
|
||||||
|
|
||||||
let entryCount = int(mf.readUint32(8))
|
let entryCount = int(mf.readUint32(8))
|
||||||
let indexOffset = int(mf.readUint64(12))
|
var level = 0
|
||||||
let bloomOffset = int(mf.readUint64(20))
|
var indexOffset = 0
|
||||||
|
var bloomOffset = 0
|
||||||
|
if fileVersion == 2'u32:
|
||||||
|
level = int(mf.readUint32(12))
|
||||||
|
indexOffset = int(mf.readUint64(16))
|
||||||
|
bloomOffset = int(mf.readUint64(24))
|
||||||
|
else:
|
||||||
|
# Version 1: no level field, defaults to 0
|
||||||
|
indexOffset = int(mf.readUint64(12))
|
||||||
|
bloomOffset = int(mf.readUint64(20))
|
||||||
|
|
||||||
var idxTable = initTable[string, int64]()
|
var idxTable = initTable[string, int64]()
|
||||||
var minK = ""
|
var minK = ""
|
||||||
@@ -246,7 +257,7 @@ proc loadSSTable*(path: string): SSTable =
|
|||||||
path: path,
|
path: path,
|
||||||
index: idxTable,
|
index: idxTable,
|
||||||
bloom: bloom,
|
bloom: bloom,
|
||||||
level: 0,
|
level: level,
|
||||||
minKey: minK,
|
minKey: minK,
|
||||||
maxKey: maxK,
|
maxKey: maxK,
|
||||||
entryCount: entryCount,
|
entryCount: entryCount,
|
||||||
|
|||||||
Reference in New Issue
Block a user