0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilor
dفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy
jmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈
qtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0
x▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بd
◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgj
∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknq
فehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux
ilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇
psvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞
wz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0اف
◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفi
π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmp
cلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtw
hknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆
orux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π
vy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xc
░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افeh
∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilo
بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsv
gjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░
nqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑
ux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1ب
▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلgjmpsvy▒◇∆π1بdفilorux▓◆∇∑0افehknqtwz░◈Ξ∞xcلg
rehan@neural-mesh :~
$
R
Rehan Rao
all insights
Backend Architecture 2025 · 08 9 min

FastAPI at scale: shape your service like an OS.

The mistake most teams make: treating FastAPI like a request handler. Treat it like an operating system with schedulers, middlewares, and syscalls, and it holds under any load.

Rehan Rao
AI & Backend Systems Engineer
architecturefastapi.os
ClientHTTP/2 · WSFASTAPI KERNELMiddleware RingDI · Auth · RateLimitRouter · ValidatorsScheduler (asyncio)Handlers (syscalls)Background WorkersPostgresRedisObject Store

Every FastAPI codebase that eventually falls over shares one root cause: it was written as a stack of endpoints instead of a system with clear boundaries. When traffic ramps, the endpoints do too much — auth, validation, DB calls, third-party fan-out — and the event loop starves. The fix is not more workers. The fix is treating the service like a small operating system.

The mental shift

An OS is a scheduler surrounded by rings of privilege. Requests enter at the outermost ring, pass through middlewares (auth, tracing, rate limits), get validated into typed intents, and only then reach handlers — the equivalent of syscalls. Long-running work is delegated to background workers, never blocked on inline.

The kernel model

Below is the shape I converge on for any FastAPI service pushing beyond ~500 rps per pod. Every arrow is await-safe; every box has one job.

Middleware as a ring

Order matters. Trace context first (so failures upstream still get spans), then request logging, then auth, then rate limiting, then body validation. Anything that can reject a request cheaply belongs earlier than anything that touches a database.

app = FastAPI()
app.add_middleware(TraceMiddleware)
app.add_middleware(AccessLogMiddleware)
app.add_middleware(AuthMiddleware)
app.add_middleware(RateLimitMiddleware, per_route=True)

Scheduling & backpressure

The event loop is the CPU. If a handler does synchronous DB work, you have introduced a global lock. Use asyncpg or databases, wrap CPU-heavy work in run_in_executor, and expose a bounded semaphore per downstream so one slow dependency cannot drain the pool.

note
A shared asyncio.Semaphore(64) in front of every third-party HTTP client is the single highest-leverage change I have shipped. It turns cascading failures into graceful degradation.

Background workers

Anything longer than the P99 request budget goes to a queue — arq, Celery, or a plain Redis stream. Handlers stay boring: validate, enqueue, return an ID. Idempotency keys on the enqueue path save you the first time a client retries.

Observability

  • OpenTelemetry spans on every middleware and every outbound call.
  • RED metrics per route: Rate, Errors, Duration — nothing more, nothing less.
  • Structured logs keyed by trace_id, sampled at 10% in prod.

Takeaways

  • Handlers are syscalls. Keep them ten lines or fewer.
  • Every downstream gets a semaphore, a timeout, and a circuit breaker.
  • Async all the way down, or don't bother going async at all.
  • The bottleneck is almost always the DB pool — size it, measure it, alert on it.