17. Performance
17.1 Execution model
Every job execution rides entirely on Hangfire's own worker thread pool and async/await — the framework
introduces no additional threading, no custom scheduler loop, and no execution concurrency model of its own.
Overhead added per execution is limited to: constructing one BackgroundJobExecutionContext, one job-specific
adapter, one BackgroundJobExecutor, and invoking the pipeline's thin RunAsync (a cancellation check, an
await of the caller-supplied delegate, and exception classification only on the failure path). See
§4 — Runtime Pipeline for exactly what that call chain does.
17.2 Allocation strategy
Nothing in the current design pools or caches objects across executions — this is a deliberate simplicity choice, not an oversight requiring one:
BackgroundJobExecutionContext— a new instance per execution, immutable, no unmanaged resources, garbage collected normally once the call chain returns.- The job-specific
IBackgroundJobadapter andBackgroundJobExecutor— both constructed withnew, per execution, by the bridge; neither is DI-registered, so there is no container overhead for either. BackgroundJobExecutionLifetime— one lightweight timing struct-like object per pipeline call; itsElapsedvalue is currently computed but has no consumer (see AP-01).- The one DI-resolved, Scoped service (
IBackgroundJobTenantContext) is resolved once per job's DI scope, not re-created per call within that scope.
For the recurring, high-frequency jobs already in production (campaign-workflow-executor-pipeline and
notification-retry-processor-pipeline both run every 2 minutes; campaign-scheduler-pipeline runs every
minute), this means each tick allocates a small, fixed number of short-lived objects — no unbounded growth,
no per-tick accumulation of state.
17.3 Dependency graph efficiency
Every service is resolved via standard constructor-injection DI — there is no runtime reflection in the hot
path of an execution (reflection is used only in the framework's own test suite, for DI-graph verification
tests like ConstructorCoverageTests, never at runtime). The dependency graph itself is shallow and acyclic
by design and by test (DependencyGraphTests.DependencyGraph_HasNoCircularDependencies), so container
resolution cost per execution is proportional to the small, fixed number of services each job's bridge
actually constructor-injects — not to the size of the framework's overall public surface.
17.4 Runtime efficiency — what's cheap and what to be deliberate about
Cheap, by design:
- Jobs that are naturally idempotent at the set level (dead-letter cleanup, retry expire, campaign cleanup) add no locking or claiming overhead at all.
- The distributed lock, when used, is a single round-trip to Hangfire's own storage-backed
AcquireDistributedLockcall — no polling loop, no custom retry-on-contention logic inside the framework itself.
Where the real cost lives — deliberately not in this framework's layer: for the two jobs with real
per-row concurrency concerns (notification-retry-processor, campaign-workflow-executor), the actual
contention-handling cost is an atomic, indexed SQL UPDATE ... WHERE Status = Pending AND WorkerToken IS NULL inside the Notification Framework — not a framework-level lock. This is intentional: per-row database
claiming scales far better under concurrent execution than a coarse job-level lock would, since only rows
actually being contested pay any cost at all. See
§9.4.
17.5 What is not yet measured
Unlike the Notification Framework (which has a dedicated Shumoul.Notification.Tests.Performance project
using BenchmarkDotNet), no formal benchmark suite exists for the Background Jobs Framework today. No
throughput, latency, or allocation numbers are published for this framework's runtime — anything beyond the
structural facts in this chapter would be invented rather than measured, and is deliberately left out per
this guide's source-of-truth requirement. If performance benchmarking is added in the future, following the
Notification Framework's Tests.Performance project as the template would keep the two Golden Reference
modules consistent in this respect too.