Cold starts en agentes IA serverless: Vercel, Workers y Fly
Resumen
Cómo diagnosticar y reducir la latencia del primer request en un agente serverless: Fluid Compute y bytecode cache en Vercel, límite de startup de 1 segundo en Workers (global scope), autostop/autostart y min_machines_running en Fly, y cuándo el cold start no es el problema (healthcheck, webhooks con timeout corto).

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Un webhook de Telegram corta a los 3 segundos. Tu agente en Fly tarda 4 en despertar. El usuario ve silencio y tú ves un 502 que no aparece en logs. Eso es un cold start: el primer request paga el arranque de la instancia. No es un bug de tu prompt; es física de serverless. Las fuentes oficiales fueron consultadas el 3 de septiembre de 2026.
Esta guía cubre Vercel Functions, Cloudflare Workers y Fly Machines — las tres plataformas del stack de deploy. Si el agente vive en un VPS con Docker, el cold start es otro problema (reinicio del contenedor), no el de esta página.
La regla de diagnóstico
Antes de optimizar cold starts, confirma que es un cold start:
- ¿Solo el primer request tras inactividad es lento? → cold start probable.
- ¿Todos los requests son lentos? → modelo, red o tool, no arranque.
- ¿El proveedor del webhook reintenta y duplica respuestas? → timeout del canal, no solo latencia.
Mide p50 y p99 con y sin tráfico reciente. Si p99 sube 10× tras 30 minutos de silencio, estás pagando arranque.
Vercel: Fluid Compute y excepciones
Desde abril de 2025, Fluid Compute es el modelo por defecto en proyectos nuevos. Reutiliza instancias, cachea bytecode en Node 20+ y pre-calienta en Production. La documentación describe cold starts como la excepción, no la norma.
Casos donde aún duele:
- Función de bajo tráfico: pocas invocaciones → pocas instancias calientes.
- Función archivada: tras 2 semanas sin tráfico en Production (48 h en Preview), la primera invocación suma al menos 1 s de latencia.
- Proyecto legacy sin Fluid: habilítalo en Project Settings → Functions.
Para agentes I/O-bound (LLM + tools), Fluid permite varias invocaciones concurrentes en la misma instancia — menos arranques que el serverless de una request por instancia.
Acciones concretas:
- Mantén el handler del webhook delgado; inicializa clientes pesados de forma lazy.
- No importes todo el SDK en el top-level si solo usas una función.
- Si el webhook exige respuesta rápida, responde 200 y procesa en segundo plano (cola o
waitUntilsegún plataforma).
Workers: 1 segundo de global scope
Workers arrancan con isolates V8, no contenedores completos. Un isolate puede arrancar ~100× más rápido que un proceso Node en un contenedor. El global scope (código fuera del handler) debe parsearse y ejecutarse en 1 segundo — el techo es el mismo en Free y Paid. Si se pasa, el deploy falla con Script startup exceeded CPU time limit (10021). Wrangler emite un CPU profile y reporta startup_time_ms en wrangler deploy / versions upload.
Para un agente:
// ❌ En global scope: schema Zod gigante, parse de JSON de tools
const tools = buildToolsFromOpenAPISpec(hugeSpec);
export default {
async fetch(request, env) {
// ...
},
};
// ✅ Lazy: el arranque pasa; el costo va al primer request real
let tools;
function getTools() {
if (!tools) tools = buildToolsFromOpenAPISpec(hugeSpec);
return tools;
}
El heap es 128 MB por isolate (Free y Paid): JS + WebAssembly. No es por invocación. Un isolate puede servir muchas requests concurrentes. Si se pasa de 128 MB, el runtime deja terminar las in-flight y abre otro isolate. Evita estado mutable global salvo que asumas que el isolate puede desaparecer.
El flag cf.cold_start en la request te dice si esa invocación pagó arranque. Úsalo en logs estructurados para separar p99 de cold vs warm.
Fly: autostop, suspend y min_machines_running
Fly no es “function por request”; es una Machine que duerme. fly launch deja por defecto:
[http_service]
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 0
Con min_machines_running = 0, la primera request tras idle despierta la Machine. Eso puede ser 1–5 s según imagen y framework — el deploy en Fly ya lo menciona en errores comunes. CPU y RAM de una Machine stopped o suspended no se facturan. min_machines_running solo aplica a la región primaria; otras regiones no heredan el mínimo. Sin esas claves en fly.toml, el Proxy sí arranca Machines paradas y no las apaga solo.
Opciones oficiales:
| Objetivo | Config |
|---|---|
| Cero cold start en prod | min_machines_running = 1 (pagas RAM 24/7) |
| Ahorrar cuando no hay tráfico | auto_stop_machines = "stop" o "suspend" |
| Resume más rápido, reloj impreciso al despertar | "suspend" (≤ 2 GB RAM, sin swap) |
| Proxy espera al app | Grace period más largo en healthcheck |
Fly documenta en troubleshooting: para frameworks pesados (Rails, JVM), mantén al menos una Machine caliente. Para agentes Node ligeros, un /health que responde antes de cargar el grafo completo del agente reduce 502 en el primer webhook.
Qué hacer según el síntoma

