Skip to content

Commit 50fedec

Browse files
committed
Performance change to improve throughput
Reduce thread contention and await states This change currently breaks the maximumMessageBuffer
1 parent f90c9d0 commit 50fedec

File tree

1 file changed

+41
-30
lines changed

1 file changed

+41
-30
lines changed

src/kafka-net/Producer.cs

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Threading;
56
using System.Threading.Tasks;
@@ -23,17 +24,22 @@ public class Producer : IMetadataQueries
2324

2425
private readonly CancellationTokenSource _stopToken = new CancellationTokenSource();
2526
private readonly int _maximumAsyncRequests;
26-
private readonly int _maximumMessageBuffer;
2727
private readonly AsyncCollection<TopicMessage> _asyncCollection;
2828
private readonly SemaphoreSlim _semaphoreMaximumAsync;
29-
private readonly SemaphoreSlim _boundedCapacitySemaphore;
3029
private readonly IMetadataQueries _metadataQueries;
3130
private readonly Task _postTask;
3231

32+
private int _inFlightMessageCount = 0;
33+
3334
/// <summary>
3435
/// Get the number of messages sitting in the buffer waiting to be sent.
3536
/// </summary>
36-
public int BufferCount { get { return _maximumMessageBuffer - _boundedCapacitySemaphore.CurrentCount; } }
37+
public int BufferCount { get { return _asyncCollection.Count; } }
38+
39+
/// <summary>
40+
/// Get the number of messages staged for Async upload.
41+
/// </summary>
42+
public int InFlightMessageCount { get { return _inFlightMessageCount; } }
3743

