-
Notifications
You must be signed in to change notification settings - Fork 798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multithreaded Sink #809
Comments
Stephen does a good job of explaining the Producer/Consumer pattern implementation here: http://blog.stephencleary.com/2012/11/async-producerconsumer-queue-using.html |
Hi @jezzsantos, Nothing directly exists OoTB that I am aware of. As you mentioned you could implement via the delegating sink or your own sink implementation. Just out of interest, was the call to the Azure Queue an IO or a network latency issue? (to give you 5x improvement) |
https://github.com/serilog/serilog-sinks-periodicbatching should go some of the way towards it (it's what the other sinks use). The difference from what you've suggested is that the queue used is strictly non-blocking (and thus unbounded) but otherwise should cover what you need, and has the benefit of allowing batched writes for fewer roundtrips. (It'd be interesting to look at a fixed-length/blocking queue, but probably significant work.) |
@merbla thanks for the confirmation. I have started creating a sink like that described here, for general purpose use. Might be useful to community for anyone with slow sinks, that they can just plugin perhaps. An Azure queue should in theory be quick to write to (in terms of network latency), but it is still an HTTP across the network, and that's when in production in the cloud. Primarily for us, the 5X improvement we want to see is locally in the Azure Emulator. And that directly affects the speed of our integration and E2E testing feedback loop. Which is vital to us. Which is why we are going after it. We assume any improvement seen locally will equate to some improvement in production (at scale), so there is more value in it. Besides which, since we are calling our logging on average about 50-100 times in a single REST API call, it makes sense to batch these logging requests in memory before writing to queue, so this optimization is long overdue for us anyhow. |
Guys @merbla @nblumhardt , I have just finished refining and performance testing a
The reasons and motivation for writing this sink is described in the opening post of this issue. I am happy to share this implementation along with its unit tests and integration tests, as a standalone component with the community to use, if someone can simply guide me how best to do that. The code is less than 200 lines long (2 x classes). Here is the gist of the code: https://gist.github.com/jezzsantos/44bf844b27bebafcb64d9947e1896649 |
Hey Jezz, sounds awesome! I haven't had a chance to check out the gist, but in terms of making this available what do you think of packaging this in something like One possible API is: Log.Logger = new LoggerConfiguration()
.WriteTo.Async(wt => wt.File("log.txt"))
.CreateLogger(); The There's enough interest in this kind of thing that I can see potentially being part of Serilog in the long-term, but just standing up a personal repo that we can all help to shape during the development process would be the easiest/quickest way to get started. What do you think? |
I am happy to do as you suggest. wilco |
@nblumhardt might need your help here, to get this to where you need it. I have the extension method:
used like this:
|
public static LoggerConfiguration Async(this LoggerSinkConfiguration configuration, Action<LoggerSinkConfiguration> sinkConfiguration)
{
var sublogger = new LoggerConfiguration();
sinkConfiguration(sublogger.WriteTo);
var wrapper = new BufferedQueueSink((ILogEventSink)sublogger.CreateLogger());
return configuration.Sink(wrapper);
} |
@jezzsantos I haven't thought this through entirely, and Accepting Hope this gets the ball rolling, anyway! |
Thanks here is my first crack at it. https://github.com/jezzsantos/Serilog.Sinks.Async I am working on the Nuget and AppVeyor CI right now |
Shall we move the discussion over there? |
Awesome, sounds great. |
CC @DmitryNaumov - Dmitry, thinking this could be a good option to provide the background file I/O we were talking about, would be great to have your thoughts. Cheers! |
@nblumhardt @jezzsantos I think all of us understand that most of the sinks are slow, especially in multi-threaded applications. File sink is slow, network also slow, even console is slow. And while our implementation approach may differ, looks like we share the idea that we need way to make any given sink "async"/background. This includes managing how often it would flush, maximum memory would consume, how behave if it can't "write" to destination etc. I made a glance look at @jezzsantos implementation and it seems matching to what I just mentioned. One thing which bothers me, but I'm not good at DataFlow, is how it will behave when buffer is full, will it block the caller or drop buffers? I especially don't like blocking, that's why we want "async" sink. So imo if we run into situation when producers are steadily faster than destination sink, I would prefer to be able to provide a "strategy" how to deal in this situation - block, drop or something else. And one thing which may lead to this situation and I mentioned it somewhere before is that to be efficient we need to provide sinks with "batching" method. So when our "async" sink decides it's time to write to destination it can do it in most efficient way. |
Hey @DmitryNaumov, As I understand it, when the buffer is full, the async producer call is async waited on until the consumer catches up. So, not blocked per-se, just async blocked. Can we take this discussion over there: https://github.com/jezzsantos/Serilog.Sinks.Async? I am actually witnessing problems with this implementation in our REST service, that I don't yet understand, and it would be good if we could collaborate on that, over there rather than on this closed thread? |
We are using Serilog for our REST service, between our logger class and an Azure queue.
We have a custom
ILogEventSink
to write the messages to our Azure queue.Each REST service call is resulting in on average 100 logging calls (various things like Tracing, Counting, Auditing etc.) And that has really slowed down the response time of each web service call for us.
If we remove the [final] step in our sink that actually makes the call to write to the Azure queue, a service call completes 5X faster!
So we have concluded that the performance bottleneck for us is having the web service request thread doing the actual writing to the Azure queue in the sink.
We want to offload that I/O bound work to another thread pool thread, and allow the web request thread to get out of logging as soon as possible.
A typical design pattern for this is the Producer/Consumer pattern, where one thread produces data on a shared [thread safe] memory queue (i.e.
BlockingCollection<T>
, orBufferBlock<T>
), and another thread consumes that data and does work with it.Ideally, with serilog, this producer/consumer could be implemented as a delegating
ILogEventSink
that can be configured between SerilogLoggerConfiguration
(producer) and another sink that does the hard (consumer) work (in our case our Azure sink).Does serilog accomadate anything like this?
Basically, we just don't want the same thread that makes the call to
ILogger.*
to be the same thread that runs ourILogEventSink
.What would be the Serilog way to solve this problem?
The text was updated successfully, but these errors were encountered: