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:
@@ -24,6 +24,12 @@ public sealed class ConsumerManager : IDisposable
|
||||
/// </summary>
|
||||
public event EventHandler<(string Stream, string Name)>? OnAutoResumed;
|
||||
|
||||
/// <summary>
|
||||
/// Optional reference to the stream manager, used to resolve DeliverPolicy.New
|
||||
/// start sequences at consumer creation time.
|
||||
/// </summary>
|
||||
public StreamManager? StreamManager { get; set; }
|
||||
|
||||
public ConsumerManager(JetStreamMetaGroup? metaGroup = null)
|
||||
{
|
||||
_metaGroup = metaGroup;
|
||||
@@ -47,6 +53,20 @@ public sealed class ConsumerManager : IDisposable
|
||||
if (!JetStreamConfigValidator.IsMetadataWithinLimit(config.Metadata))
|
||||
return JetStreamApiResponse.ErrorResponse(400, "consumer metadata exceeds maximum size");
|
||||
|
||||
// Go: DeliverPolicy.New — snapshot the stream's current last sequence at creation
|
||||
// time so the consumer only sees messages published after this point.
|
||||
// Reference: server/consumer.go — setStartingSequenceForDeliverNew.
|
||||
// We set OptStartSeq but preserve DeliverPolicy.New in the stored config;
|
||||
// the fetch engine uses OptStartSeq when set regardless of policy.
|
||||
if (config.DeliverPolicy == DeliverPolicy.New && StreamManager != null)
|
||||
{
|
||||
if (StreamManager.TryGet(stream, out var streamHandle))
|
||||
{
|
||||
var streamState = streamHandle.Store.GetStateAsync(default).GetAwaiter().GetResult();
|
||||
config.OptStartSeq = streamState.LastSeq + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.FilterSubjects.Count == 0 && !string.IsNullOrWhiteSpace(config.FilterSubject))
|
||||
config.FilterSubjects.Add(config.FilterSubject);
|
||||
|
||||
@@ -302,6 +322,13 @@ public sealed class ConsumerManager : IDisposable
|
||||
return handle.AckProcessor.PendingCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there are any consumers registered for the given stream.
|
||||
/// Used to short-circuit the LoadAsync call on the publish hot path.
|
||||
/// </summary>
|
||||
public bool HasConsumersForStream(string stream)
|
||||
=> _consumers.Keys.Any(k => string.Equals(k.Stream, stream, StringComparison.Ordinal));
|
||||
|
||||
public void OnPublished(string stream, StoredMessage message)
|
||||
{
|
||||
foreach (var handle in _consumers.Values.Where(c => c.Stream == stream && c.Config.Push))
|
||||
|
||||
Reference in New Issue
Block a user