ITB.HangfirePostgreSql.ValkeyQueue
0.1.2-alpha
See the version list below for details.
dotnet add package ITB.HangfirePostgreSql.ValkeyQueue --version 0.1.2-alpha
NuGet\Install-Package ITB.HangfirePostgreSql.ValkeyQueue -Version 0.1.2-alpha
<PackageReference Include="ITB.HangfirePostgreSql.ValkeyQueue" Version="0.1.2-alpha" />
<PackageVersion Include="ITB.HangfirePostgreSql.ValkeyQueue" Version="0.1.2-alpha" />
<PackageReference Include="ITB.HangfirePostgreSql.ValkeyQueue" />
paket add ITB.HangfirePostgreSql.ValkeyQueue --version 0.1.2-alpha
#r "nuget: ITB.HangfirePostgreSql.ValkeyQueue, 0.1.2-alpha"
#:package ITB.HangfirePostgreSql.ValkeyQueue@0.1.2-alpha
#addin nuget:?package=ITB.HangfirePostgreSql.ValkeyQueue&version=0.1.2-alpha&prerelease
#tool nuget:?package=ITB.HangfirePostgreSql.ValkeyQueue&version=0.1.2-alpha&prerelease
ITB.HangfirePostgreSql.ValkeyQueue
A Valkey/Redis-backed queue provider for Hangfire.PostgreSql. PostgreSQL stays the durable source
of truth (job data, state history, scheduled/recurring jobs, dashboard); only the hot queue path (job
IDs) moves to Valkey — LPUSH to enqueue, blocking BLMOVE to dequeue — so idle worker polling load
on PostgreSQL drops toward zero.
In a measured run, an idle Hangfire server's PostgreSQL load fell by 97.6 % (1.37 → 0.03 queries/s). Per-job cost fell 12.5 %, and throughput was unchanged. Full methodology, environment and caveats: docs/LOAD-TEST.md.
Prerelease. On Hangfire.PostgreSql 1.21.1 the baseline already wakes idle workers via PostgreSQL
LISTEN/NOTIFY, so the pickup-latency win is marginal and the reduction under sustained load is modest. The win is idle database load. Validate against your own workload before relying on it, and keep pure-PostgreSQL as the fallback.
Installation
dotnet add package ITB.HangfirePostgreSql.ValkeyQueue
Targets .NET 10. Requires Hangfire.PostgreSql 1.21.x and a Valkey (or Redis) 7+ server.
Usage
using Hangfire;
using Hangfire.PostgreSql;
using Hangfire.PostgreSql.Factories;
using Hangfire.PostgreSql.ValkeyQueue;
using Hangfire.Server;
using StackExchange.Redis;
var storageOptions = new PostgreSqlStorageOptions { UseSlidingInvisibilityTimeout = true };
var storage = new PostgreSqlStorage(
new NpgsqlConnectionFactory(pgConnectionString, storageOptions), storageOptions);
var redisOptions = ConfigurationOptions.Parse(valkeyConnectionString);
var mux = ConnectionMultiplexer.Connect(redisOptions);
var valkey = new ValkeyQueueOptions
{
InvisibilityTimeout = TimeSpan.FromMinutes(30), // MUST exceed your longest job
BlockingConnectionConfig = redisOptions, // required on managed Valkey (TLS + AUTH)
};
storage.UseValkeyQueues(mux, valkey); // point the "default" queue at Valkey
services.AddHangfire(c => c.UseStorage(storage));
services.AddHangfireServer();
// Safety nets — recover from Valkey data loss / worker death. Register as a singleton
// IBackgroundProcess so AddHangfireServer picks it up.
services.AddSingleton<IBackgroundProcess>(
new ValkeyQueueMaintenance(mux, valkey, pgConnectionString));
A complete, runnable worked example — including the ValkeyQueueMaintenance registration that is
easy to forget — is in docs/EXAMPLE.md.
Configuration
ValkeyQueueOptions:
| Option | Default | Notes |
|---|---|---|
KeyPrefix |
hangfire: |
Prefix for every Valkey key this provider owns. |
BlockTimeout |
2 s | How long a single BLMOVE blocks before the worker loops. |
InvisibilityTimeout |
30 min | A job in a processing list longer than this is treated as orphaned. Must exceed your longest job. |
MaintenanceInterval |
15 s | How often the reconciler and orphan sweep run. |
ReconcileGrace |
30 s | The reconciler ignores jobs enqueued more recently than this, so it never races an in-flight LPUSH. |
Schema |
hangfire |
The schema Hangfire.PostgreSql created its tables in. |
Queues |
["default"] |
Queues served by Valkey. Anything not listed stays on PostgreSQL. |
BlockingConnectionConfig |
null |
ConfigurationOptions for the dedicated blocking connections. Set this whenever AUTH/TLS is in play — see below. |
Correctness
The enqueue is no longer transactional with job creation (it pushes to Valkey, not PostgreSQL), so two safety nets close every failure window:
- Reconciler re-enqueues jobs PostgreSQL reports as
Enqueuedbut missing from Valkey (Valkey data loss / failover). - Orphan sweep requeues jobs stuck in a processing list past
InvisibilityTimeout(worker died mid-job).
Both live in ValkeyQueueMaintenance. Without it registered, those failures are silent job loss —
this is the one registration step you cannot skip.
Duplicates are execution-safe: only one worker wins the Enqueued → Processing state transition in
PostgreSQL; the loser is discarded.
Notes
- One dedicated blocking connection per worker thread (
BLMOVEmonopolises its connection) — fine for tens of workers, revisit for hundreds. - On managed Valkey (ElastiCache with TLS + AUTH) set
BlockingConnectionConfig; otherwise the blocking connections are derived from the shared multiplexer's connection string, which masks the password and every blocking connection fails to authenticate. InvisibilityTimeoutmust exceed your longest-running job or the sweep will re-flag it.- Keys are wrapped in a
{hash tag}so all three keys for a queue land in the same cluster hash slot; without it, cluster mode rejects theBLMOVEwithCROSSSLOT.
Development
# Postgres + Valkey for the tests (pg_stat_statements is only needed for benchmarks)
docker run -d --name hfvq-pg -e POSTGRES_PASSWORD='Asd123!1' \
-e POSTGRES_DB=hangfire_bench -p 55432:5432 postgres:17-alpine
docker run -d --name hfvq-valkey -p 56379:6379 valkey/valkey:8-alpine
dotnet test
The suite covers the queue hand-off, both recovery paths, and the cluster hash-slot invariant.
Endpoints are overridable via POSTGRES_TEST_CONNECTION, REDIS_TEST_HOST and REDIS_TEST_PORT.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Hangfire.Core (>= 1.8.23)
- Hangfire.PostgreSql (>= 1.21.1)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql (>= 10.0.3)
- StackExchange.Redis (>= 2.8.24)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 142 | 9/28/2026 |
| 1.0.2 | 185 | 9/22/2026 |
| 1.0.1 | 100 | 9/21/2026 |
| 1.0.0 | 86 | 9/21/2026 |
| 0.1.2-alpha | 85 | 9/21/2026 |