Commit Graph

23 Commits

Author SHA1 Message Date
Joseph Doherty
7434844a39 feat: optimize FilteredState and LoadMsg with block-aware search (Gap 1.10)
Add NumFiltered with generation-based result caching, block-range binary
search helpers (FindFirstBlockAtOrAfter, FindBlockForSequence), and
CheckSkipFirstBlock optimization. FilteredState uses the block index to
skip sealed blocks below the requested start sequence. LoadMsg gains an
O(log n) block fallback for cases where _messages does not hold the
sequence. Generation counter incremented on every write/delete/purge to
invalidate NumFiltered cache entries.

Add 18 tests in FileStoreFilterQueryTests covering literal and wildcard
FilteredState, start-sequence filtering across multiple blocks, NumFiltered
basic counts and cache invalidation, LoadMsg block search, and
CheckSkipFirstBlock behaviour.

Also fix pre-existing slopwatch violations in WriteCacheTests.cs: replace
Task.Delay(150)/Task.Delay(5) with TrackWriteAt using explicit past
timestamps, and restructure Dispose to avoid empty catch blocks.

Reference: golang/nats-server/server/filestore.go:3191 (FilteredState),
           golang/nats-server/server/filestore.go:8308 (LoadMsg).
2026-02-25 08:13:52 -05:00
Joseph Doherty
6d754635e7 feat: add bounded write cache with TTL eviction and background flush (Gap 1.8)
Add WriteCacheManager inner class to FileStore with ConcurrentDictionary-based
tracking of per-block write sizes and timestamps, a PeriodicTimer (500ms tick)
background eviction loop, TTL-based expiry, and size-cap enforcement. Add
TrackWrite/TrackWriteAt/EvictBlock/FlushAllAsync/DisposeAsync API. Integrate
into FileStore constructor, AppendAsync, StoreMsg, StoreRawMsg, and RotateBlock.
Add MaxCacheSize/CacheExpiry options to FileStoreOptions. 12 new tests cover
size tracking, TTL eviction, size-cap eviction, flush-all, and integration paths.

Reference: golang/nats-server/server/filestore.go:4443 (setupWriteCache),
           golang/nats-server/server/filestore.go:6148 (expireCache).
2026-02-25 08:12:06 -05:00
Joseph Doherty
cbe41d0efb feat: add SequenceSet for sparse deletion tracking with secure erase (Gap 1.7)
Replace HashSet<ulong> _deleted in MsgBlock with SequenceSet — a sorted-range
list that compresses contiguous deletions into (Start, End) intervals. Adds
O(log n) Contains/Add via binary search on range count, matching Go's avl.SequenceSet
semantics with a simpler implementation.

- Add SequenceSet.cs: sorted-range compressed set with Add/Remove/Contains/Count/Clear
  and IEnumerable<ulong> in ascending order. Binary search for all O(log n) ops.
- Replace HashSet<ulong> _deleted and _skipSequences in MsgBlock with SequenceSet.
- Add secureErase parameter (default false) to MsgBlock.Delete(): when true, payload
  bytes are overwritten with RandomNumberGenerator.Fill() before the delete record is
  written, making original content unrecoverable on disk.
- Update FileStore.DeleteInBlock() to propagate secureErase flag.
- Update FileStore.EraseMsg() to use secureErase: true via block layer instead of
  delegating to RemoveMsg().
- Add SequenceSetTests.cs: 25 tests covering Add, Remove, Contains, Count, range
  compression, gap filling, bridge merges, enumeration, boundary values, round-trip.
- Add FileStoreTombstoneTrackingTests.cs: 12 tests covering SequenceSet tracking in
  MsgBlock, tombstone persistence through RebuildIndex recovery, secure erase
  payload overwrite verification, and FileStore.EraseMsg integration.

Go reference: filestore.go:5267 (removeMsg), filestore.go:5890 (eraseMsg),
              avl/seqset.go (SequenceSet).
2026-02-25 08:02:44 -05:00
Joseph Doherty
646a5eb2ae feat: add atomic file writer with SemaphoreSlim for crash-safe state writes (Gap 1.6)
- Add AtomicFileWriter static helper: writes to {path}.{random}.tmp, flushes,
  then File.Move(overwrite:true) — concurrent-safe via unique temp path per call
- Add _stateWriteLock (SemaphoreSlim 1,1) to FileStore; dispose in both Dispose
  and DisposeAsync paths
- Promote WriteStreamState to async WriteStreamStateAsync using AtomicFileWriter
  under the write lock; FlushAllPending now returns Task
- Update IStreamStore.FlushAllPending signature to Task; fix MemStore no-op impl
- Fix FileStoreCrashRecoveryTests to await FlushAllPending (3 sync→async tests)
- Add 9 AtomicFileWriterTests covering create, no-tmp-remains, overwrite,
  concurrent safety, memory overload, empty data, and large payload
