FastAPI database session dependency injection considered harmful
The common FastAPI pattern of injecting a database session via Depends() creates a long-lived session that spans the entire request lifecycle. This causes subtle bugs.
The pattern
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
return db.query(User).filter(User.id == user_id).first()
The problems
Stale reads. SQLAlchemy’s identity map caches objects. If you read a user early in the request, then call an external service that modifies that user, subsequent reads return stale cached data.
@app.post("/process")
def process(db: Session = Depends(get_db)):
user = db.query(User).get(1) # cached
external_service.update_user(1) # modifies user in another transaction
user = db.query(User).get(1) # still returns cached version
Memory accumulation. Every object loaded stays in the session’s identity map. Long-running requests that process many records accumulate memory.
Background tasks inherit dead sessions. FastAPI background tasks run after the response is sent, but the session is already closed.
@app.post("/submit")
def submit(background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
background_tasks.add_task(send_email, db) # db is closed when this runs
return {"status": "ok"}
Alternatives
Explicit session management. Create sessions where you need them with clear boundaries.
@app.post("/process")
def process():
with SessionLocal() as db:
user = db.query(User).get(1)
# do work
# session closed, cache cleared
with SessionLocal() as db:
user = db.query(User).get(1) # fresh read
Expire on commit. Use expire_on_commit=True and commit frequently to force fresh reads. This is the default but often disabled.
Session-per-operation. For read-heavy endpoints, use db.expire_all() or create short-lived sessions for each logical operation.
The convenience of dependency injection hides the session lifecycle from you. Sometimes that’s fine. Often it’s not.