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
This commit is contained in:
Joseph Doherty
2026-03-13 15:09:21 -04:00
parent 82cc3ec841
commit 7404ecdb0e
7 changed files with 106 additions and 30 deletions
@@ -32,6 +32,13 @@ public interface IStreamStore
// Existing MemStore/FileStore implementations return this type.
ValueTask<ApiStreamState> GetStateAsync(CancellationToken ct);
// Cached state properties — avoid GetStateAsync on the publish hot path.
// These are maintained incrementally by FileStore/MemStore and are O(1).
ulong LastSeq => throw new NotSupportedException("LastSeq not implemented.");
ulong MessageCount => throw new NotSupportedException("MessageCount not implemented.");
ulong TotalBytes => throw new NotSupportedException("TotalBytes not implemented.");
ulong FirstSeq => throw new NotSupportedException("FirstSeq not implemented.");
// -------------------------------------------------------------------------
// Go-parity sync interface — mirrors server/store.go StreamStore
// Default implementations throw NotSupportedException so existing