Why BackWave

Why BackWave

BackWave is a background-job system for .NET in the same species as Hangfire: you enqueue opaque work and it runs once-or-more with retries. What sets it apart is that the job logic is deterministically testable, enqueueing can be transactional, and it runs on infrastructure you already have.

The case for BackWave

The point-by-point comparisons against other products live on their own pages. First, what you actually get when you adopt BackWave, and how it earns the reliability claims it makes.

Test your jobs deterministically

Enqueue a job, advance a virtual clock your test owns, and assert the outcome. Retries, backoff, cron schedules, and dependency releases all run under that clock, so a year of daily cron ticks completes in one call in well under a second, with no sleeps and no real database. The harness pairs the shipping scheduler core with a real in-memory implementation of the same storage contract Postgres passes, not a mock.

Built like infrastructure software

BackWave’s own test suite drives a whole simulated cluster through crashes, clock skew, node isolation, and racing operator actions, and every run replays exactly from a single seed. One conformance suite runs against every supported store on real database instances. Invariant checks stay on in production and fail-stop rather than quietly mis-process work.

Enqueue inside your transaction

Enqueue a job inside your own database transaction: commit and it is durably queued, roll back and it never existed. That closes the dual-write race without an outbox pattern to build or a relay process to babysit, and the same guarantee extends to enqueueing whole workflow graphs.

Guarantees stated honestly

The handler may run more than once, and the docs say so plainly. The recorded outcome and everything downstream of it (terminal state, dependency release, concurrency slot) apply exactly once. Running jobs are held under heartbeat-renewed leases, so a crashed or frozen node simply stops renewing and its peers reclaim the work automatically.

No new infrastructure

The queue is tables in the Postgres, SQL Server, or SQLite you already run, next to your data. There is no broker or Redis to stand up, and no coordinator or leader election either; every node is an interchangeable stateless peer. On Postgres, LISTEN/NOTIFY wakes an idle worker the moment an enqueue commits, giving sub-second pickup with polling underneath as the correctness backstop.

Easy to adopt, hard to outgrow

Put an attribute on a method and the source generator emits the typed payload, reflection-free serialization, and registration. Job identity is a stable Wire Name, so renames never strand in-flight work, and because serialization and dispatch are generated rather than reflected, the whole enqueue-execute-store-observe loop on Postgres publishes warning-clean under Native AOT and trimming. Each attempt gets a fresh DI scope, so a scoped DbContext injects exactly as in a controller. If you’re coming from Hangfire, a documented playbook runs both in one process while you port one job at a time.

Operations you can defend

Every dashboard write action (requeue, cancel, pause, trigger) is an individual default-deny callback into your app’s own authorization, and each one lands in an append-only audit trail with the acting identity. Everything an operator can click is also a public API with defined semantics. Health checks tell a transient store blip apart from a genuine halt.

Orchestration for real pipelines

Continuations and dependencies are core and free: a dependent waits until its parent reaches a terminal state, a failed parent cancels the downstream branch, and a job can persist typed output that dependents read race-free. Pro adds a validated fan-out/fan-in DAG builder with atomic graph enqueue and group lifecycle controls.

Efficiency and throughput

BackWave does the same work as Hangfire while holding a fraction of the database connections and CPU, and then scales past it on demand without giving that efficiency back. The efficiency is the headline, and it holds as you scale up, so you go faster and still spend less.

See how we benchmark →
~10×
fewer database connections

At a matched work rate, held whether busy or idle. Connections are the genuinely scarce resource on managed Postgres (RDS, Cloud SQL, Supabase all cap them).

~3×
less CPU

Same throughput, more idle headroom. That headroom is the thing you spend to scale, rather than a resource you have already given away.

≈ near-linear
scale-out with fan-out

Running more pumps in one process multiplies throughput near-linearly, so a node grows on demand without a redeploy or a new tier of infrastructure.

Indicative figures from local runs, not official benchmarks. They describe durable ratios and shapes; absolute jobs/sec numbers are pending an official run.

Throughput holds as jobs get heavier

On trivial empty jobs Hangfire is a touch faster. As handlers do realistic work in the 10 to 50 ms range, BackWave’s throughput stays flat while Hangfire’s falls off, and the lead crosses over in BackWave’s favor. Most real jobs do real work, so that crossover is the regime you actually live in.

Where Hangfire still wins, plainly

At default config on very short sub-10 ms jobs Hangfire is modestly faster, and its always-polling workers pick light, steady load up a few milliseconds sooner. BackWave trades those milliseconds for a quarter of the connections and half the CPU. The efficiency gap narrows in this light-job default regime but never reverses; at a matched work rate it widens to the ~10× and ~3× above, plus faster job submission.

How it compares

One long-form report per product, written against that project’s own source and documentation. BackWave publishes them, so they are not neutral. They are checkable: every claim is sourced, and each report has a section on where the other product wins.

Ready to look closer?

The documentation covers the core concepts, storage, testing, and the dashboard in depth.

Read the docs