FastAPI hits 50k requests per second on commodity hardware with the right setup. Most teams stop at 5k and conclude FastAPI is slow. The bottleneck is almost never FastAPI. Here is a tour of what we changed to get there.
Workers and processes
Default uvicorn with a single worker tops out around 5k RPS on synthetic load. Gunicorn with N uvicorn workers (N = CPU cores) gets us to 25k. Past that, the wins come from elsewhere.
Connection pooling
The single largest jump in our benchmarks: setting asyncpg pool sizes per worker to match the actual concurrent query count, not the defaults. We landed on min 5 / max 20 per worker, after measuring the actual saturation curve.
The bottleneck that surprised us
DNS. Every outbound HTTP call was resolving the hostname fresh. Caching the resolver doubled throughput on endpoints that hit downstream services.
What else mattered
- Pydantic v2 (the C extension): meaningful single-digit-percent wins on every endpoint.
- orjson instead of stdlib json: bigger wins on response-heavy endpoints.
- Disabling access logs at high load: surprisingly large effect.
- Keeping middleware lean: every layer compounds at this throughput.
Reproducing this yourself
Benchmark with realistic traffic, not "hello world." Use connection-reuse settings that match production. Run the load generator on a different machine than the server, on the same network. And measure latency percentiles, not just throughput.