Scaling Hangfire: Process More Jobs Concurrently

As you start enqueuing more background jobs with Hangfire, you might need to increase the number of Consumers that can process jobs. Scaling Hangfire can be done in a couple of ways that I’ll explain in this post, along with one tip on what to be aware of when starting to scale out.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Producers & Consumers

First, let’s clear up how Hangfire works with producers and consumers.

A Hangfire producer is creating background jobs but not executing them. This is when you’re using the BackgroundJobClient from the Hangfire.

Once you call Enqueue, the job is stored in Hangfire JobStorage. There are many job storages you can use, such as SQL Server, Redis, and others.

The Hangfire Server, which is a consumer, will get that job from JobStorage and them execute the job.

There are two ways of scaling Hangfire to add more consumers. Worker Threads and Hangfire Servers.

Worker Threads

A Hangfire server can be hosted along with ASP.NET Core, or entirely standalone in a Console app using the Generic Host, which is the example below.

The Hangfire Server uses multiple threads to perform background jobs. Meaning it can process a background job per thread within the Hangfire server. This allows you to execute background jobs concurrently.

By default, the number of threads it uses is 5 per Processor Count. With a maximum of 20.

Math.Min(Environment.ProcessorCount * 5, 20);

However, you can configure this by setting the WorkerCount in the AddHangfireServer()

Constraints

Ultimately your constrained by the host where this Hangfire Server is running. Regardless if you’re running in a container, VM, or Physical Server, you’re going to be constrained by the CPU and Memory of the host. Meaning, you cannot just arbitrarily set the WorkerCount to a very high number as you could max out CPU if you had a high number of concurrent jobs that are CPU intensive.

You’ll have to monitor your application and the background jobs specific to your app to determine what the right number is. The default is a good default.

Hangfire Servers

The second option for scaling Hangfire is to simply run more Hangfire Servers.

Now when running two instances of my hangfire Server, the dashboard shows each server with 30 worker threads. This means our application can process 60 jobs concurrently.

Hangfire fully manages dispatching jobs to the appropriate server. You simply need to add servers to increase consumers, which ultimately increases the number of jobs you can process.

Downstream Services

Once you start scaling out by increasing the overall worker count or adding Hangfire servers, you will want to pay attention to downstream services.

If for example, your background jobs interact with a Database, you’re going to now be adding more load to the database because you’re going to be performing more jobs concurrently.

Just be aware that adding more consumers can move the bottleneck to downstream servers. They also become a constraint when trying to scale your system that’s using Hangfire.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Links

Using Hangfire and MediatR as a Message Dispatcher

Two popular libraries in Hangfire and MediatR can be used together to create a pretty powerful out-of-process messaging dispatcher. I’ve covered this a bit many years ago but I figured I’d give it a refresh since it’s a bit easier in the world of ASP.NET Core.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Hangfire and MediatR Bridge/Wrapper

The first thing you need to do is create a bridge/wrapper around MediatR. At first this may seem completely pointless, but it has a purpose. Hangfire has a lot extensibility in terms of how jobs are executed that are often times defined by using attributes. Because we don’t own the MediatR library, we need to have our own class that we can defined these attributes on.

Here’s a simple start to our Bridge/Wrapper

In the example above, I have an overload for Send() that accepts the jobName as the first parameter. The DisplayName attribute will be used by Hangfire to show in the UI Dashboard the name of the job. This is a simple example of why we need this wrapper.

MediatR Extensions

The next piece of the puzzle is creating extension methods to be able to use Hangfire to create background jobs. In the example below I’ve created Enqueue() extension methods that use the Hangfire BackgroundJobClient to enqueue work using our Bridge/Wrapper

Hangfire Serialization

In the BackgroundJobClient.Enqueue() above, Hangfire will use Json.NET to serialize the IRequest that we are passing to Send() that ultimately gets put into storage. When Hangfire then pulls that job out of storage to perform the work, it needs to deserialize it. Because it’s just an IRequest it has no way to turning that into a concrete type.

To handle this, we need to configure Hangfire to add type handling when it serializes/deserializes.

ConfigureServices

The last thing we need to do is actually configure Hangfire in the ConfigureServices of either your ASP.NET Core Startup or your HostBuilder.

Here’s an example of my Worker process. This is just a console app that is purely a Hangfire server that just processes jobs from Hangfire.

Enqueuing a Request

