perf: batch flush signaling and fetch path optimizations (Round 6)
Implement Go's pcd (per-client deferred flush) pattern to reduce write-loop wakeups during fan-out delivery, optimize ack reply string construction with stack-based formatting, cache CompiledFilter on ConsumerHandle, and pool fetch message lists. Durable consumer fetch improves from 0.60x to 0.74x Go.
This commit is contained in:
@@ -375,6 +375,32 @@ public sealed class ConsumerManager : IDisposable
|
||||
|
||||
public sealed record ConsumerHandle(string Stream, ConsumerConfig Config)
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiled filter derived from Config. Cached per-instance; invalidated when Config
|
||||
/// changes (either via <c>with { Config = newConfig }</c> or in-place mutation of
|
||||
/// FilterSubject/FilterSubjects).
|
||||
/// Go reference: consumer.go — filter subjects resolved once at consumer creation.
|
||||
/// </summary>
|
||||
private Consumers.CompiledFilter? _compiledFilter;
|
||||
private string? _compiledFilterSubject;
|
||||
private int _compiledFilterSubjectsCount;
|
||||
public Consumers.CompiledFilter CompiledFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
// Detect both reference change (with expression) and in-place mutation
|
||||
if (_compiledFilter == null
|
||||
|| _compiledFilterSubject != Config.FilterSubject
|
||||
|| _compiledFilterSubjectsCount != Config.FilterSubjects.Count)
|
||||
{
|
||||
_compiledFilter = Consumers.CompiledFilter.FromConfig(Config);
|
||||
_compiledFilterSubject = Config.FilterSubject;
|
||||
_compiledFilterSubjectsCount = Config.FilterSubjects.Count;
|
||||
}
|
||||
return _compiledFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong NextSequence { get; set; } = 1;
|
||||
public bool Paused { get; set; }
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@ public sealed class CompiledFilter
|
||||
|
||||
public sealed class PullConsumerEngine
|
||||
{
|
||||
// Reusable fetch buffer to avoid per-fetch List allocation.
|
||||
// Go reference: consumer.go — reuses slice backing array across fetch calls.
|
||||
[ThreadStatic] private static List<StoredMessage>? t_fetchBuf;
|
||||
// Go: consumer.go — cluster-wide pending pull request tracking keyed by reply subject.
|
||||
// Reference: golang/nats-server/server/consumer.go waitingRequestsPending / proposeWaitingRequest
|
||||
private readonly ConcurrentDictionary<string, PullWaitingRequest> _clusterPending =
|
||||
@@ -155,7 +158,11 @@ public sealed class PullConsumerEngine
|
||||
public async ValueTask<PullFetchBatch> FetchAsync(StreamHandle stream, ConsumerHandle consumer, PullFetchRequest request, CancellationToken ct)
|
||||
{
|
||||
var batch = Math.Max(request.Batch, 1);
|
||||
var messages = new List<StoredMessage>(batch);
|
||||
// Use thread-static buffer to avoid per-fetch List allocation.
|
||||
// Results are snapshot'd into PullFetchBatch before the buffer is reused.
|
||||
var messages = t_fetchBuf ??= new List<StoredMessage>();
|
||||
messages.Clear();
|
||||
if (messages.Capacity < batch) messages.Capacity = batch;
|
||||
|
||||
// Go: consumer.go — enforce ExpiresMs timeout on pull fetch requests.
|
||||
// When ExpiresMs > 0, create a linked CancellationTokenSource that fires
|
||||
@@ -215,8 +222,8 @@ public sealed class PullConsumerEngine
|
||||
return new PullFetchBatch(messages);
|
||||
}
|
||||
|
||||
// Use CompiledFilter for efficient multi-filter matching
|
||||
var compiledFilter = CompiledFilter.FromConfig(consumer.Config);
|
||||
// Use cached CompiledFilter from ConsumerHandle (avoids per-fetch allocation)
|
||||
var compiledFilter = consumer.CompiledFilter;
|
||||
var sequence = consumer.NextSequence;
|
||||
|
||||
// Go: consumer.go — MaxBytes caps the total byte payload returned in one pull request
|
||||
@@ -358,7 +365,8 @@ public sealed class PullFetchBatch
|
||||
|
||||
public PullFetchBatch(IReadOnlyList<StoredMessage> messages, bool timedOut = false)
|
||||
{
|
||||
Messages = messages;
|
||||
// Snapshot: caller may reuse the list (ThreadStatic pooling), so take a copy.
|
||||
Messages = messages.Count == 0 ? [] : messages.ToArray();
|
||||
TimedOut = timedOut;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user