FastAPI background jobs: when BackgroundTasks is not enough
Choose between in-process tasks and durable jobs, deploy FastAPI and a worker on Inquir Compute, and test retries and recovery.
A user uploads a document, and your API needs to extract text, call a model and assemble a report. Holding the HTTP request open for all that work is fragile: the client may disconnect, a proxy may time out, and a retry may create another job.
FastAPI provides BackgroundTasks to send a response before additional work completes. Important jobs need more: a persisted status and a recovery path after a process restart.
What BackgroundTasks actually does
BackgroundTasks runs scheduled work after the response is sent, inside the application process. It does not create durable job storage or promise recovery after a crash. The FastAPI documentation distinguishes it from tools for heavy distributed work.
Use it for small operations whose loss is acceptable or compensated elsewhere, such as a diagnostic event. Paid reports, order processing and mandatory notifications need a durable record.
import logging
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
logger = logging.getLogger("uvicorn.error")
def record_demo_event():
logger.info("Demo background operation completed")
@app.get("/health")
async def health():
return {"status": "ok"}
@app.post("/demo")
async def demo(tasks: BackgroundTasks):
tasks.add_task(record_demo_event)
return {"message": "Response sent; the diagnostic operation runs next"}
This demonstrates execution order, not a durable queue. A log should not be the only record of accepted business work.
Accept a job without losing it
A report-generation API can use this contract:
POST /reportsvalidates the user, input and idempotency key.- The server persists the job and its input.
- Only after storage succeeds does it return
202 Acceptedwith ajob_id. - A worker performs the work and stores a result or failure reason.
GET /reports/{job_id}exposes status only to that job’s owner.
These are application routes you implement, not built-in Inquir endpoints. Useful states include queued, running, succeeded and failed. A job lease or equivalent recovery mechanism must reclaim abandoned running jobs.
When the database and broker are separate systems, a crash between saving a job and publishing its message can leave work stranded. A transactional outbox is one solution: save the job and an outgoing event in one transaction, then let a separate dispatcher publish with retries. That dispatcher also needs monitoring and duplicate protection.
Make retries safe
A worker may create an external report and crash before saving the result. The queue will deliver the job again. Without safeguards, the second attempt can consume resources or create another object.
Persist an operation key, check existing results and pass an idempotency key to external APIs that support it. Bound attempts, add backoff and distinguish temporary network failures from invalid input. Repeating an authentication failure forever will not fix a credential.
A queue alone does not guarantee exactly one execution. The useful goal is one business outcome despite multiple attempts. See idempotency for background jobs.
Deploy the API and worker
The API accepts HTTP requests; an always-running worker consumes jobs independently. Separate processes can be released and scaled independently while sharing the storage contract and access rules.
An HTTP entrypoint can be:
uvicorn main:app --host 0.0.0.0 --port 8080
Install pinned FastAPI and Uvicorn versions during the image build. In Inquir, create an HTTP application, set its port to 8080 and its health path to /health, then build and release it. Add connection credentials through application variables. Keep the worker private and always running, with its own command and the same durable job store. Follow application deployment and FastAPI hosting.
Do not enable --reload in production. Binding to 127.0.0.1 inside a container prevents normal ingress from reaching the process.
Start with one Uvicorn process per container and measure memory and latency. Every additional process has its own memory and connection pool. Four processes with ten database connections each can open forty connections before you add another replica. This is configuration arithmetic, not a benchmark. See FastAPI worker processes.
For multi-step work, consider Inquir background jobs and pipelines. Existing Python code does not automatically become a pipeline step: adapt the handler, input and result to the platform contract.
Test more than a green health endpoint
In a test environment, accept a job and stop its worker before completion. After recovery, verify that the job still exists, its status advances and the external effect is not duplicated. Repeat a request with the same idempotency key and confirm another user cannot access its job_id.
Measure the age of the oldest job, retry count and time to completion alongside HTTP latency. A fast 202 says little about a queue whose jobs remain stuck for hours.