graphile-worker
repository·main·Indexed 25 days ago
https://github.com/graphile/workerA Node.js-based job queue for PostgreSQL that manages and executes background tasks asynchronously. It allows applications to offload long-running processes—such as sending emails or performing complex calculations—to a PostgreSQL backend. The library includes a CLI and supports exporting jobs to external systems like Faktory, GCP Cloud Tasks, and BullMQ.
What's inside graphile-worker
- graphile-worker is a job queue for PostgreSQL running on Node.js. It allows you to offload tasks—such as sending emails, performing complex calculations, or generating PDFs—to run in the background. This prevents your main application code or HTTP responses from being delayed by long-running processes. It is designed to work with any PostgreSQL-backed application and integrates well with PostGraphile or PostgREST.
What is Graphile Worker?
mainGraphile Worker is a job queue that uses PostgreSQL to store jobs and executes them on Node.js. It allows you to run background tasks—such as sending emails, performing calculations, or generating PDFs—without blocking your main application code or HTTP responses.
Key characteristics include:
- Postgres-centric: Designed to work seamlessly with jobs created directly inside the database via triggers or stored procedures. It is highly compatible with PostGraphile and PostgREST.
- Reliable: Leverages PostgreSQL's transactional guarantees to ensure jobs are not lost. It guarantees at least once execution, with most jobs executing exactly once. Failed jobs are automatically retried using exponential backoff.
- High Performance: Uses
SKIP LOCKEDfor fast job fetching andLISTEN/NOTIFYfor low-latency job discovery (typically under 3ms from schedule to execution). - Flexible Deployment: Can be run as a standalone process or embedded directly within your existing Node.js server process to reduce infrastructure complexity.
Overview of Worker Pro features
mainWorker Pro is a proprietary preset for Graphile Worker that adds advanced features designed for complex deployments and larger teams. Key features include:
- Live migration: Allows you to upgrade Worker versions without needing to scale to zero to safely transition.
- Crashed worker recovery: Automatically tracks running workers and unlocks jobs if a worker appears to have stopped unexpectedly.
Note: Worker Pro is not open source, but it is "source available," meaning you can modify the code for internal usage under the provided license terms.
What is a task executor and how does it work?
mainA task executor is an asynchronous function that performs the work associated with a specific task identifier.
When a job is found in the database with a matching task identifier, Graphile Worker calls the executor.
- Success: If the executor returns successfully, the job is considered successful and is deleted from the queue (unless it is a batch job).
- Failure: If the executor throws an error or rejects its promise, the job is considered a failure and is rescheduled using an exponential-backoff algorithm.
Important: Task executors must
awaitall asynchronous work before returning. Do not create "untethered" promises, or Graphile Worker may prematurely mark the job as successful.Each executor receives two arguments:
payload: The JSON payload passed when the job was created.helpers: A collection of utility functions (see helpers).
export default async function task1(payload) { await doMyLogicWith(payload); }What is a Task and how to implement a Task executor
mainA Task is a type of work that can be executed (e.g., "send email"). A Task identifier is the unique name given to that task (e.g.,
send_email).A Task executor is the actual code responsible for performing the work. In JavaScript or TypeScript, a Task executor is typically an asynchronous function that receives a
payload(the data for the job) andhelpers(utility functions provided by the worker).import type { Task } from "graphile-worker"; import { ses, Source } from "../lib/aws-ses.js"; export const send_email: Task = async (payload, helpers) => { const send = ses.sendEmail({ Destination: { ToAddresses: [payload.address] }, Message: { Subject: { Charset: "UTF-8", Data: payload.subject }, Body: { Text: { Charset: "UTF-8", Data: payload.body } }, }, Source, }); await send.promise(); };How to listen to WorkerEvents
mainGraphile Worker supports a wide range of events via an
EventEmitter. You can consume these events using two primary methods:- Via the
Runnerobject: Access theeventsproperty on the returnedRunnerinstance. This is the standard way to listen to events during the worker's lifecycle. - Via
WorkerOptions.events: Provide your ownEventEmitterinstance in the configuration options. This is useful for capturing events that occur during the startup procedure, before therun()promise has resolved.
Both methods allow you to react to pool lifecycle changes, worker lifecycle changes, and job status changes.
// Method 1: via runner.events runner.events.on("job:success", ({ worker, job }) => { console.log(`Hooray! Worker ${worker.workerId} completed job ${job.id}`); }); // Method 2: using a custom EventEmitter in options /** @type {import("graphile-worker").WorkerEvents} */ const events = new EventEmitter(); events.on("job:success", ({ worker, job }) => { // ... }); const runner = await run({ events, ... });- Via the
How to handle batch jobs in task executors
mainIf the job payload is an array, a task executor can optionally return an array of promises of the same length.
Error Handling in Batches: If any promise in the returned array rejects, the entire job is re-enqueued. However, the payload is updated to contain only the entries associated with the rejected promises. Successful entries are removed from the payload for the retry.
Tip: Accumulating batches: You can use
job_keywith array payloads to automatically concatenate new payloads onto existing ones (unlessjob_key_modeis set tounsafe_dedupe).Connect via TCP socket
mainTo connect using a TCP socket, specify the
hostandport(if the port is not the default5432).Important: If you use
localhostorlocalhost:port, the client might attempt to use a domain socket instead. To force a TCP connection, use127.0.0.1as the host.postgres://user:password@host:port/dbname?...Key features of Graphile Worker
mainGraphile Worker provides several advanced features for managing background tasks:
- Execution Modes: Supports both standalone and embedded modes.
- Task Management:
- Parallelism: Runs tasks in parallel by default.
- Serial Queues: Adding jobs to the same named queue causes them to run in series.
- De-duplication: Supports task de-duplication via a unique
job_key. - Batching: Supports 'batch jobs' by appending data to already enqueued jobs.
- Scheduling & Retries:
- Retries: Automatically re-attempts failed jobs with exponential back-off. The default is 25 attempts over approximately 3 days (customizable).
- Scheduling: Includes a Crontab-like scheduling feature for recurring tasks, with optional backfill.
- Developer Experience:
- Modern API: 100% async/await API (no callbacks) written in TypeScript.
- Testing: Easy to test (the
runTaskListOnceutility is recommended). - Rate Limiting: Supports flexible runtime controls for complex rate limiting (e.g., via
graphile-worker-rate-limiter).
Use Graphile Config presets for configuration
mainGraphile Worker uses a "Graphile Config preset" to manage settings. A preset is a JavaScript/TypeScript object that can include
extends(to merge other presets) andplugins. For Graphile Worker, settings specific to the worker are placed under theworkerkey.Using a
graphile.config.js(or.ts,.mjs) file is recommended because it allows you to:- Share configuration between CLI and Library modes.
- Share common options between different instances.
- Use the
graphileCLI tool to inspect configuration viagraphile config printorgraphile config options.
It is recommended to export the preset as the default export of your configuration file.
import { WorkerPreset } from "graphile-worker"; export default { extends: [WorkerPreset], worker: { connectionString: process.env.DATABASE_URL, maxPoolSize: 10, pollInterval: 2000, preparedStatements: true, schema: "graphile_worker", crontabFile: "crontab", concurrentJobs: 1, fileExtensions: [".js", ".cjs", ".mjs", ".ts", ".mts"], }, };How live migration works in Worker Pro
mainWorker Pro enables 'live migration', allowing you to roll out new versions of Worker on a server-by-server basis without scaling your workers to zero.
Worker Pro performs startup checks and tracks running workers to determine if a worker is out of date (requiring a graceful shutdown) or if it needs to wait for previous worker versions to exit before running migrations. It distinguishes between safe migrations and breaking migrations to prevent issues like duplicate job execution or long waits caused by schema/API mismatches.
Interact with Graphile Worker using public APIs only
mainDo not interact with Graphile Worker by querying or modifying its internal tables directly (e.g.,
_private_jobs,_private_job_queues,_private_known_crontabs,_private_tasks, ormigrations). The table schemas are not stable and may change in minor versions.Directly accessing tables can also cause performance issues or unexpected behavior:
- Scanning the jobs table impacts queue performance.
- Reading from the jobs table inside a transaction can prevent jobs from being worked on, potentially leading to out-of-order execution.
Instead, use the documented public APIs such as
graphile_worker.add_job(), administrative functions, and thejobsview.