2026-02-25 07:55:33 -05:00
Joseph Doherty
5beeb1b3f6 feat: add checksum validation on MsgBlock read path (Gap 1.5)
Add _lastChecksum field and LastChecksum property to MsgBlock tracking
the XxHash64 checksum of the last written record (Go: msgBlock.lchk,
filestore.go:2204). Capture the checksum from the encoded record trailer
on every Write/WriteAt/WriteSkip call. Read-path validation happens
naturally through the existing MessageRecord.Decode checksum check.
2026-02-25 07:50:03 -05:00
Joseph Doherty
be432c3224 feat(filestore): implement StoreRawMsg, LoadPrevMsg, Type, Stop on FileStore
Complete IStreamStore Batch 1 — all core operations now have FileStore
implementations instead of throwing NotSupportedException:
- StoreRawMsg: caller-specified seq/ts for replication/mirroring
- LoadPrevMsg: backward scan for message before given sequence
- Type: returns StorageType.File
- Stop: flush + dispose blocks, reject further writes

14 new tests in FileStoreStreamStoreTests.
2026-02-25 01:20:03 -05:00
Joseph Doherty
f031edb97e feat(filestore): implement FlushAllPending with atomic stream state writes
Add FlushAllPending() to FileStore, fulfilling the IStreamStore interface
contract. The method flushes the active MsgBlock to disk and atomically
writes a stream.state checkpoint using write-to-temp + rename, matching
Go's flushPendingWritesUnlocked / writeFullState pattern.

Add FileStoreCrashRecoveryTests with 5 tests covering:
- FlushAllPending flushes block data to .blk file
- FlushAllPending writes a valid atomic stream.state JSON checkpoint
- FlushAllPending is idempotent (second call overwrites with latest state)
- Recovery prunes messages backdated past the MaxAgeMs cutoff
- Recovery handles a tail-truncated block without throwing

Reference: golang/nats-server/server/filestore.go:5783-5842
2026-02-25 01:07:32 -05:00
Joseph Doherty
51ebded300 fix: correct MaxBytes enforcement and consumer start sequence after purge
StreamManager.Capture now accounts for full message size (subject +
payload + 16-byte overhead) when checking MaxBytes, matching Go's
memStoreMsgSize. PullConsumerEngine uses stream FirstSeq instead of
hardcoded 1 for DeliverAll after purge. Fix 6 tests with Go parity
assertions and updated MaxBytes values.
2026-02-24 23:59:37 -05:00
Joseph Doherty
13a3f81d7e fix: resolve MemStore timestamp overflow causing immediate message expiry
DateTime.UtcNow.Ticks * 100L overflows long (Ticks ~6.38e17, x100 =
6.38e19 > long.MaxValue 9.22e18), producing corrupted timestamps that
cause PruneExpiredMessages to immediately expire all messages when
MaxAgeMs is set. Use Unix epoch offset to match Go's time.Now().UnixNano().
2026-02-24 23:08:39 -05:00
Joseph Doherty
f45c76543a feat: add store interface contract tests & fix Compact dmap cleanup (Task 4)
Port Go store_test.go contract tests for IStreamStore interface using
MemStore. Fix CompactInternal to correctly walk forward to find new
FirstSeq before backward cleanup (matching Go behavior).

21 new tests ported from store_test.go.
2026-02-24 20:38:09 -05:00
Joseph Doherty
f35961abea feat: add MemStore Go-parity methods & 25 new tests (Task 3)
Port Go memstore sync interface: Compact, Truncate, PurgeEx, SkipMsgs,
LoadNextMsg, NumPending, MultiLastSeqs, AllLastSeqs, SubjectsTotals,
SubjectsState, GetSeqFromTime, NextWildcardMatch, NextLiteralMatch,
MessageTTL, UpdateConfig. Fix RestoreSnapshotAsync to handle gapped
sequences, update MsgSize to include subject length + 16 overhead
(Go parity).

25 new tests ported from memstore_test.go.
2026-02-24 20:17:42 -05:00
Joseph Doherty
7eb06c8ac5 feat: add FileStore tombstone, TTL & consumer state persistence (Task 2)
Port Go filestore tombstone/deletion tests, consumer state encode/decode,
consumer file store persistence, and message TTL enforcement. Adds
ConsumerStateCodec and ConsumerFileStore implementations.

17 new tests ported from filestore_test.go.
2026-02-24 20:17:35 -05:00
Joseph Doherty
b4ad71012f test(parity): port FileStore recovery & compaction tests (T1) + DB update
Ports 34 Go FileStore tests from filestore_test.go to
FileStoreRecovery2Tests.cs (31 pass, 4 skipped). Tests cover block
recovery, compaction, PSIM indexing, skip-msg handling, TTL expiry,
corrupt index/state detection, and read-only permission checks.

