Choosing a backend
This is not a development versus production choice. All three run in production. Pick based on what you need.sync, when blocking the request is acceptable
sync, when blocking the request is acceptable
- Simple apps where jobs finish fast enough
- Debugging job logic, since errors surface in the request
- Cases where infrastructure simplicity matters more than response time
thread, for fast responses without infrastructure
thread, for fast responses without infrastructure
Jobs run in a Python thread pool. Choose it when:
- You want sub-second response times
- You would rather not run Redis
- Jobs are fire-and-forget, and losing a few on a crash is acceptable
- You need concurrency limits for memory-hungry work such as ML or transcription
rq, when reliability matters
rq, when reliability matters
Jobs persist to Redis before acknowledgement, and workers run as independent
services. Choose it when:
- Losing a job would cause real problems, as with payments or notifications
- You need to retry failures and inspect history
- You run multiple servers
- You need scheduled or recurring tasks
- Background processing has to survive deploys and crashes
Configuration
JOB_BACKEND is unset, REDIS_URL selects rq. See
automatic backends.
Defining and enqueuing
Concurrency control
Cap concurrent executions to keep memory-hungry work from exhausting the machine.Retries
Failed jobs retry with exponential backoff.Resource monitoring
.env
Scheduled tasks
Recurring jobs need the RQ backend and rq-scheduler.From a platform scheduler
When your host runs cron jobs as commands, point it atfeather jobs run. It runs one @job function in the
current process, on any backend, and exits non-zero if the job raises.
Workers as services
On the RQ backend, workers are independent processes that share your codebase but run separately from the web server. They have full access to your models, services and config, on their own lifecycle. That separation is what makes them recoverable. If the web server crashes, jobs already in Redis keep waiting, and a restarted worker picks up where it left off. If a worker crashes mid-job, RQ marks the job failed so it can be retried. Nothing disappears quietly. That suits work which has to complete eventually: billing cycles, subscription renewals, webhook delivery, report generation. Workers also replace cron. Rather than configuring an external scheduler, you enqueue delayed or recurring work from application code, and the worker’s built-in scheduler promotes delayed jobs when they come due. A billing job enqueued withdelay=55 fires on
time even if the web server restarted in between.
feather worker covers the setup that would otherwise need a custom script. It creates
the Flask app and pushes app context so jobs can reach the database and config, uses
SimpleWorker on macOS to avoid the fork() crash with the Obj-C runtime, and enables
the built-in scheduler by default.
Deploying workers
In production, workers run as separate services from the same Docker image as the web server, with a different start command. The generateddocker-compose.yml already
includes one when background jobs are enabled.
docker-compose.yml
docker compose up -d --scale worker=3, unless your jobs include a singleton
loop such as a scheduler or a billing tick, which must not run twice.
- Workers share the image, environment variables and database with 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
- Restarting the web service doesn’t interrupt running jobs