Unit 11.00: Background tasks and what they cannot promise
A background task runs after the response, in the same process, with no guarantees.
Three things it cannot promise
A task scheduled after a 202 response.
The code shows the response returning before the task has run.
from fastapi import BackgroundTasks, FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
SENT = []
def send_receipt(email: str) -> None:
SENT.append(email)
@app.post("/orders", status_code=202)
def create_order(background: BackgroundTasks) -> dict:
background.add_task(send_receipt, "[email protected]")
return {"status": "accepted"}
client = TestClient(app)
r = client.post("/orders")
print(f"{r.status_code} {r.json()}, receipts sent: {len(SENT)}")
print("""
The task ran after the response was sent -- in the same process. Three things
it cannot promise.
It is not durable: a restart or a crash loses it silently. It is not retried.
And it competes for the same workers, so a slow task slows real requests.
Use it for cheap, best-effort work. Anything that must happen needs a queue.
""")
It is not durable - a restart or a crash loses it with no record. It is not retried. And it competes for the same workers, so a slow task makes real requests slower.
That makes it right for cheap, best-effort work where loss is acceptable: a notification, a cache warm, a metric. Anything that must happen needs a queue.
The mistake this prevents
The mistake is using it for work that must complete - sending a receipt, charging a card, writing an audit record. It usually works, which is the problem: the failures are silent and rare enough to go unnoticed until they are counted.
Takeaway
Background tasks are best-effort, in-process and not retried. Use them only where losing the work is acceptable.