Updates docs/test_parity.db with mapped/skipped status for all 34 tests.
2026-02-24 19:39:40 -05:00
Joseph Doherty
a245bd75a7 feat(storage): port FileStore Go tests and add sync methods (Go parity)
Add 67 Go-parity tests from filestore_test.go covering:
- SkipMsg/SkipMsgs sequence reservation
- RemoveMsg/EraseMsg soft-delete
- LoadMsg/LoadLastMsg/LoadNextMsg message retrieval
- AllLastSeqs/MultiLastSeqs per-subject last sequences
- SubjectForSeq reverse lookup
- NumPending with filters and last-per-subject mode
- Recovery watermark preservation after purge
- FastState NumDeleted/LastTime correctness
- PurgeEx with empty subject + keep parameter
- Compact _first watermark tracking
- Multi-block operations and state verification

Implements missing IStreamStore sync methods on FileStore:
RemoveMsg, EraseMsg, SkipMsg, SkipMsgs, LoadMsg, LoadLastMsg,
LoadNextMsg, AllLastSeqs, MultiLastSeqs, SubjectForSeq, NumPending.

Adds MsgBlock.WriteSkip() for tombstone sequence reservation.
Adds IDisposable to FileStore for synchronous test disposal.
2026-02-24 14:43:06 -05:00
Joseph Doherty
d0068b121f feat(storage): add write cache and TTL scheduling (Go parity)
Add MsgBlock write cache (mirrors Go's msgBlock.cache) to serve reads
for recently-written records without disk I/O; cleared on block seal via
RotateBlock. Add HashWheel-based TTL expiry in FileStore (ExpireFromWheel /
RegisterTtl), replacing the O(n) linear scan on every append with an
O(expired) wheel scan. Implement StoreMsg sync method with per-message TTL
override support. Add 10 tests covering cache hits/eviction, wheel expiry,
retention, StoreMsg seq/ts, per-msg TTL, and recovery re-registration.
2026-02-24 13:54:37 -05:00
Joseph Doherty
b0b64292b3 feat(storage): add tombstone tracking and purge operations (Go parity)
Implement PurgeEx, Compact, Truncate, FilteredState, SubjectsState,
SubjectsTotals, State, FastState, GetSeqFromTime on FileStore. Add
MsgBlock.IsDeleted, DeletedSequences, EnumerateNonDeleted. Includes
wildcard subject support via SubjectMatch for all filtered operations.
2026-02-24 13:45:17 -05:00
Joseph Doherty
2816e8f048 feat(storage): rewrite FileStore to use block-based MsgBlock storage
Replace JSONL persistence with real MsgBlock-based block files (.blk).
FileStore now acts as a block manager that creates, seals, and rotates
MsgBlocks while maintaining an in-memory cache for fast reads/queries.

Key changes:
- AppendAsync writes transformed payloads to MsgBlock via WriteAt
- Block rotation occurs when active block reaches size limit
- Recovery scans .blk files and rebuilds in-memory state from records
- Legacy JSONL migration: existing messages.jsonl data is automatically
  converted to block files on first open, then JSONL is deleted
- PurgeAsync disposes and deletes all block files
- RewriteBlocks rebuilds blocks from cache (used by trim/restore)
- InvalidDataException propagates during recovery (wrong encryption key)

MsgBlock.WriteAt added to support explicit sequence numbers and timestamps,
needed when rewriting blocks with non-contiguous sequences (after removes).

Tests updated:
- New FileStoreBlockTests.cs with 9 tests for block-specific behavior
- JetStreamFileStoreCompressionEncryptionParityTests updated to read
  FSV1 magic from .blk files instead of messages.jsonl
- JetStreamFileStoreDurabilityParityTests updated to verify .blk files
  instead of index.manifest.json

All 3,562 tests pass (3,535 passed + 27 skipped, 0 failures).
2026-02-24 12:39:32 -05:00
Joseph Doherty
09252b8c79 feat(storage): add MsgBlock block-based message storage unit
MsgBlock is the unit of storage in the file store — a single append-only
block file containing sequentially written binary message records. Blocks
are sealed (read-only) when they reach a configurable byte-size limit.

Key features:
- Write: appends MessageRecord-encoded messages with auto-incrementing
  sequence numbers and configurable first sequence offset
- Read: positional I/O via RandomAccess.Read for concurrent reader safety
- Delete: soft-delete with on-disk persistence (re-encodes flags byte +
  checksum so deletions survive recovery)
- Recovery: rebuilds in-memory index by scanning block file using
  MessageRecord.MeasureRecord for record boundary detection
- Thread safety: ReaderWriterLockSlim allows concurrent reads during writes

Also adds MessageRecord.MeasureRecord() — computes a record's byte length
by parsing varint field headers without full decode, needed for sequential
record scanning during block recovery.

Reference: golang/nats-server/server/filestore.go:217-267 (msgBlock struct)

12 tests covering write, read, delete, seal, recovery, concurrency,
and custom sequence offsets.
2026-02-24 12:21:33 -05:00
Joseph Doherty
17731e2af5 feat(storage): add binary message record encoding (Go parity)
Add MessageRecord class with Encode/Decode for the binary wire format
used in JetStream file store blocks. Uses varint-encoded lengths,
XxHash64 checksums, and a flags byte for deletion markers.

Go reference: filestore.go:6720-6724, 8180-8250, 8770-8777

13 tests covering round-trip, headers, checksum validation, corruption
detection, varint encoding, deleted flag, empty/large payloads.
2026-02-24 12:12:15 -05:00
Joseph Doherty
3e972f217e feat: add FileStore 6-way permutation tests (Go testFileStoreAllPermutations parity)
Port 20 tests across the {NoCipher, ChaCha, Aes} x {NoCompression, S2Compression}
matrix from Go's testFileStoreAllPermutations (filestore_test.go:55), yielding 120
total Theory executions in FileStorePermutationTests.cs.

Also fix PushFrame.Subject property missing compilation error that was blocking
the test build (JetStreamConsumerDeliveryEdgeTests.cs:119).

Tests covered (each runs 6x):
  Store_and_load_basic                       TestFileStoreBasics:86
  Store_multiple_messages_load_by_sequence   TestFileStoreBasics:86
  LoadLastBySubject_returns_most_recent_for_subject
  Remove_single_message_updates_state        TestFileStoreBasics:129
  Purge_clears_all_messages                  TestFileStorePurge:710
  TrimToMaxMessages_enforces_limit           TestFileStoreMsgLimitBug:518
  Block_rotation_when_exceeding_block_size   TestFileStoreAndRetrieveMultiBlock:1527
  GetState_returns_correct_counts            TestFileStoreBasics:104
  Snapshot_and_restore_round_trip            TestFileStoreSnapshot:1799
  ListAsync_returns_ordered_messages         TestFileStoreTimeStamps:683
  MaxAge_prunes_expired_messages             TestFileStoreAgeLimit:616
  Recovery_after_reopen_preserves_messages   TestFileStoreBasicWriteMsgsAndRestore:181
  Large_payload_store_and_load               64 KiB random payload variant
  Multiple_subjects_filter_by_subject        TestFileStoreBasics multi-subject
  Sequential_writes_maintain_ordering        TestFileStoreSelectNextFirst:304
  Store_creates_files_on_disk                disk-presence variant
  Write_and_read_same_block                  TestFileStoreWriteAndReadSameBlock:1510
  Stored_messages_have_non_decreasing_timestamps TestFileStoreTimeStamps:683
  Remove_out_of_order_collapses_first_seq    TestFileStoreCollapseDmap:1561
  Snapshot_after_removes_preserves_remaining TestFileStoreSnapshot:1904
2026-02-24 06:43:48 -05:00
Joseph Doherty
9cc9888bb4 feat: add S2 compression and AEAD encryption for FileStore (Go parity)
Replace Deflate+XOR with IronSnappy S2 block compression and ChaCha20-Poly1305 / AES-256-GCM
AEAD encryption, matching golang/nats-server/server/filestore.go. Introduces FSV2 envelope
format alongside existing FSV1 for backward compatibility. Adds 55 new tests across
S2CodecTests, AeadEncryptorTests, and FileStoreV2Tests covering all 6 cipher×compression
permutations, tamper detection, and legacy format round-trips.
2026-02-24 06:29:34 -05:00
Joseph Doherty
3ff801865a feat: Waves 3-5 — FileStore, RAFT, JetStream clustering, and concurrency tests
Add comprehensive Go-parity test coverage across 3 subsystems:
- FileStore: basic CRUD, limits, purge, recovery, subjects, encryption,
  compression, MemStore (161 tests, 24 skipped for not-yet-implemented)
- RAFT: core types, wire format, election, log replication, snapshots
  (95 tests)
- JetStream Clustering: meta controller, stream/consumer replica groups,
  concurrency stress tests (90 tests)

Total: ~346 new test annotations across 17 files (+7,557 lines)
Full suite: 2,606 passing, 0 failures, 27 skipped
2026-02-23 22:55:41 -05:00
Joseph Doherty
28d379e6b7 feat: phase B distributed substrate test parity — 39 new tests across 5 subsystems
FileStore basics (4), MemStore/retention (10), RAFT election/append (16),
config reload parity (3), monitoring endpoints varz/connz/healthz (6).
972 total tests passing, 0 failures.
2026-02-23 19:41:30 -05:00