Controlling Parallelism with Wolverine Background Processing

A couple weeks back I started a new blog series meant to explore Wolverine’s capabilities for background processing. Working in very small steps and only one new concept at a time, the first time out just showed how to set up Wolverine inside a new ASP.Net Core web api service and immediately use it for offloading some processing from HTTP endpoints to background processing by using Wolverine’s local queues and message handlers for background processing. In the follow up post, I added durability to the background processing so that our work being executed in the background would be durable even in the face of application restarts.

In this post, let’s look at how Wolverine allows you to either control the parallelism of your background processing, or restrict the processing to be strictly sequential.

To review, in previous posts we were “publishing” a SignupRequest message from a Minimal API endpoint to Wolverine like so:

app.MapPost("/signup", (SignUpRequest request, IMessageBus bus) 
    => bus.PublishAsync(request));

In this particular case, our application has a message handler for SignupRequest, so Wolverine has a sensible default behavior of publishing the message to a local, in memory queue where each message will be processed in a separate thread from the original HTTP request, and do so asynchronously in the background.

So far, so good? By default, each message type gets its own local, in memory queue, with a default “maximum degree of parallelism” equal to the number of detected processors (Environment.ProcessorCount). In addition, the local queues do not enforce strict ordering by default.

But now, what if you do need to strict sequential ordering? Or if you want to restrict or expand the number of parallel messages that can be processed? Or the get really wild, constrain some messages to running sequentially while other messages run in parallel?

First, let’s see how we could alter the parallelism of our SignUpRequest to an absurd degree and say that up to 20 messages could theoretically be processed at one time by the system. We’ll do that by breaking into the UseWolverine() configuration and adding this:

builder.Host.UseWolverine(opts =>
{
    // The other stuff...

    // Make the SignUpRequest messages be published with even 
    // more parallelization!
    opts.LocalQueueFor<SignUpRequest>()
        
        // A maximum of 20 at a time because why not!
        .MaximumParallelMessages(20);
});

Easy enough, but now let’s say that we want all logical event messages in our system to be handled in the sequential order that our process publishes these messages. An easy way to do that with Wolverine is to have each event message type implement Wolverine’s IEvent marker interface like so:

public record Event1 : IEvent;
public record Event2 : IEvent;
public record Event3 : IEvent;

To be honest, the IEvent and corresponding IMessage and ICommand interfaces were added to Wolverine originally just to make it easier to transition a codebase from using NServiceBus to Wolverine, but those types have little actual meaning to Wolverine. The only way that Wolverine even uses them is for the purpose of “knowing” that a type is an outbound message so that Wolverine can preview the message routing for a type implementing one of these interfaces automatically in diagnostics.

Revisiting our UseWolverine() code block again, we’ll add that publishing rule like this:

builder.Host.UseWolverine(opts =>
{
    // Other stuff...

    opts.Publish(x =>
    {
        x.MessagesImplementing<IEvent>();
        x.ToLocalQueue("events")
            // Force every event message to be processed in the 
            // strict order they are enqueued, and one at a 
            // time
            .Sequential();
        });
});

With the code above, our application would be publishing every single message where the message type implements IEvent to that one local queue named “events” that has been configured to process messages in strict sequential order.

Summary and What’s Next

Wolverine makes it very easy to do work in background processing within your application, and even to easily control the desired parallelism in your application, or to make a subset of messages be processed in strict sequential order when that’s valuable instead.

To be honest, this series is what I go to when I feel like I need to write more Critter Stack content for the week, so it might be a minute or two before there’s a follow up. There’ll be at least two more posts, one on scheduling message execution and an example of using the local processing capabilities in Wolverine to implement the producer/consumer pattern.

Leave a comment