Guía9 min

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).

VercelCloudflare
Reloj y gráfica de latencia del primer request frente a requests calientes en serverless

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:

  1. ¿Solo el primer request tras inactividad es lento? → cold start probable.
  2. ¿Todos los requests son lentos? → modelo, red o tool, no arranque.
  3. ¿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 waitUntil segú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 arranca Machines paradas y no las apaga solo.

Opciones oficiales:

ObjetivoConfig
Cero cold start en prodmin_machines_running = 1 (pagas RAM 24/7)
Ahorrar cuando no hay tráficoauto_stop_machines = "stop" o "suspend"
Resume más rápido, reloj impreciso al despertar"suspend" (≤ 2 GB RAM, sin swap)
Proxy espera al appGrace 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

Comparativa de cold start y mitigaciones en Vercel, Workers y Fly

SíntomaPlataformaCausa típicaFix
Primer request lento tras horasVercelInstancia archivada o sin FluidFluid Compute; ping de keep-alive si el plan lo permite
Deploy rechazado 10021WorkersInit pesado en global scopeLazy imports; wrangler check startup
502 solo al primer mensajeFlyMachine dormida + webhook cortomin_machines_running = 1 o bot con retry
Latencia alta siempreCualquieraLLM o tool lentoNo es cold start — mide tiempo hasta primer token
Doble respuesta al usuarioTelegram/SlackTimeout < cold start + retryResponde rápido; procesa async

Errores comunes

Errores típicos al confundir cold start con otros problemas de latencia

ErrorPor qué dueleFix
Optimizar bundle antes de medirPuedes estar arreglando el modelo, no el arranqueLog cf.cold_start; Vercel Observability startup time
min_machines_running = 0 en bot 24/7Cada madrugada sin tráfico = sorpresa al primer usuario1 Machine caliente o cron de ping interno
Healthcheck que llama al LLMEl check tarda 30 s y Fly marca unhealthy/health solo proceso + DB, sin inferencia
Mismo timeout en Preview y ProdPreview 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_running decidido según SLA del canal, no por defecto
  • Webhook del proveedor: timeout documentado > tu peor cold start medido
  • /health no 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.