tezvyn:

Choosing Uvicorn worker count in production

AI-drafted, machine-checkedintermediate
WHAT IT TESTS

Sizing process parallelism.

OUTLINE

workers exist to use multiple CPU cores past the GIL; a common starting point ties count to cores, then you tune by load testing, balancing CPU and memory (each worker is a full copy) against connection-pool…

WHAT THIS TESTS Whether you understand why multiple worker processes exist for an async server and can reason about the CPU, memory, and database limits that bound the right count.

A GOOD ANSWER COVERS A single Uvicorn worker runs one asyncio event loop on effectively one CPU core because of the GIL, so it handles many concurrent I/O-bound requests but cannot use more than one core for CPU work. Running multiple worker processes lets you use all cores and adds fault isolation. A common starting heuristic ties the count to CPU cores, with rules of thumb like number of cores, or 2 times cores plus one for mixed workloads, but the honest answer is that you pick a starting value then load test with realistic traffic and measure latency, throughput, CPU, and memory to converge. The trade-off you manage: more workers increase parallelism, throughput, and resilience, but each worker is a full process that duplicates memory, so total RAM grows roughly linearly, and each worker holds its own database connection pool, so the database sees workers times pool size connections and can hit max_connections. Too many workers also cause CPU contention and context switching. For CPU-bound workloads more workers help up to core count; for I/O-bound workloads a single worker already multiplexes well, so you scale workers mainly to use cores and add headroom.

COMMON WRONG ANSWERS Choosing a large fixed number with no relation to cores or testing. Assuming more workers always means more throughput. Ignoring that each worker copies memory and multiplies database connections. Confusing async concurrency within a worker with cross-core parallelism.

LIKELY FOLLOW-UPS How does this interact with SQLAlchemy pool_size and DB max_connections? How do containers and CPU limits change the calculation? Workers versus threads versus async concurrency?

ONE CONCRETE EXAMPLE On a 4-core box you might start with 4 to 9 workers, load test, and find 4 gives the best latency without memory pressure. Crucially, if each worker's pool_size is 10, four workers can open 40 DB connections, so you confirm Postgres max_connections accommodates that before raising the worker count further.

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.