Choosing a backend
The choice is not “development versus production”. All three work in production. It is about what you are trying to achieve.sync — when blocking the request is acceptable
sync — when blocking the request is acceptable
- Simple apps where job execution is fast enough
- Debugging job logic, since errors appear in the request
- Apps where infrastructure simplicity matters more than response time
thread — fast responses without infrastructure
thread — fast responses without infrastructure
Jobs run in a thread pool managed by Python. Choose it when:
- You want sub-second response times
- You do not want to run Redis
- Jobs are fire-and-forget, and losing some on a crash is acceptable
- You need concurrency control for memory-intensive tasks such as ML or transcription
rq — when reliability is critical
rq — when reliability is critical
Jobs are persisted to Redis before acknowledgement, and workers run as independent
services. Choose it when:
- Losing a job would cause real problems — payments, notifications
- You need job visibility: retry failures, see history
- You are running multiple servers
- You need scheduled or recurring tasks
- You want background processing that survives deploys and crashes
Configuration
Defining and enqueuing
Concurrency control
Limit concurrent executions to prevent resource exhaustion. Essential for memory-intensive work.Retries
Failed jobs retry with exponential backoff.Resource monitoring
.env
Scheduled tasks
For recurring jobs, use the RQ backend with rq-scheduler.Workers as services
With the RQ backend, workers are independent processes that share your app’s codebase but run separately from the web server. Think of them as sidecars — full access to your models, services and config, but their own lifecycle. This matters because workers are self-healing. If your web server crashes, jobs already in Redis keep waiting. When the worker restarts it picks up where it left off. If a worker crashes mid-job, RQ marks the job failed and it can be retried. Nothing is silently lost. That makes workers suitable for operations that must eventually complete: billing cycles, subscription renewals, webhook delivery, report generation. Workers also replace cron. Instead of configuring external schedulers, you enqueue delayed or recurring work through application code. The worker’s built-in scheduler promotes delayed jobs automatically — a billing job enqueued withdelay=55 fires
exactly when it should, even if the web server restarted in between.
feather worker handles the setup that would otherwise need a custom script: it creates
the Flask app and pushes app context so jobs can query the database and read config, it
uses SimpleWorker on macOS to avoid the fork() crash with the Obj-C runtime, and it
enables the built-in scheduler by default.
Deploying workers
In production, workers run as separate services sharing the same Docker image as the web server, just with a different start command. The generateddocker-compose.yml already
has one when you enable background jobs.
docker-compose.yml
docker compose up -d --scale worker=3, unless your jobs include a singleton
loop — a scheduler, a billing tick — that must not run twice.
- Workers share the same image, environment variables and database as the web service
- Scale them independently, or dedicate workers to specific queues
- Each worker connects to Redis for job pickup and to your database for business logic
- Workers survive web deploys: restarting the web service does not interrupt running jobs