Skip to main content
Most apps need to do work outside the request cycle: sending email, processing uploads, calling external APIs. Feather provides three job backends.

Choosing a backend

This is not a development versus production choice. All three run in production. Pick based on what you need.
  • 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
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
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

When JOB_BACKEND is unset, REDIS_URL selects rq. See automatic backends.
Using the thread backend in development? Set FLASK_DEBUG=0 in .env. Flask’s auto-reloader restarts the process on every file change and kills running threads, so jobs die mid-execution each time you save.

Defining and enqueuing

Concurrency control

Cap concurrent executions to keep memory-hungry work from exhausting the machine.
Jobs queue when the limit is reached, first in first out within each task type. Different tasks have independent limits. This matters for audio and video transcription, ML inference, rate-limited external APIs, and database work bounded by the connection pool.

Retries

Failed jobs retry with exponential backoff.

Resource monitoring

.env
Error logs for failed jobs then carry memory in MB, memory percent, CPU percent and thread count.

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 at feather 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 with delay=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 generated docker-compose.yml already includes one when background jobs are enabled.
docker-compose.yml
Scale with 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
Set JOB_SERIALIZER=json for RQ. Pickle payloads are code execution for anyone who can write to your Redis.

Managing the queue