Uvicorn workers: почему четыре процесса съели вчетверо больше памяти
API работает на одном процессе, но под нагрузкой начинает отвечать медленнее. Первое желание — поставить workers=4. После перезапуска скорость почти не изменилась, зато контейнер стал упираться в память. Особенно быстро это заметно, если при старте загружается модель, большой справочник или индекс.
Воркеры — отдельные процессы. У каждого свой Python, приложение, настройки и ресурсы, созданные при старте. Увеличение числа процессов меняет не только число обработчиков запросов.
Увидеть процессы своими глазами
Сохраните main.py, установите python -m pip install fastapi uvicorn:
import os
from fastapi import FastAPI
app = FastAPI()
@app.get("/pid")
def process_id():
return {"pid": os.getpid()}python -m uvicorn main:app --workers 4
Сделайте несколько запросов, желательно через новые соединения, и сравните pid с логами запуска. Не обязательно каждый следующий запрос попадёт в другой процесс: соединения и распределение запросов не обещают строгое чередование.
Бюджет ресурсов
Если один прогретый процесс использует около 300 МБ, четыре потребуют существенно больше 300 МБ. Точное значение зависит от приложения и разделяемых страниц памяти; для планирования измеряйте расход в вашем окружении после прогрева и под нагрузкой, а не только сразу после старта.
То же с соединениями. Пул базы на 10 соединений в каждом из четырёх процессов допускает до 40 соединений. Две реплики приложения удвоят этот предел. HTTP-клиенты и фоновые потоки тоже создаются по процессам, если вы инициализируете их в lifespan.
Не запускайте в lifespan задание, которое должно выполняться ровно один раз на весь сервис: четыре процесса могут запустить его четыре раза. Для такой работы нужна отдельная точка запуска или согласование между экземплярами.
Как выбирать число, а не угадывать
Возьмите один характерный сценарий и сравните один, два и четыре процесса при одинаковом входящем потоке. Запишите задержку, долю ошибок, CPU, память и число подключений к базе. Если задержка упирается в медленный SQL, больше процессов могут лишь увеличить очередь у базы.
Для локальной разработки reload удобен, но это другой режим работы. Не используйте его как способ обновления боевого сервера и не рассчитывайте совместить его с моделью нескольких production-воркеров.
Если рост числа процессов упирается в PostgreSQL, продолжите с бюджетом пулов соединений. Если приложение уже контейнеризировано, полезно понимать и проверку OOM после Exit 137.
Собрать следующий этап проекта
Чтобы запуск проекта не держался на наборе команд в вашей памяти, соберите его в Docker Compose. В курсе по Docker вы упакуете HTTP-сервис, подключите PostgreSQL и разберёте сети, тома, обновление приложения и восстановление данных.
Посмотреть программу: Docker и Docker Compose: контейнеризация приложений
Проверить детали: FastAPI: процессы и память, Uvicorn: настройки.
Продолжить на практике
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
- DevOps-инженер с нуля: полный курс ↗
Практический курс по DevOps для начинающих: от настройки Linux и сетей до сборки контейнеров, автоматизации в GitLab CI/CD, развёртывания в Kubernetes, мониторинга и восстановления данных на собственном компьютере.
