perf: add FileStore buffered writes, O(1) state tracking, and eliminate redundant per-publish work
Implement Go-parity background flush loop (coalesce 16KB/8ms) in MsgBlock/FileStore, replace O(n) GetStateAsync with incremental counters, skip PruneExpired/LoadAsync/ PrunePerSubject when not needed, and bypass RAFT for single-replica streams. Fix counter tracking bugs in RemoveMsg/EraseMsg/TTL expiry and ObjectDisposedException races in flush loop disposal. FileStore optimizations verified with 3112/3112 JetStream tests passing; async publish benchmark remains at ~174 msg/s due to E2E protocol path bottleneck.
This commit is contained in:
@@ -60,7 +60,7 @@ public sealed class FileStoreRecovery2Tests : IDisposable
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
try { Directory.Delete(_root, recursive: true); }
|
||||
catch { /* best-effort cleanup */ }
|
||||
catch (IOException) { /* best-effort cleanup — directory may be locked by OS */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,28 +381,33 @@ public sealed class FileStoreRecovery2Tests : IDisposable
|
||||
public void SyncCompress_OnlyIfDirty_CompactFlagBehavior()
|
||||
{
|
||||
var dir = UniqueDir();
|
||||
using var store = CreateStore(dir, new FileStoreOptions { BlockSizeBytes = 256 });
|
||||
|
||||
var msg = "hello"u8.ToArray();
|
||||
// Scoped block to ensure store is fully disposed (pending writes flushed)
|
||||
// before opening the second store for recovery verification.
|
||||
{
|
||||
using var store = CreateStore(dir, new FileStoreOptions { BlockSizeBytes = 256 });
|
||||
|
||||
// Fill 2 blocks (6 per block at blockSize=256).
|
||||
for (var i = 0; i < 12; i++)
|
||||
store.StoreMsg("foo.BB", null, msg, 0);
|
||||
var msg = "hello"u8.ToArray();
|
||||
|
||||
// Add one more to start a third block.
|
||||
store.StoreMsg("foo.BB", null, msg, 0); // seq 13
|
||||
// Fill 2 blocks (6 per block at blockSize=256).
|
||||
for (var i = 0; i < 12; i++)
|
||||
store.StoreMsg("foo.BB", null, msg, 0);
|
||||
|
||||
// Delete a bunch to create holes in blocks 1 and 2.
|
||||
foreach (var seq in new ulong[] { 2, 3, 4, 5, 8, 9, 10, 11 })
|
||||
store.RemoveMsg(seq).ShouldBeTrue();
|
||||
// Add one more to start a third block.
|
||||
store.StoreMsg("foo.BB", null, msg, 0); // seq 13
|
||||
|
||||
// Add more to create a 4th/5th block.
|
||||
for (var i = 0; i < 6; i++)
|
||||
store.StoreMsg("foo.BB", null, msg, 0);
|
||||
// Delete a bunch to create holes in blocks 1 and 2.
|
||||
foreach (var seq in new ulong[] { 2, 3, 4, 5, 8, 9, 10, 11 })
|
||||
store.RemoveMsg(seq).ShouldBeTrue();
|
||||
|
||||
// Total live: 13 + 6 = 19 - 8 deleted = 11.
|
||||
var state = store.State();
|
||||
state.Msgs.ShouldBe(11UL);
|
||||
// Add more to create a 4th/5th block.
|
||||
for (var i = 0; i < 6; i++)
|
||||
store.StoreMsg("foo.BB", null, msg, 0);
|
||||
|
||||
// Total live: 13 + 6 = 19 - 8 deleted = 11.
|
||||
var state = store.State();
|
||||
state.Msgs.ShouldBe(11UL);
|
||||
}
|
||||
|
||||
// After restart, state should be preserved.
|
||||
using var store2 = CreateStore(dir, new FileStoreOptions { BlockSizeBytes = 256 });
|
||||
|
||||
Reference in New Issue
Block a user