In order to enqueue a MediatR request to Hangfire is a matter of calling the Enqueue() extension method we created off of IMediator.

In the example above, the HTTP Request to our Controller action will return a 204 NoContent, even though we are throwing in our PlaceOrderHandler because it’s execution is actually done out of the context of the HTTP Request. This could either be in a different thread, an entire different process, or on a totally on a different server).

More

There’s a lot more you can do with this and take it much farther to handle things like event publishing to have each event handler be it’s own job within Hangfire.

You can get all the source code of this running example on my GitHub.

If you have any thoughts or questions, please leave a comment on my blog, Twitter, or on the YouTube video.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Links

Why use MediatR? 3 reasons why and 1 reason not

Why use MediatR? 3 reasons why and 1 reason not

The MediatR library by Jimmy Bogard has become increasingly popular over recent years, and deservedly so. By its own definition, it’s a simple, unambitious mediator implementation in .NET. Why are so many developers using it? Why should you use MediatR? Here are 3 reasons why you should at least consider using it and one reason why shouldn’t.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this one regarding MediatR.

What is MediatR?

For those unfamiliar with MediatR library or the mediator pattern:

In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program’s running behavior.

Reason #1: Decoupling

Most examples I’ve seen using MediatR are with ASP.NET Core, but that doesn’t mean that’s the only framework where it’s useful. The point is decoupling your application code from the top-level framework code. Whatever code is actually in charge of executing your code.

Here’s a example using ASP.NET Core MVC

The point is creating a request object that you pass to MediatR which in turn invokes the correct Handler for that request object.

The PlaceOrderHandler has no references to any types or APIs in ASP.NET Core.

Reason #2: Application Requests

The reason why decoupling from top-level framework code such as ASP.NET Core can be important is to ask yourself: Is the application I’m creating an application or a web application?

There’s a difference between a web application and just an application.

An application can have many different inputs. Not all incoming requests are going to be via HTTP. There may be various ways that interact with your application.

For example, you could have recurring jobs that need to perform specific actions at a given time during the day. Or you may also have work that gets triggered from specific messages you’re picking up from a Message Broker.

There are a number of ways that you may want to invoke behaviors in your system that don’t initiate from an HTTP request.

In my example above with ASP.NET Core, we’re ultimately converting an HTTP request into an Application Request. That Application request is entirely decoupled from any specific top level framework and could be invoke from anywhere.

Using MediatR to create application requests to cross an integration boundary.

Reason #3: Request Pipelines

Once you start thinking about application requests, you can go deeper into creating a pipeline for those requests.

If you’re familiar with ASP.NET Core middleware, the purpose is to define a pipeline for an HTTP request.

You can create the same concept using MediatR behaviors. There are a few different ways to create them but here’s an example of a that implements the IPipelineBehavior

When you call mediator.Send() this behavior is called first prior to your primary handler (PlaceOrderHandler). The RequestHandlerDelegate which is a delegate to call PlaceOrderHandler. This allows us to do primary work before calling it or possibly even short-circuiting and not calling it at all, say if we had some validation issues.

This means you could have any number of pre-processors that can be run prior to the primary handler, or post-processors that can be run after.

Why use MediatR

This is incredibly powerful and allows you to separate different concerns related to a given application request.

Why Not?

The reason you may not want to use MediatR is simply that everything is done in-process. Meaning that wherever process is calling mediator.Send() is also the same process that is executing the relevant Handler for that request.

Udi Dahan the founder of NServiceBus posed this question on Twitter with my response.

Why use MediatR

With everything in-process, this becomes really apparent with Events/Notifications (MediatR calls them Notifications).

A notification can have zero or multiple handlers. However the execution of all the notification handlers are done all in the same process.

Why use MediatR

In the example above, if you publish a notification to MediatR, and there are 3 handlers for that notification, it will execute all 3. But what happens if one of them fails and throws an exception?

That exception will bubble up back to where you published the message via MediatR. How do you want to handle this? Can you re-publish that message again and have the handlers that succeeded run again? What if one of them was sending an email?

You want event/notification handlers to be run in independently in isolation. This is where out-of-process messaging comes in.

In this case, you might want to look at NServiceBus, Brighter, MassTransit, Rebus.

More

If you want more details on how MediatR works, I’ve talked and written about this in my Fat Controller CQRS Diet where I use MediatR as an example on how to decouple your application code from top-level framework code.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design