PostgreSQL
Use Nimbus with PostgreSQL as a message transport — no additional broker required
The Nimbus PostgreSQL transport stores messages in database tables and delivers them via polling. If your application already uses PostgreSQL, this is the easiest way to add messaging — no extra broker, no new infrastructure, and works with any managed PostgreSQL service (Supabase, Neon, RDS, Cloud SQL, Azure Database for PostgreSQL).
Messages are dequeued atomically using FOR UPDATE SKIP LOCKED, so multiple consumers can drain a queue in parallel without duplicates or blocking.
Installation
dotnet add package Nimbus.Transports.Postgres
Quick Start
using Nimbus;
using Nimbus.Configuration;
using Nimbus.Transports.Postgres;
var bus = new BusBuilder()
.Configure()
.WithNames("OrderService", Environment.MachineName)
.WithTransport(new PostgresTransportConfiguration()
.WithConnectionString("Host=localhost;Database=nimbus;Username=postgres;Password=secret")
.WithAutoCreateSchema())
.WithTypesFrom(typeProvider)
.WithAutofacDefaults(container)
.Build();
await bus.Start();
WithAutoCreateSchema() creates the three required tables (nimbus_messages, nimbus_subscriptions, nimbus_dead_letters) using CREATE TABLE IF NOT EXISTS. It’s safe to leave enabled in production. If you prefer to manage the schema yourself, omit it and create the tables manually using the SQL below.
Configuration
Poll Interval
The default poll interval is 1 second. Reduce it for lower latency at the cost of more database queries:
new PostgresTransportConfiguration()
.WithConnectionString("...")
.WithPollInterval(TimeSpan.FromMilliseconds(250))
| Poll interval | Message latency | DB queries/consumer/min |
|---|---|---|
| 100 ms | ~100 ms | 600 |
| 250 ms | ~250 ms | 240 |
| 1000 ms (default) | ~1 s | 60 |
| 5000 ms | ~5 s | 12 |
Schema
Three tables are required. WithAutoCreateSchema() creates these automatically, or run the SQL manually:
nimbus_messages
CREATE TABLE IF NOT EXISTS nimbus_messages (
message_id UUID NOT NULL,
destination TEXT NOT NULL,
body BYTEA NOT NULL,
visible_after TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NULL,
CONSTRAINT pk_nimbus_messages PRIMARY KEY (message_id)
);
CREATE INDEX IF NOT EXISTS ix_nimbus_messages_dequeue
ON nimbus_messages (destination, visible_after)
INCLUDE (expires_at);
nimbus_subscriptions
CREATE TABLE IF NOT EXISTS nimbus_subscriptions (
topic_name TEXT NOT NULL,
subscriber_queue TEXT NOT NULL,
CONSTRAINT pk_nimbus_subscriptions PRIMARY KEY (topic_name, subscriber_queue)
);
nimbus_dead_letters
CREATE TABLE IF NOT EXISTS nimbus_dead_letters (
message_id UUID NOT NULL,
original_destination TEXT NULL,
body BYTEA NOT NULL,
delivery_attempts INTEGER NOT NULL,
failed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT pk_nimbus_dead_letters PRIMARY KEY (message_id)
);
Limitations
- Polling-based delivery means latency is bounded by the poll interval
- No large message body store — all payloads stored as
BYTEA; use a claim-check pattern for large data
Compatibility
- ✅ PostgreSQL 12+
- ✅ PostgreSQL 9.5+ (requires
pgcryptoextension forgen_random_uuid()) - ✅ Supabase, Neon, RDS, Cloud SQL, Azure Database for PostgreSQL
- ✅ Npgsql 9.x