Configuration
Every worker option, what it does, and when to change it.
Worker options
| Option | Type | Default | Notes |
|---|---|---|---|
connectionString |
string |
— | Required unless pool is given |
pool |
Pool |
— | Reuse an existing pg pool |
jobs |
Job[] |
[] |
Handlers this worker will process |
concurrency |
number |
10 |
Jobs run in parallel per worker |
pollInterval |
string |
"200ms" |
How often to check for work |
shutdownTimeout |
string |
"30s" |
Grace period for in-flight jobs |
heartbeatInterval |
string |
"5s" |
Liveness signal for orphan detection |
claimTimeout |
string |
"5m" |
Jobs held longer are reclaimed |
logger |
Logger |
console |
Any object with info/warn/error |
Choosing concurrency
Concurrency is bounded by your database connection pool, not by CPU. Each running job holds a connection for the duration of its transaction.
A reasonable starting point:
concurrency = (pool size - 2) / number of worker processes
The - 2 leaves headroom for polling and heartbeats. Setting concurrency
above what the pool can serve produces connection timeouts that look like job
failures.
claimTimeout and orphan recovery
When a worker dies mid-job, its rows stay marked running with no live owner.
Halyard reclaims them once claimTimeout has elapsed since the last
heartbeat.
Set this longer than your slowest job. If a job routinely takes 10
minutes and claimTimeout is 5 minutes, a second worker will pick it up while
the first is still working — producing exactly the duplicate execution your
idempotency guards then have to absorb.
Graceful shutdown
process.on("SIGTERM", async () => {
await worker.stop(); // stops claiming, waits for in-flight jobs
process.exit(0);
});
stop() resolves once in-flight jobs finish or shutdownTimeout expires,
whichever comes first. Jobs still running at the timeout are left claimed and
recovered by another worker after claimTimeout.
Set your container’s termination grace period higher than
shutdownTimeout, or the orchestrator will SIGKILL the process mid-job.