1.Gunicorn, Uvicorn, and choosing a worker class
**Gunicorn** is a process manager: one master forks N workers, watches them, and restarts them when they die. A **worker class** decides how each worker handles concurrency — `sync` is one request at a time, `gthread` uses a thread pool, and an ASGI worker runs an event loop. **Uvicorn** is an ASGI server; Django documents running it directly, or under Gunicorn with `-k uvicorn_worker.UvicornWorker` so you get Uvicorn's event loop and Gunicorn's supervision.
Key features
- Gunicorn supervises; the worker class decides concurrency — two separate jobs
- `worker_class` defaults to `sync`: one request per worker at a time
- Setting `threads` > 1 on `sync` silently switches you to the `gthread` worker
- ASGI workers hold many idle connections on one loop — the only affordable way to stream
- Django documents `-k uvicorn_worker.UvicornWorker` from the `uvicorn-worker` package
- An `async def` view that calls the ORM synchronously blocks the whole event loop
The worker classes, and what each is for
The commands Django itself documents
- WSGI · sync workers
- One request per worker, from first byte to last
- Concurrency = worker count. 5 workers = 5 requests at once
- A 2-second outbound call occupies a whole worker for 2 seconds
- Memory scales with workers: every process is a full copy
- Cannot hold a WebSocket or an SSE stream affordably
- Predictable, boring, and correct for most Django sites
- ASGI · Uvicorn workers
- One event loop per process, many requests in flight
- A request awaiting I/O yields the loop instead of holding it
- Long-lived connections (WebSocket, SSE) cost little while idle
- Only pays off if the path is async all the way down
- A synchronous ORM call inside `async def` blocks every connection
- Still wants Gunicorn outside it, for supervision and restarts
Command
gunicorn config.wsgi:application -k gthread --workers 4 --threads 4Gunicorn supervises processes; the worker class decides concurrency, and those are separate decisions. `sync` is the default and means one request per worker, which makes capacity equal worker count — the right, boring choice behind a buffering proxy. `gthread` adds a thread pool for I/O-bound views, and gunicorn switches you to it automatically if you set `--threads` above one on `sync`. ASGI workers hold many connections on one event loop, which is the only affordable way to serve WebSockets or SSE; Django documents `python -m gunicorn myproject.asgi:application -k uvicorn_worker.UvicornWorker`. Match the callable to the class, and remember an event loop is only fast while nothing blocks it.