graphile-worker

repository·main·Indexed 25 days ago

https://github.com/graphile/worker

A 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.

Tokens
46.4K
Snippets
93
Records
239
Agent score
80%

What's inside graphile-worker

  1. Overview of graphile-worker

    main
    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.
  2. What is Graphile Worker?

    main

    Graphile 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 LOCKED for fast job fetching and LISTEN/NOTIFY for 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.
  3. Overview of Worker Pro features

    main

    Worker 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.

  4. What is a task executor and how does it work?

    main

    A 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 await all asynchronous work before returning. Do not create "untethered" promises, or Graphile Worker may prematurely mark the job as successful.

    Each executor receives two arguments:

    1. payload: The JSON payload passed when the job was created.
    2. helpers: A collection of utility functions (see helpers).
    export default async function task1(payload) {
      await doMyLogicWith(payload);
    }
  5. What is a Task and how to implement a Task executor

    main

    A 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) and helpers (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();
    };
  6. How to listen to WorkerEvents

    main

    Graphile Worker supports a wide range of events via an EventEmitter. You can consume these events using two primary methods:

    1. Via the Runner object: Access the events property on the returned Runner instance. This is the standard way to listen to events during the worker's lifecycle.
    2. Via WorkerOptions.events: Provide your own EventEmitter instance in the configuration options. This is useful for capturing events that occur during the startup procedure, before the run() 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, ... });
  7. How to handle batch jobs in task executors

    main

    If 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_key with array payloads to automatically concatenate new payloads onto existing ones (unless job_key_mode is set to unsafe_dedupe).

  8. Connect via TCP socket

    main

    To connect using a TCP socket, specify the host and port (if the port is not the default 5432).

    Important: If you use localhost or localhost:port, the client might attempt to use a domain socket instead. To force a TCP connection, use 127.0.0.1 as the host.

    postgres://user:password@host:port/dbname?...
  9. Key features of Graphile Worker

    main

    Graphile 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 runTaskListOnce utility is recommended).
      • Rate Limiting: Supports flexible runtime controls for complex rate limiting (e.g., via graphile-worker-rate-limiter).
  10. Use Graphile Config presets for configuration

    main

    Graphile Worker uses a "Graphile Config preset" to manage settings. A preset is a JavaScript/TypeScript object that can include extends (to merge other presets) and plugins. For Graphile Worker, settings specific to the worker are placed under the worker key.

    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 graphile CLI tool to inspect configuration via graphile config print or graphile 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"],
      },
    };
  11. How live migration works in Worker Pro

    main

    Worker 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.

  12. Interact with Graphile Worker using public APIs only

    main

    Do 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, or migrations). 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 the jobs view.