Comparison

Runqy vs RQ vs Dramatiq

RQ and Dramatiq are libraries: you import them in your app, call a function, and a worker you deployed runs it. Runqy is a server: your app posts a job, and Runqy takes care of the workers that run it, including deploying them and renting machines for them.

All three use Redis. This page compares what each one gives you out of the box. Runqy is our project; we say when the libraries are the better fit.

What you get out of the box

Features available without writing glue code or adding another service.

Out of the boxRQDramatiqRunqy
Broker Redis, Valkey RabbitMQ, Redis Redis
What a job is A Python function call A message to an actor JSON payload to a worker process
Retries with backoff✓ Retry(max, interval)✓ per actor✓ per queue
Execution timeout✓ job_timeout✓ time_limit✓ per queue, process restarted
Expire jobs that waited too long✓ ttl✓ max_age✓ pending_timeout
Priorities~ queue order on each worker✓ per actor✓ weighted or strict sub-queues
Delayed jobs✓ enqueue_in / enqueue_at✓ delay✗
Chains and groups~ depends_on✓ pipelines, groups✗
Rate limiting✗ ✓ rate limiters✗
Keeps a model loaded between jobs~ your worker code~ your worker code✓ @load
Enqueue over HTTP from any language✗ ✗ ✓ REST API, batch endpoint
Web dashboard~ rq-dashboard, separate✗ community tools✓
Ships your code to workers✗ Docker/CI✗ Docker/CI✓ git clone + deps
Rents and releases GPU machines✗ ✗ ✓ RunPod, Vast.ai
Remote drain, code update, upgrade✗ ✗ ✓
Email alerts✗ ✗ ✓
License BSD-2 LGPL-3 MIT

✓ built in · ~ partial or limited · ✗ not built in (you add a separate tool or write it yourself)

The same job in each

RQ

from redis import Redis
from rq import Queue, Retry

q = Queue("images", connection=Redis())
q.enqueue(
    generate, "a fox",
    job_timeout=300,
    retry=Retry(max=2),
)
# Worker, on a machine you set up:
# rq worker images

Dramatiq

import dramatiq

@dramatiq.actor(
    max_retries=2,
    time_limit=300_000,
)
def generate(prompt):
    ...

generate.send("a fox")
# Worker, on a machine you set up:
# dramatiq tasks

Runqy

# main.py, in your Git repo
@load
def setup():
    return {"pipe": load_pipeline()}

@task
def generate(payload, ctx):
    return save(ctx["pipe"](payload["prompt"]))

# Enqueue from any language:
# POST /queue/add
# {"queue": "images",
#  "data": {"prompt": "a fox"}}

With RQ and Dramatiq, the producer and the worker share Python code, and you deploy the worker yourself. With Runqy, the worker clones the repo named in the queue config; retries and timeouts live in that config too.

Choose RQ when

  • Your app is in Python and already runs Redis.
  • Jobs are ordinary functions: emails, reports, cleanups.
  • You want the smallest thing that works, with delayed jobs and dependencies.

Choose Dramatiq when

  • You want Celery's model with a smaller API.
  • You need RabbitMQ, rate limiting, pipelines or groups.
  • The LGPL license is fine for your project.

Choose Runqy when

  • Jobs need a GPU and a model loaded once, not per call.
  • Workers run on several machines, owned or rented, and you do not want to deploy them by hand.
  • Producers are not all Python, or talk to the queue over HTTP.

Try Runqy

Open source and self-hosted. Redis, the server and a worker run with one Docker Compose file.