| Síntoma | Plataforma | Causa típica | Fix |
|---|---|---|---|
| Primer request lento tras horas | Vercel | Instancia archivada o sin Fluid | Fluid Compute; ping de keep-alive si el plan lo permite |
| Deploy rechazado 10021 | Workers | Init pesado en global scope | Lazy imports; wrangler check startup |
| 502 solo al primer mensaje | Fly | Machine dormida + webhook corto | min_machines_running = 1 o bot con retry |
| Latencia alta siempre | Cualquiera | LLM o tool lento | No es cold start — mide tiempo hasta primer token |
| Doble respuesta al usuario | Telegram/Slack | Timeout < cold start + retry | Responde rápido; procesa async |
Errores comunes

| Error | Por qué duele | Fix |
|---|---|---|
| Optimizar bundle antes de medir | Puedes estar arreglando el modelo, no el arranque | Log cf.cold_start; Vercel Observability startup time |
min_machines_running = 0 en bot 24/7 | Cada madrugada sin tráfico = sorpresa al primer usuario | 1 Machine caliente o cron de ping interno |
| Healthcheck que llama al LLM | El check tarda 30 s y Fly marca unhealthy | /health solo proceso + DB, sin inferencia |
| Mismo timeout en Preview y Prod | Preview archiva más rápido (48 h) | No uses Preview como referencia de latencia |
Relación con el resto
- Healthchecks: el proxy debe esperar al arranque real.
- Preview vs Production: Preview en Vercel archiva antes — no compares latencias cruzadas.
- Rollback: un deploy que rompe el startup no se arregla con rollback si el binario nuevo arranca más lento — vuelve al deployment anterior y mide.
Cold start es un trade de costo: pagas RAM fija (min_machines_running) o pagas latencia en el primer request. Para un agente de soporte con 50 mensajes al día, una Machine caliente en Fly suele costar menos que perder un cliente en el primer 502. Para un cron nocturno en Workers, el arranque de milisegundos casi no importa.
Checklist
- Mediste p99 con tráfico reciente vs tras 30+ min de silencio
- Vercel: Fluid Compute activo; handler del webhook delgado
- Workers: sin init pesado en global scope; deploy pasa límite 1 s
- Fly:
min_machines_runningdecidido según SLA del canal, no por defecto - Webhook del proveedor: timeout documentado > tu peor cold start medido
-
/healthno llama al LLM
Si después de esto el agente sigue lento en cada request, el cuello de botella está en el modelo o en una tool externa — mira observabilidad y APIs baratas, no en el arranque.
Siguiente paso: si el cold start ya está acotado y necesitas aislar entornos, preview vs production. Si todavía no tienes runtime, el curso gratuito deja un agente local para medir arranque antes de subirlo.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



