Skip to content

refactor(tasks): move background tasks to Postgres (pg-boss); remove node-resque + resque-admin - #519

Draft
evantahler wants to merge 3 commits into
mainfrom
evantahler/pluggable-task-queue-backend-abstract-node-resqu
Draft

evantahler wants to merge 3 commits into
mainfrom
evantahler/pluggable-task-queue-backend-abstract-node-resqu

Conversation

@evantahler

@evantahler evantahler commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Move background tasks to Postgres (pg-boss); remove node-resque + resque-admin

Keryx's background-task system now runs entirely on Postgres via pg-boss — no Redis required for tasks. The public api.actions.* task interface (enqueue, enqueueIn, enqueueAt, fanOut, fanOutStatus, taskDetails, stopRecurrentAction, Action.task) is unchanged; only the engine underneath changed.

closes #415

Note: #415 originally proposed keeping node-resque as a pluggable default and adding pg-boss. Per follow-up discussion we went further — committing to Postgres-only and removing node-resque + the resque-admin plugin — after research (below) confirmed pg-boss is the right single backend.

What changed

  • New: PgBossBackend (queue) and PgFanOutStore (fan-out state) behind thin abstract seams (TaskBackend / FanOutStore). Fan-out state lives in framework-managed keryx_fanout / keryx_fanout_events tables, created at boot. pg-boss owns its own schema (keryx_tasks).
  • Removed: the node-resque dependency, NodeResqueBackend, RedisFanOutStore, the TASKS_BACKEND / TASKS_FANOUT_STORE selectors, the deprecated api.resque alias, and the resque-only api.actions introspection (locks, delLock, timestamps, delayedAt, allDelayed, workingOn, cleanOldWorkers, delByFunction, delQueue).
  • Deleted: the @keryxjs/resque-admin plugin workspace (a Redis/resque dashboard, obsolete without node-resque).
  • Docs / config: docs/guide/tasks.md + config.md, both .env.examples, README.md, and CLAUDE.md updated to the Postgres story.

Recurring tasks stay single-instance without a leader

The one hard requirement with no obvious Postgres answer. Recurring actions are routed to a dedicated pg-boss queue created with the short policy, whose unique index permits exactly one pending job per action cluster-wide and frees the slot the moment the job starts — so the job's own re-enqueue succeeds while duplicates are rejected. Verified by a test: 5 concurrent enqueues → 1 pending copy.

Why pg-boss (research)

A fact-checked comparison of embeddable Postgres queues narrowed the field to pg-boss and graphile-worker (pgmq has no official TS client + no managed retries/DLQ; Hatchet is a gRPC client to a separate engine). pg-boss wins on maturity (post-1.0, ~754k weekly downloads), verified SKIP LOCKED + dead-letter-with-redrive, and an integration that's Bun-smoke-tested and green. graphile-worker's edges (native TS, verified leaderless cron) are neutralized here since we solve recurring dedup ourselves.

Verification

keryx suite (Postgres)     858 pass / 0 fail
example/backend            273 pass / 0 fail
tracing 9 · csrf 11 · mcpApp 6 · docs build clean
tsc + biome clean across all workspaces

🤖 Generated with Claude Code

evantahler and others added 2 commits July 26, 2026 17:20
…nOutStore)

Introduce a TaskBackend + FanOutStore seam beneath the unchanged public
api.actions.* task interface. Both node-resque+Redis and pg-boss+Postgres are
implemented as adapters, selected from config.tasks.backend / fanOutStore.

- classes/TaskBackend.ts, classes/FanOutStore.ts — the two abstractions
- backends/NodeResqueBackend.ts + RedisFanOutStore.ts — default adapters (zero behavior change)
- backends/PgBossBackend.ts + PgFanOutStore.ts — Postgres adapters (drop Redis entirely)
- initializers/tasks.ts — selects adapters, owns the shared task runner; api.resque kept as a deprecated alias
- contract tests parameterized across both backends and both fan-out stores
- recurring tasks stay single-instance with no leader (node-resque QueueLock/DelayQueueLock; pg-boss short-policy queue)

closes #415

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + resque-admin

Research validated pg-boss as the best embeddable Postgres queue, so commit fully to
Postgres for background tasks and drop the Redis/node-resque path entirely.

- remove NodeResqueBackend + RedisFanOutStore and the node-resque dependency
- PgBossBackend + PgFanOutStore are now the only implementations; TaskBackend and
  FanOutStore stay abstract as a thin internal seam
- drop the TASKS_BACKEND / TASKS_FANOUT_STORE selectors and the api.resque alias
- remove the resque-only api.actions introspection (locks, delLock, timestamps,
  delayedAt, allDelayed, workingOn, cleanOldWorkers, delByFunction, delQueue)
- delete the @keryxjs/resque-admin plugin workspace (obsolete without node-resque)
- framework + example default to Postgres; docs, env examples, and CLAUDE.md updated

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@evantahler evantahler changed the title feat(tasks): pluggable task queue backend + fan-out store (node-resque + pg-boss) refactor(tasks): move background tasks to Postgres (pg-boss); remove node-resque + resque-admin Jul 27, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant