QueueFlowDocs

Help

Comparison with other queues

How QueueFlow compares with River, Hatchet, Graphile Worker, pg-boss, and Temporal on storage, languages, workflows, dashboard, cron, dead letters, hosting, and license, and when to pick each of them instead.

These are the projects people most often weigh against QueueFlow. The table is about facts that are easy to get wrong; the paragraphs are about fit, including the cases where another tool is the better choice. Everything was checked against each project's current documentation on the date given under Sources. Versions and features move, so re-check before deciding.

For hands-on migration from BullMQ, Celery, Sidekiq, or pg-boss, see the migration guides.

#At a glance

QueueFlow 0.2RiverHatchetGraphile Workerpg-boss 12Temporal
StoragePostgreSQL 13+, no extensionsPostgreSQL (SQLite experimental)PostgreSQL, optional RabbitMQPostgreSQLPostgreSQL 13+ (also CockroachDB, Citus, PGlite)Cassandra, MySQL, or PostgreSQL; SQLite for development; Elasticsearch recommended for visibility
Engine languageRustGoGoNode.js (TypeScript)Node.js (TypeScript)Go
Client languagesTypeScript, Python, Go, Rust, each SDK with a worker runtime (plus the first-party Rust queueflow-client crate); HTTP for anything elseGo, Ruby, Rust, TypeScript; Python for inserting onlyPython, TypeScript, Go, RubyNode.js; jobs can be inserted from SQLNode.js; SQL for most operations from other runtimes.NET, Go, Java, PHP, Python, Ruby, Rust, TypeScript
Workflows / DAGsYes: static DAG, depends_on, shared context, per-step halt/skip/continue; no conditional steps, sub-workflows, or fan-out yetRiver Pro (paid): DAG workflows with dependency failure handlingYes: DAGs with parent outputs, skip_if conditions, plus durable executionNoListed as "job dependency workflow orchestration"Durable execution: workflows are code that is replayed, not declared DAGs
DashboardNot yet (in progress)River UI (separate open-source project)YesNo@pg-boss/dashboardWeb UI
CronYes, UTC crontab, pause/resume, one firing per occurrence across serversPeriodic jobs held in memory by the elected leader; durable periodic jobs in River ProYes, in code, by API, and in the dashboardYes, crontab file, UTC, optional backfillYes, cron and RRULE with time zonesSchedules with pause, backfill, and overlap policies
Dead lettersYes, per tenant, inspect and replay onceRiver ProNot described; exhausted tasks are marked failedNo; exhausted jobs stay in the tableYes, a named dead-letter queue per source queue, with redriveNo DLQ concept; retry policies and workflow failure
Hosting modelSeparate server binary (or Docker image) plus Postgres; self-hostedLibrary inside your Go application; self-hostedSelf-hosted (single image, Compose, Helm) or Hatchet CloudLibrary or CLI inside your Node process; self-hostedLibrary inside your Node process; self-hostedSelf-hosted multi-service cluster, or Temporal Cloud
LicenseMITMPL-2.0 (River Pro is a paid subscription)MITMITMITMIT

Throughput is deliberately not a column. The only number QueueFlow publishes is its own: a drain rate that plateaus around 3,000 no-op jobs per second on one Postgres on a laptop, with batch enqueue at roughly 30,000 to 36,000 jobs per second (see Benchmarks). Graphile Worker's site claims "up to 10,000 jobs per second". Neither figure was measured on the same hardware, and none of these numbers matter if your jobs do real work.

#River

River is a Go-first job queue on Postgres, with the queue running as a library inside your application and clients for Ruby, Rust, and TypeScript that insert and work jobs against the same tables. It is fast, well documented, and its unique-jobs feature (enforced with a partial unique index on ByArgs, ByPeriod, ByQueue, ByState) is more capable than QueueFlow's creation-time Idempotency-Key. River's retry policy defaults to 25 attempts with attempts^4 backoff, which is a long tail compared with QueueFlow's default 3 retries.

Pick River over QueueFlow when your workers are Go and you want the queue embedded in the application process with no separate service to run, or when you need database-enforced uniqueness. Be aware that DAG workflows, a dead-letter queue, and durable periodic jobs are River Pro features sold as a paid subscription; the open-source periodic scheduler holds its schedule in memory on the leader. Pick QueueFlow when you want a language-neutral service with workflows, cron, and a dead-letter queue in the free product, multi-tenant isolation at the API, or workers in languages River does not cover.

#Hatchet

Hatchet is an orchestration engine for tasks, agents, and durable workflows, written in Go on Postgres (with optional RabbitMQ for high-throughput event delivery), with SDKs for Python, TypeScript, Go, and Ruby, a real-time dashboard, cron, rate limiting, concurrency policies, and conditional DAG execution (skip_if, cancel_if). It is available as Hatchet Cloud or self-hosted.

Pick Hatchet over QueueFlow when you need a dashboard today, conditional branching or durable execution inside workflows, rate limits and fair concurrency keys, or a managed cloud option. It covers more of the workflow space than QueueFlow does, and that gap is real. Pick QueueFlow when you want something smaller to operate (one binary and a database, no message broker, no cloud dependency), a REST API with a code-generated OpenAPI spec and clients you can regenerate yourself, or plain job-queue semantics with an explicit lease protocol you can implement in any language in an afternoon.

#Graphile Worker

Graphile Worker is a Node.js job runner on Postgres with a minimal surface: tasks are files in a tasks/ folder, jobs are added with addJob or graphile_worker.add_job(...) from SQL, delivery uses LISTEN/NOTIFY, retries use exp(least(10, attempt)) seconds of backoff up to maxAttempts (default 25), and a crontab file next to tasks/ provides UTC schedules with optional backfill. jobKey with jobKeyMode lets you replace or deduplicate a pending job. There is no dashboard, no DLQ (exhausted jobs stay in the table until an admin function or cleanup handles them), and no workflow layer.

Pick Graphile Worker over QueueFlow when you are all-Node, want to enqueue from database triggers or SQL functions, and want the smallest possible footprint inside an existing Postgres-backed app. It is a very good fit for PostGraphile and PostgREST setups. Pick QueueFlow when workers are in several languages, when you need DAG workflows or a dead-letter queue, when jobs are created by callers you do not fully trust and tenant isolation matters, or when you prefer an HTTP boundary between the queue and the application.

#pg-boss

pg-boss is the closest architectural relative on this list: a Node.js library on Postgres 13+, claiming with FOR UPDATE SKIP LOCKED, with per-job retries and exponential backoff, deferred jobs, priorities, cron and RRULE schedules with time zones, named dead-letter queues with redrive, queue policies for throttling and singletons, transactional job creation through ORM adapters, OpenTelemetry, pub/sub, a dashboard package, and support for CockroachDB, Citus, and embedded PGlite.

Pick pg-boss over QueueFlow when you are all-Node and want to create jobs inside your own database transactions, when you need throttling or singleton queue policies, time-zone cron, or OpenTelemetry, or when you want a dashboard now. It is mature and well maintained. Pick QueueFlow when the queue should be a service shared by several languages, when you want DAG workflows with failure policies, when you want tenant isolation enforced by the queue rather than the application, or when you want the lease-token protocol's guarantee that a stalled worker cannot overwrite a job that has since been retried or completed elsewhere. The pg-boss migration guide goes through the semantic differences.

#Temporal

Temporal is a durable-execution platform, not a job queue. Workflows are ordinary code in one of eight SDK languages; the Temporal Service records every event, and when a worker dies another one replays the history and continues from where execution stopped. It brings a Web UI, Schedules with backfill and overlap policies, retry policies enforced by the service, and a choice of Cassandra, MySQL, or PostgreSQL persistence with Elasticsearch recommended for visibility, self-hosted as a multi-service cluster or as Temporal Cloud.

Pick Temporal over QueueFlow when your workflows have branching, loops, timers measured in days, signals from outside, human-in-the-loop steps, or sagas with compensation, and when you can staff running (or pay for) the platform. A declared DAG of jobs is a poor fit for those shapes; durable execution is built for them. Pick QueueFlow when the problem is background jobs with retries and some fixed fan-in/fan-out pipelines, when the operational budget is one binary and the Postgres you already have, or when you want a plain REST API rather than an SDK-mediated programming model.

#Where QueueFlow sits

QueueFlow is a server, not a library, and that is the main axis of the comparison. Compared with the embedded Postgres queues (River, Graphile Worker, pg-boss) it costs you one more process to run and buys you language independence, tenant isolation, and an explicit lease protocol. Compared with the orchestration platforms (Hatchet, Temporal) it is far smaller to operate and far less expressive: static DAGs only, no conditional steps, no sub-workflows, no dynamic fan-out, no dashboard yet, no OpenTelemetry, no rate limits, and no webhooks. Those gaps are listed on the roadmap and in the FAQ.

#Sources

All checked 2026-10-10.

River:

Hatchet:

Graphile Worker:

pg-boss:

Temporal: