Uvicorn reload vs Gunicorn Uvicorn workers: trade‑off for resource‑constrained production
0 reputation · 09 Feb 2023, 22:42 UTC
0 reputation · 09 Feb 2023, 22:42 UTC
Goal: Deploy a FastAPI application in a containerized environment with limited CPU and memory while preserving the ability to iterate quickly during development.
Constraint: The Uvicorn reload spawns a file‑watcher process that consumes extra CPU and file descriptors; in production this can conflict with resource limits and cause repeated restarts when the container is restarted. The alternative is to run Gunicorn with Uvicorn workers, which removes the watcher but adds a process‑manager layer. The exact point at which the reload overhead becomes unacceptable depends on the container’s allocated resources and the frequency of file changes, a threshold not specified in the documentation.
What CPU or memory ceiling makes the watcher’s extra usage unacceptable for a typical workload? How does the rate of file‑system changes per minute influence the likelihood of watcher‑triggered restarts in a production container? At what concurrency level does the Gunicorn worker model provide measurably better latency and stability compared to a single‑process Uvicorn with reload?
35775 reputation · 10 Feb 2023, 07:20 UTC
Do not use Uvicorn --reload in production. Use Gunicorn with Uvicorn workers for any container that is subject to CPU/memory limits and must serve traffic reliably.
There is no documented universal CPU or memory ceiling at which the reload watcher becomes unacceptable, and there is no fixed concurrency level where Gunicorn workers become measurably better. Both thresholds are workload and container specific and must be measured for your app.
Acceptability of the watcher depends on container CPU shares, memory limit, base application footprint, size of the watched directory tree, and OS inotify limits. In resource-constrained containers the watcher overhead can become material relative to the app budget and can contribute to FD exhaustion or OOM kills.
The likelihood of watcher-triggered restarts rises with the rate of filesystem changes per minute. With zero changes the watcher is idle but still consumes resources. With changes from logs, writable volumes, or tmp files the watcher can cause spurious reload loops and repeated cold starts in production.
Measurable latency and stability improvements from the Gunicorn worker model versus a single-process Uvicorn with reload typically appear when concurrent request load exceeds what one asyncio worker can sustain reliably for your specific handlers, I/O patterns, and container limits. The exact concurrency level cannot be given as a fixed number without profiling.
To give a concrete ceiling for your setup, provide the container CPU shares / millicores and memory limit, the size of the watched tree, and the observed file change rate per minute from logs/volumes. That changes the recommendation on worker count and whether any watcher can be tolerated at all.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 10 Feb 2023, 07:42 UTC
In resource-constrained containers, the reload watcher’s cost is often less about a fixed CPU or memory ceiling and more about how the watcher interacts with the container filesystem. On overlayfs or bind-mounted volumes, even infrequent writes can trigger unexpected reloads if watched paths are broad. For concurrency, a single-process Uvicorn with reload cannot use multiple cores regardless of request volume; Gunicorn with multiple Uvicorn workers helps as soon as the workload saturates one core or benefits from process isolation. Rather than guessing thresholds, profile the watcher’s actual RSS and CPU under your cgroup limits, then simulate expected filesystem churn to observe restart behavior. These dynamics vary by Uvicorn version and mount type, so treat any general ceiling as a hypothesis to verify.