Skip to main content
Many web apps need to do work outside the request cycle — sending emails, processing uploads, calling external APIs. Feather provides three job backends.

Choosing a backend

The choice is not “development versus production”. All three work in production. It is about what you are trying to achieve.
  • 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
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
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

Using the thread backend in development? Set FLASK_DEBUG=0 in .env. Flask’s auto-reloader restarts the process on every file change, which kills running background threads — your jobs are terminated mid-execution whenever you save a file.

Defining and enqueuing

Concurrency control

Limit concurrent executions to prevent resource exhaustion. Essential for memory-intensive work.
Jobs wait in a queue when the limit is reached, first-in-first-out within each task type. Different tasks have independent limits. Where it matters: audio and video transcription, ML model inference, rate-limited external APIs, and database-heavy operations bounded by the connection pool.

Retries

Failed jobs retry with exponential backoff.

Resource monitoring

.env
When a job fails, error logs then include memory in MB, memory percent, CPU percent and thread count.

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

Managing the queue