3844
/// <summary>
3945
/// Get the number of active async threads sending messages.
@@ -78,11 +84,9 @@ public Producer(IBrokerRouter brokerRouter, int maximumAsyncRequests = MaximumAs
7884
{
7985
BrokerRouter = brokerRouter;
8086
_maximumAsyncRequests = maximumAsyncRequests;
81-
_maximumMessageBuffer = maximumMessageBuffer;
8287
_metadataQueries = new MetadataQueries(BrokerRouter);
8388
_asyncCollection = new AsyncCollection<TopicMessage>();
8489
_semaphoreMaximumAsync = new SemaphoreSlim(maximumAsyncRequests, maximumAsyncRequests);
85-
_boundedCapacitySemaphore = new SemaphoreSlim(maximumMessageBuffer, maximumMessageBuffer);
8690

8791
BatchSize = DefaultBatchSize;
8892
BatchDelayTime = TimeSpan.FromMilliseconds(DefaultBatchDelayMS);
@@ -104,10 +108,11 @@ public Producer(IBrokerRouter brokerRouter, int maximumAsyncRequests = MaximumAs
104108
/// <param name="timeout">Interal kafka timeout to wait for the requested level of ack to occur before returning. Defaults to 1000ms.</param>
105109
/// <param name="codec">The codec to apply to the message collection. Defaults to none.</param>
106110
/// <returns>List of ProduceResponses from each partition sent to or empty list if acks = 0.</returns>
107-
public async Task<List<ProduceResponse>> SendMessageAsync(string topic, IEnumerable<Message> messages, Int16 acks = 1,
111+
public Task<List<ProduceResponse>> SendMessageAsync(string topic, IEnumerable<Message> messages, Int16 acks = 1,
108112
TimeSpan? timeout = null, MessageCodec codec = MessageCodec.CodecNone)
109113
{
110-
if (_stopToken.IsCancellationRequested) throw new ObjectDisposedException("Cannot send new documents as producer is disposing.");
114+
if (_stopToken.IsCancellationRequested)
115+
throw new ObjectDisposedException("Cannot send new documents as producer is disposing.");
111116
if (timeout == null) timeout = TimeSpan.FromMilliseconds(DefaultAckTimeoutMS);
112117

113118
var batch = messages.Select(message => new TopicMessage
@@ -119,20 +124,17 @@ public async Task<List<ProduceResponse>> SendMessageAsync(string topic, IEnumera
119124
Message = message
120125
}).ToList();
121126

122-
foreach (var item in batch)
123-
{
124-
item.Tcs.Task.ContinueWith(t => _boundedCapacitySemaphore.Release(), TaskContinuationOptions.ExecuteSynchronously);
125-
_boundedCapacitySemaphore.Wait(_stopToken.Token);
126-
_asyncCollection.Add(item);
127-
}
127+
_asyncCollection.AddRange(batch);
128128

129-
var results = new List<ProduceResponse>();
130-
foreach (var topicMessage in batch)
131-
{
132-
results.Add(await topicMessage.Tcs.Task.ConfigureAwait(false));
133-
}
129+
return Task.WhenAll(batch.Select(x => x.Tcs.Task))
130+
.ContinueWith(t =>
131+
{
132+
t.ThrowOnFault();
134133

135-
return results.Distinct().ToList();
134+
return batch.Select(topicMessage => topicMessage.Tcs.Task.Result)
135+
.Distinct()
136+
.ToList();
137+
});
136138
}
137139

138140
/// <summary>
@@ -181,6 +183,8 @@ private async Task BatchSendAsync()
181183
{
182184
try
183185
{
186+
await _asyncCollection.OnHasDataAvailable(_stopToken.Token).ConfigureAwait(false);
187+
184188
batch = await _asyncCollection.TakeAsync(BatchSize, BatchDelayTime, _stopToken.Token).ConfigureAwait(false);
185189
}
186190
catch (OperationCanceledException ex)
@@ -229,16 +233,18 @@ private async Task BatchSendAsync()
229233
}
230234
}
231235

232-
private async Task ProduceAndSendBatchAsync(List<TopicMessage> batchs, CancellationToken cancellationToken)
236+
private async Task ProduceAndSendBatchAsync(List<TopicMessage> messages, CancellationToken cancellationToken)
233237
{
238+
Interlocked.Add(ref _inFlightMessageCount, messages.Count);
239+
234240
//we must send a different produce request for each ack level and timeout combination.
235-
foreach (var ackLevelBatch in batchs.GroupBy(batch => new { batch.Acks, batch.Timeout }))
241+
foreach (var ackLevelBatch in messages.GroupBy(batch => new { batch.Acks, batch.Timeout }))
236242
{
237243
var messageByRouter = ackLevelBatch.Select(batch => new
238-
{
239-
TopicMessage = batch,
240-
Route = BrokerRouter.SelectBrokerRoute(batch.Topic, batch.Message.Key),
241-
})
244+
{
245+
TopicMessage = batch,
246+
Route = BrokerRouter.SelectBrokerRoute(batch.Topic, batch.Message.Key),
247+
})
242248
.GroupBy(x => new { x.Route, x.TopicMessage.Topic, x.TopicMessage.Codec });
243249

244250
var sendTasks = new List<BrokerRouteSendBatch>();
@@ -269,8 +275,7 @@ private async Task ProduceAndSendBatchAsync(List<TopicMessage> batchs, Cancellat
269275
};
270276

271277
//ensure the async is released as soon as each task is completed
272-
brokerSendTask.Task.ContinueWith(t => { _semaphoreMaximumAsync.Release(); }, cancellationToken,
273-
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
278+
brokerSendTask.Task.ContinueWith(t => { _semaphoreMaximumAsync.Release(); }, cancellationToken);
274279

275280
sendTasks.Add(brokerSendTask);
276281
}
@@ -293,11 +298,17 @@ private async Task ProduceAndSendBatchAsync(List<TopicMessage> batchs, Cancellat
293298
{
294299
foreach (var topicMessageBatch in ackLevelBatch)
295300
{
296-
topicMessageBatch.Tcs.TrySetException(new KafkaApplicationException("An exception occured while executing a send operation against {0}. Exception:{1}",
297-
failedTask.Route, failedTask.Task.Exception));
301+
topicMessageBatch.Tcs.TrySetException(
302+
new KafkaApplicationException(
303+
"An exception occured while executing a send operation against {0}. Exception:{1}",
304+
failedTask.Route, failedTask.Task.Exception));
298305
}
299306
}
300307
}
308+
finally
309+
{
310+
Interlocked.Add(ref _inFlightMessageCount, messages.Count * -1);
311+
}
301312
}
302313
}
303314

@@ -312,7 +323,7 @@ public void Dispose()
312323
using (_metadataQueries)
313324
{
314325
}
315-
}
326+
}
316327
#endregion
317328
}
318329

0 commit comments

Comments
 (0)