Workers & scaling
Every process that calls AddPgWorkflows is a worker. Call DisableWorkers() and it’s
a client. That’s the whole model.
- A worker leases and executes workflows and activities.
- A client starts, signals, and awaits workflows — and never executes anything.
Postgres is the only coordination layer. No scheduler service, no leader election, no message broker.
Workers
Section titled “Workers”There is no worker setup. AddPgWorkflows registers a hosted background worker, so the
app that defines your workflows processes them — an ASP.NET API, a console app, and a
Windows service are all equally valid workers.
builder.Services.AddPgWorkflows(pg => pg.UsePostgres(connectionString) .AddWorkflow<TrialOnboardingWorkflow>() .AddActivities<EmailActivities>());To scale, deploy more instances. Leases in Postgres make this safe:
- Two workers never run the same step (
FOR UPDATE SKIP LOCKED). - Leases are heartbeated while work runs, so slow work isn’t stolen.
- A dead worker’s lease expires; a peer resumes the run from the last completed step.
- A worker that lost its lease has its writes rejected — no stale-worker corruption.
Clients
Section titled “Clients”A front-facing API shouldn’t compete for work — it should dispatch and move on.
// API — pure client, runs no workersbuilder.Services.AddPgWorkflows(pg => pg.UsePostgres(connectionString) .DisableWorkers() .AddWorkflow<TrialOnboardingWorkflow>());Starting a workflow is a single INSERT — cheap enough for your hottest request path.
Whichever worker leases the run first executes it.
For high-throughput APIs, prefer fire-and-forget: StartAsync, return the run id, let
callers check back. Awaiting GetResultAsync per request polls the database — fine for
tens of waiters, not a million.
Sharing definitions
Section titled “Sharing definitions”Put workflows and activities in a shared class library; every participating process registers from it:
MyApp.Workflows/ ← workflow + activity classesMyApp.Api/ ← client: AddWorkflow + DisableWorkersMyApp.Worker/ ← worker: AddWorkflow + AddActivitiesClients need AddWorkflow (to resolve names and types) but can skip AddActivities —
activities only matter where they execute.
Tuning
Section titled “Tuning”All knobs and defaults are in the configuration reference. Two worth knowing early:
WorkerIddefaults to the machine name — set it explicitly in containers so leases are attributable when debugging.- Activity
MaxConcurrencydefaults to four per processor (IO-friendly); lower it for CPU-bound work.