El coste oculto de –reload en uvicorn: qué consume CPU realmente en producción

Etiquetas:

El problema que nadie ve

Hace dos meses desplegué una API FastAPI en producción y el servidor se comportaba extraño. CPU estable en 40-50% sin motivo aparente. Pensé que era un leak de memoria, que eran los logs, que era la base de datos. Era --reload.

Resulta que copiar-pegar el comando de desarrollo directamente al contenedor Docker es más común de lo que debería ser. Y sí, uvicorn con --reload funciona. El servidor responde. Los requests van rápido. Pero hay un coste que no ves hasta que tienes 10K requests diarios.

Qué es exactamente el file-watcher

El flag --reload de uvicorn inicia un proceso adicional que monitoriza todos los ficheros Python de tu proyecto. No solo tu código. Todos.

Cuando activas --reload, uvicorn:

  1. Inicia un process manager (watchfiles por defecto)
  2. Cada X segundos (por defecto 0.4s) escanea todos los .py del directorio
  3. Calcula checksums o hashes de cada fichero
  4. Si detecta cambios, reinicia el worker completo
  5. Mientras tanto, sigue escaneando en cada ciclo, incluso sin cambios

Este escaneo no es gratis. En un proyecto mediano con 200 ficheros Python distribuidos en vendor, librerías y módulos propios, cada ciclo de watchfiles toca disco y CPU.

Cómo consume CPU en cada request

La parte criminal es que el file-watcher no se pausa durante los requests. Mientras tu API está procesando una solicitud:

  • El monitor sigue escaneando ficheros en background
  • Compite por I/O de disco con tu aplicación
  • En contenedores, si no tienes límites de recursos, puede llegar a consumir más CPU que la propia lógica del request

Medí esto en mi servidor. Con un request simple a una endpoint que tarda 50ms:

Sin --reload:

CPU: 2-3% por request
I/O wait: <1%

Con --reload:

CPU: 8-12% por request
I/O wait: 3-5%

No parece mucho en un request aislado. Pero con 100 requests concurrentes, ese overhead se multiplica.

Cómo detectarlo en tu servidor

1. Mira los procesos en ejecución

ps aux | grep uvicorn

Si ves dos procesos uvicorn (o un uvicorn + watchfiles), tienes --reload activo.

2. Revisa tu comando de inicio

# MAL - esto es lo que probablemente tienes
uvicorn main:app --host 0.0.0.0 --port 8000 --reload

# BIEN - así debe estar en producción
uvicorn main:app --host 0.0.0.0 --port 8000

3. Monitoriza CPU durante un test de carga

# Terminal 1: corre tu servidor
docker run -it tu-contenedor

# Terminal 2: genera carga
ab -n 1000 -c 10 http://localhost:8000/endpoint

Observa top o docker stats. Si ves picos inexplicables, sospecha de --reload.

4. Revisa los logs de uvicorn

Con --reload activo, verás mensajes como:

INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Will watch for changes in these directories: ...

Si ves «Will watch for changes», tienes un problema.

La solución (es obvia, pero importante)

En Docker, asegúrate de que tu Dockerfile NO usa --reload:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# Aquí no va --reload
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

Para desarrollo local, usa --reload sin culpa:

uvicorn main:app --reload

Lo que aprendí

El file-watcher de uvicorn es excelente para desarrollo. Es transparente, funciona bien y acelera el ciclo. Pero en producción es como dejar el ordenador escaneando antivirus continuamente.

Revisé mis otros contenedores después de esto. Encontré tres más con --reload activo. Después de quitarlo, el consumo de CPU bajó entre 30-45%.

Es uno de esos bugs que no es un bug. Tu aplicación funciona. Los requests se procesan. Pero tu servidor está haciendo trabajo invisible que no necesita hacer.


Próxima vez antes de desplegar a producción, grep por --reload. Te ahorrará una sesión de troubleshooting.


Equipamiento recomendado

Enlaces de afiliado. Sin coste extra para ti.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *