Hangfire Pro: progress of batch job with many child jobs

Hi,

I’m using the batch functionality of Hangfire Pro (which is awesome by the way!). We have a ParentBatch which enqueues many ChildJobs.
I need to show the progress of the ParentBatch in our UI.

What is the easiest way to do this?

I read some suggestions like:

  • using a BackgroundProcess
  • using the Monitoring API (however this doesn’t seem to contain the information I’m looking for)
  • implementing your own counter, which needs something done in each child job
  • I saw the other post ‘Track custom progress’ but this does not help either

The Hangfire dashboard is able to show a progress bar for BatchJobs, so it must be possible without adding custom code. I’ve been digging in the hangfire sql tables and found some Counters (key ‘bulk:Hangfire-2017-4:succeededJobs’) etc that might be used for progress.

Can you please help?
Thank you in advance!

Hi @tomboo. Yes, you can use Hangfire API to get the details. Dashboard page uses the following code to determine the numbers. Total number is the sum of all the given numbers. Total number of non-finished jobs is created + pending + processing, and so on.

using (var connection = Storage.GetConnection())
{
    var storageConnection = connection as JobStorageConnection;

    created = storageConnection.GetSetCount($"batch:{BatchId}:created") + storageConnection.GetSetCount($"batch:{BatchId}:created:batches");
    pending = storageConnection.GetSetCount($"batch:{BatchId}:pending") + storageConnection.GetSetCount($"batch:{BatchId}:pending:batches");
    processing = storageConnection.GetSetCount($"batch:{BatchId}:processing") + storageConnection.GetSetCount($"batch:{BatchId}:processing:batches");
    succeeded = storageConnection.GetSetCount($"batch:{BatchId}:succeeded") + storageConnection.GetSetCount($"batch:{BatchId}:succeeded:batches");
    finished = storageConnection.GetSetCount($"batch:{BatchId}:finished") + storageConnection.GetSetCount($"batch:{BatchId}:finished:batches");
}

However, if you want to show those numbers to your users (not to your administrators), it is always better to add dedicated entities to your domain logic that will provide such details without querying Hangfire itself. This will protect your code from changes in Hangfire itself (these are breaking ones, and will only be in major releases), and will allow you to use queries whatever you want.

For example, if you send mass newsletter and using SQL Server for your application’s primary data, and use batches for campaigns, it’s better to create table Campaign with Id, Template, and CreatedAt and table Letter with columns Id, CampaignId, Name, Email and Status (sent/unsent).

Before creating jobs you create records in that tables, and tell Hangfire to use Letter's Id when enqueueing the SendLetter(int letterId) method that will look like this in pseudo-code:

public void SendLetter(int letterId)
{
    // RepeatableRead is to lock the row and hold it, it is possible that
    // under rare circumstances another worker will pick up a job with 
    // the same letterId.
    using (var transaction = BeginTransaction(IsolationLevel.RepeatableRead))
    {
        var letter = GetLetterById(letterId);
        if (letter == null) return;

        var campaign = GetCampaignById(letter.CampaignId);
        if (campaign == null) throw new CampaignAbsentException();

        SmtpSend(letter.Name, letter.Email, campaign.Template);
        ChangeLetterStatus(letterId, letterStatus.Sent);

        transaction.Commit();
    }
}

And create campaigns and letters before creating your jobs:

var transaction = BeginTransaction();
var campaign = CreateCampaign();
var letter = CreateLetter();

var batch = CreateBatch(campaignId);
var job = CreateJob(letterId);

transaction.Commit();

And now you can create use regular T-Sql to query how many letters are still unsent, what are the top campaigns by the number of letters, etc. And this is possible, because now your domain logic now knows that some of your entities may be processed or non-processed yet.