Commit Graph

30 Commits

Author SHA1 Message Date
Joseph Doherty
6e91fda7fd perf: Phase 2 lazy StoredMessage materialization in FileStore
Replace eager Dictionary<ulong, StoredMessage> with lightweight
Dictionary<ulong, MessageMeta> to eliminate ~200B StoredMessage
allocation per message on the write path.

- Add MessageMeta struct (BlockId, Subject, PayloadLength, HeaderLength,
  TimestampNs) — ~40B vs ~200B for StoredMessage
- Add MaterializeMessage(seq) for on-demand reconstruction from blocks
- Update all ~60 _messages references to use _meta
- Methods needing full payload (LoadAsync, ListAsync, etc.) call
  MaterializeMessage; metadata-only paths use _meta directly
- Fix MsgBlock.WriteAt to clear stale delete markers on re-write
2026-03-13 15:33:38 -04:00
Joseph Doherty
7404ecdb0e perf: Phase 1 JetStream async file publish optimizations
- Add cached state properties (LastSeq, MessageCount, TotalBytes, FirstSeq)
  to IStreamStore/FileStore/MemStore — eliminates GetStateAsync on publish path
- Add Capture(StreamHandle, ...) overload to StreamManager — eliminates
  double FindBySubject lookup (once in JetStreamPublisher, once in Capture)
- Remove _messageIndexes dictionary from FileStore write path — all lookups
  now use _messages directly, saving ~48B allocation per message
- Add JetStreamPubAckFormatter for hand-rolled UTF-8 success ack formatting —
  avoids JsonSerializer overhead on the hot publish path
- Switch flush loop to exponential backoff (1→2→4→8ms) matching Go server
2026-03-13 15:09:21 -04:00
Joseph Doherty
f57edca5a8 perf: optimize FileStore payload and maintenance paths 2026-03-13 11:21:48 -04:00
Joseph Doherty
9ff5216495 perf: add compact FileStore index metadata 2026-03-13 10:34:31 -04:00
Joseph Doherty
0a4e7a822f perf: eliminate per-message allocations in pub/sub hot path and coalesce outbound writes
Pub/sub 1:1 (16B) improved from 0.18x to 0.50x, fan-out from 0.18x to 0.44x,
and JetStream durable fetch from 0.13x to 0.64x vs Go. Key changes: replace
.ToArray() copy in SendMessage with pooled buffer handoff, batch multiple small
writes into single WriteAsync via 64KB coalesce buffer in write loop, and remove
profiling Stopwatch instrumentation from ProcessMessage/StreamManager hot paths.
2026-03-13 05:09:36 -04:00
Joseph Doherty
4de691c9c5 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.
2026-03-13 03:11:11 -04:00
Joseph Doherty
c30e67a69d Fix E2E test gaps and add comprehensive E2E + parity test suites
- Fix pull consumer fetch: send original stream subject in HMSG (not inbox)
  so NATS client distinguishes data messages from control messages
- Fix MaxAge expiry: add background timer in StreamManager for periodic pruning
- Fix JetStream wire format: Go-compatible anonymous objects with string enums,
  proper offset-based pagination for stream/consumer list APIs
- Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream)
- Add ~1000 parity tests across all subsystems (gaps closure)
- Update gap inventory docs to reflect implementation status
2026-03-12 14:09:23 -04: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
3ab683489e feat(filestore): implement EncodedStreamState, UpdateConfig, ResetState, fix Delete signature
Complete IStreamStore Batch 2 — all remaining interface methods now have
FileStore implementations:
- EncodedStreamState: placeholder returning empty (full codec in Task 9)
- UpdateConfig: no-op placeholder for config hot-reload
- ResetState: no-op (state derived from blocks on construction)
- Delete(bool): now calls Stop() first and matches interface signature
2026-02-25 01:22:43 -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
f402fd364f feat(filestore): implement FlushAllPending with atomic stream state writes
Add FlushAllPending() to FileStore — flushes active MsgBlock to disk and
writes a stream.state checkpoint atomically (write-to-temp + rename).
Go ref: filestore.go:5783 (flushPendingWritesUnlocked / writeFullState).

5 new tests in FileStoreCrashRecoveryTests: flush, state file, idempotent,
TTL recovery, truncated block handling.
2026-02-25 01:14:34 -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
f143295392 feat(filestore): wire AeadEncryptor into MsgBlock for at-rest encryption
Add FileStoreEncryptionTests covering ChaCha20-Poly1305 and AES-GCM
round-trips and wrong-key rejection for the FSV2 AEAD path. Fix
RestorePayload to wrap CryptographicException from AEAD decryption as
InvalidDataException so RecoverBlocks correctly propagates key-mismatch
failures instead of silently swallowing them.
2026-02-25 00:43:57 -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
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
2c9683e7aa feat: upgrade JetStreamService to lifecycle orchestrator
Implements enableJetStream() semantics from golang/nats-server/server/jetstream.go:414-523.

- JetStreamService.StartAsync(): validates config, creates store directory
  (including nested paths via Directory.CreateDirectory), registers all
  $JS.API.> subjects, logs startup stats; idempotent on double-start
- JetStreamService.DisposeAsync(): clears registered subjects, marks not running
- New properties: RegisteredApiSubjects, MaxStreams, MaxConsumers, MaxMemory, MaxStore
- JetStreamOptions: adds MaxStreams and MaxConsumers limits (0 = unlimited)
- FileStoreConfig: removes duplicate StoreCipher/StoreCompression enum declarations
  now that AeadEncryptor.cs owns them; updates defaults to NoCipher/NoCompression
- FileStoreOptions/FileStore: align enum member names with AeadEncryptor.cs
  (NoCipher, NoCompression, S2Compression) to fix cross-task naming conflict
- 13 new tests in JetStreamServiceOrchestrationTests covering all lifecycle paths
2026-02-24 06:03:46 -05:00
Joseph Doherty
921554f410 feat: define StreamStore/ConsumerStore interfaces from Go store.go
Port IStreamStore, IConsumerStore, StoreMsg, StreamState, SimpleState,
ConsumerState, FileStoreConfig, StoreCipher, StoreCompression types.
Rename Models.StreamState → ApiStreamState to avoid namespace conflict.
2026-02-23 21:06:16 -05:00
Joseph Doherty
56177a7099 feat: enforce filestore durability and recovery invariants 2026-02-23 14:51:30 -05:00
Joseph Doherty
377ad4a299 feat: complete jetstream deep operational parity closure 2026-02-23 13:43:14 -05:00
Joseph Doherty
2b64d762f6 feat: execute full-repo remaining parity closure plan 2026-02-23 13:08:52 -05:00
Joseph Doherty
b41e6ff320 feat: execute post-baseline jetstream parity plan 2026-02-23 12:11:19 -05:00
Joseph Doherty
8bce096f55 feat: complete final jetstream parity transport and runtime baselines 2026-02-23 11:04:43 -05:00
Joseph Doherty
f46b331921 feat: complete remaining jetstream parity implementation plan 2026-02-23 10:16:16 -05:00
Joseph Doherty
d73e7e2f88 feat: enforce jetstream retention and limits 2026-02-23 06:04:23 -05:00
Joseph Doherty
788f4254b0 feat: implement jetstream filestore recovery baseline 2026-02-23 06:00:42 -05:00