Skip to main content

JobMaster vs. Hangfire: Benchmark Results

This page backs the recommendations in the Performance Tuning Guide with real, repeatable numbers: a head-to-head comparison of JobMaster against Hangfire, run under matched concurrency, matched resource caps, and matched job volume.

tip

See Benchmark Methodology for how this comparison is set up: fair concurrency matching, fixed resource caps, burst-mode test shape, and a hard rule that every number below is a 3x-repeat average, not a single run.

This comparison is based on 25k-job bursts. Each config name in the tables below links to its full detail page (throughput, latency percentiles, and container resource usage). A follow-up round with a more comfortable worker CPU allocation and more workers is planned — see Benchmark Methodology for details.

Results: Scheduling and Execution

Scheduling is how long it took to accept and durably persist the entire burst. Execution is how long it took, once accepted, for jobs to actually complete.

Baseline (2 buckets/worker, Hangfire WorkerCount 10)

ConfigScheduling throughput/latencyExecution throughput/latency
JobMaster + RavenDB1945/sec, 10.0s / 12.9s158.0/sec, 84.1s / 149.9s
JobMaster + MySQL1641/sec, 12.5s / 15.3s159.6/sec, 84.0s / 148.4s
JobMaster + SQL Server1370/sec, 17.1s / 18.2s137.6/sec, 106.1s / 171.9s
JobMaster + PostgreSQL1306/sec, 17.7s / 19.2s153.3/sec, 90.8s / 154.2s
Hangfire + SQL Server982/sec, 19.5s / 25.5s324.8/sec, 48.6s / 76.3s

At baseline, JobMaster wins decisively on scheduling, accepting a burst in ~10-20s regardless of database engine versus Hangfire's ~20-25s, but loses on raw execution throughput to Hangfire, which has no bucket-assignment/coordination layer to go through. Hangfire's peer-to-peer row-locking model gets a job from "scheduled" to "running" faster once accepted; JobMaster's coordination overhead (bucket assignment, buffer refill, master/agent throttling) buys durability/ordering guarantees Hangfire doesn't provide, at the cost of execution latency.

2x Execution (4 buckets/worker, Hangfire WorkerCount 20)

ConfigScheduling throughput/latencyExecution throughput/latency
JobMaster + RavenDB1601/sec, 11.8s / 16.0s239.6/sec, 60.8s / 98.1s
JobMaster + MySQL1274/sec, 14.8s / 19.7s226.3/sec, 64.9s / 102.6s
JobMaster + SQL Server1169/sec, 20.1s / 21.5s186.4/sec, 87.0s / 129.2s
JobMaster + PostgreSQL960/sec, 22.9s / 26.1s210.3/sec, 71.5s / 109.7s
Hangfire + SQL Server646/sec, 22.0s / 38.8s313.2/sec, 49.3s / 79.1s

Doubling JobMaster's bucket count (2 → 4, matching the guidance in Parameter 2 of the tuning guide) tells a consistent story across every database engine: it trades -15% to -26% scheduling throughput for +35% to +52% execution throughput. This isn't a database-specific quirk. It's a general property of raising bucket count, and it's a real, usable dial: a workload that leans more on one side of the job lifecycle than the other can be tuned toward it directly.

Hangfire gets no equivalent benefit from matching that concurrency. Its scheduling throughput dropped the most of the group (-34%, from 982/sec to 646/sec) while execution stayed flat-to-down (-4%, 324.8/sec to 313.2/sec). Past its sweet spot, raising WorkerCount mostly adds scheduling overhead without buying Hangfire more throughput. JobMaster's bucket model is the one that scales predictably here.

info

Unlike Hangfire, JobMaster's execution-vs-scheduling balance isn't fixed. --buckets (or BucketQtyConfig in your own cluster config) is a real, usable dial. See Parameter 2 of the tuning guide for when to raise or lower it.

Results: Resource Usage

Container averages/maxes across all workers and all repeated runs. "Max" is the single highest value observed, not an average of each run's own max. Database CPU% figures exceed 100 because the cap is 3 whole CPUs (300% in single-core-percentage terms), not 1.

Baseline (2 buckets/worker)

ConfigDB avg/max CPU%DB avg/max mem (MB)Worker avg/max CPU%Worker avg/max mem (MB)
JobMaster + MySQL101.6 / 117.21688.5 / 1880.712.5 / 27.0132.1 / 148.8
JobMaster + RavenDB103.5 / 151.52444.7 / 2939.711.5 / 26.7345.2 / 382.1
JobMaster + SQL Server122.4 / 304.42913.1 / 3947.810.9 / 26.8138.0 / 153.2
JobMaster + PostgreSQL124.7 / 271.02694.7 / 3115.99.8 / 26.2128.2 / 147.3
Hangfire + SQL Server170.4 / 278.21169.9 / 1308.216.0 / 26.386.7 / 94.8

2x Execution (4 buckets/worker)

ConfigDB avg/max CPU%DB avg/max mem (MB)Worker avg/max CPU%Worker avg/max mem (MB)
JobMaster + RavenDB101.7 / 181.72335.5 / 2852.717.6 / 26.9344.4 / 400.3
JobMaster + PostgreSQL149.4 / 300.92688.2 / 3896.714.7 / 27.3130.2 / 142.9
JobMaster + MySQL164.7 / 305.91635.4 / 2145.618.0 / 26.8137.7 / 152.6
JobMaster + SQL Server193.5 / 301.53323.9 / 4541.115.2 / 26.7140.5 / 155.6
Hangfire + SQL Server209.6 / 309.91251.7 / 1418.416.3 / 26.990.9 / 99.3

Hangfire runs the hottest database CPU of the group at both bucket counts, while JobMaster's SQL engines stay comfortably under their 3-CPU cap either way. JobMaster + RavenDB's worker memory footprint is visibly higher than the SQL engines' at both bucket counts (its client keeps more in memory per connection), worth accounting for if you're tightening worker memory limits.

Takeaways

  • Scheduling: JobMaster accepts a full 25k-job burst in ~10-20s across every database engine, consistently faster than Hangfire's ~20-25s. If your callers mainly need a fast, durable ack that a job was accepted, JobMaster gets out of the way faster.

  • Execution: Hangfire completes the burst roughly 2x faster than JobMaster once jobs are accepted, because its peer-to-peer row-locking model has no bucket-assignment/coordination layer to cross. This is the cost of the durability/ordering guarantees JobMaster's coordination layer provides, not a gap to close.

  • Correctness: every JobMaster configuration stayed at zero lost, zero duplicated jobs across every run. Hangfire stayed at zero lost too, but consistently duplicated 18-58 jobs per burst. Both frameworks are designed around at-least-once delivery, not exactly-once, so occasional duplicates are an expected possibility for either one under the right conditions, worth investigating further if exactly-once delivery matters for your workload.

  • Tunability: JobMaster's execution-vs-scheduling balance isn't fixed. Raising bucket count is a real, usable dial that shifts throughput from scheduling toward execution, so a workload that leans more on one side of the lifecycle than the other can be tuned toward it directly. See the Performance Tuning Guide for the full set of tuning parameters.