disruptor-net/Disruptor-net

Question about whether to use one ringbuffer with 5 handlers or 5 ringbuffers with one handler each

Closed this issue ยท 8 comments

Hi I'm currently building a system and are planning to use Disruptor for an internal queue.
I would like to know what design pattern you would recommend:

  1. Have one ring buffer where each event has a type to indicate which handler should handle it, and then have X handlers connected that will then consume the events. Note that an event will only be relevant for one handler never more.
  2. Have multiple ring buffers where each ring buffer has one handler. In this scenario each ring buffer will only contain one type of event.

I expect to have around 5 handlers/types of events in this setup.

I have read somewhere that having many ring buffers can end up consuming too many threads and/or cpu - what is too many?

Hope you can help with my question, I have tried searching on google but couldn't find any info.

I'm not well familiarized with the internals yet. From what i know, each disruptor will spin using a dedicated thread. So, 5 threads just to consume the data. How your data will be written? One thread for each ring or the same thread will write in all 5?
If you have enough cpu cores to run all those threads it's not a problem.
I'm not sure about this info but it's what i have learned from using it

The disruptor creates one dedicated thread per handler, so there is no difference between 5 ring buffers having 1 handler each and 1 ring buffer having 5 handlers each in term of CPU usage.

For most applications, I suggest using a single shared disruptor, but it seems that your requirements are quite specific:

  • The disruptor is useful to ensure events are properly sequenced and processed in order. It seems that your handlers are independent and that you do not need a global ordering of events.
  • Multiple disruptors imply multiple ring buffers, so memory usage can be an issue. But if each disruptor processes independent event types, you will create 5 "small" ring buffers instead of 1 large one and the memory usage will be the same.
  • Multiple disruptors imply multiple publications, which could impact publisher performance. But if each disruptor processes independent event types, you will have only one publication per event and there will be no impact on the publishers.
  • If you have a single ring buffer, one slow handler could potentially block the disruptor through backpressure, thus blocking the publication and processing of other event types. With multiple ring buffers, the events processing will not be affected by other event types or handlers.
  • It is simpler to design dedicated event types than multi-purpose event types.
  • Disruptors often contain technical handlers dedicated to resource cleaning, metric publication, logging, etc. If you have multiple disruptors, you might need to duplicate these handlers, which can consume CPU resources. It is not an issue for you if you do not plan to have technical handlers.

So, using dedicated disruptors might be the best option for your use case. However, please note that if your 5 handlers need to access shared components, you will need to make these components thread safe. It is fine if you have very few and very simple shared components, but it can clearly become a bottleneck and a performance issue. There is no point in having multiple handlers if they are always stuck waiting for resources.

The disruptor creates one dedicated thread per handler, so there is no difference between 5 ring buffers having 1 handler each and 1 ring buffer having 5 handlers each in term of CPU usage.

For most applications, I suggest using a single shared disruptor, but it seems that your requirements are quite specific:

* The disruptor is useful to ensure events are properly sequenced and processed in order. It seems that your handlers are independent and that you do not need a global ordering of events.

* Multiple disruptors imply multiple ring buffers, so memory usage can be an issue. But if each disruptor processes independent event types, you will create 5 "small" ring buffers instead of 1 large one and the memory usage will be the same.

* Multiple disruptors imply multiple publications, which could impact publisher performance. But if each disruptor processes independent event types, you will have only one publication per event and there will be no impact on the publishers.

* If you have a single ring buffer, one slow handler could potentially block the disruptor through backpressure, thus blocking the publication and processing of other event types. With multiple ring buffers, the events processing will not be affected by other event types or handlers.

* It is simpler to design dedicated event types than multi-purpose event types.

* Disruptors often contain technical handlers dedicated to resource cleaning, metric publication, logging, etc. If you have multiple disruptors, you might need to duplicate these handlers, which can consume CPU resources. It is not an issue for you if you do not plan to have technical handlers.

So, using dedicated disruptors might be the best option for your use case. However, please note that if your 5 handlers need to access shared components, you will need to make these components thread safe. It is fine if you have very few and very simple shared components, but it can clearly become a bottleneck and a performance issue. There is no point in having multiple handlers if they are always stuck waiting for resources.

In the scenario where I have on Ring Buffer with 5 handlers, does each handler also get its own dedicated thread if I use the AggregateEventHandler class or do the handlers then share the thread?

AggregateEventHandler can be helpful if you want to reduce CPU usage. You have a very reasonable handler count but of course the impact of using 5 dedicated threads depends on your specific environment. Please note that the dedicated threads will not constantly consume CPU unless you use a very aggressive wait strategy. Also, AggregateEventHandler is useful if you want to prevent concurrent event processing to avoid multi-threading issues. But if each event can only be processed by a single handler, you can simply create a custom dispatching event handler:

public class DispatchingEventHandler : IEventHandler<EddiEvent>
{
    // ...
    
    public void OnEvent(EddiEvent data, long sequence, bool endOfBatch)
    {
        switch (data.EventType)
        {
            case EddiEventType.Type1:
                _type1Handler.OnEvent(data, sequence, endOfBatch);
                break;
            
            case EddiEventType.Type2:
                _type2Handler.OnEvent(data, sequence, endOfBatch);
                break;
            
            // ...
        }
    }
}

You can even use a dictionary to resolve the target handler.

The downside of using a single thread is that a very slow handler can impact the processing of other handlers.

Just in case I wasn't clear enough: yes, the handlers will share the same thread if you use AggregateEventHandler. AggregateEventHandler encapsulates a list of handlers as a single handler (basic composite pattern). The disruptor will manage it as a single handler.

Just a final question which of the wait strategies would you consider aggressive wait strategy, is the default okay(as in not aggressive)?

The default wait strategy is not aggressive. The most aggressive wait strategy is BusySpinWaitStrategy, followed by YieldingWaitStrategy and SleepingWaitStrategy.

Thanks for the quick help @ocoanet @andr3marra , it is very much appreciated :)