El límite de CPU de 10 ms en el plan gratuito Workers asusta a los lectores primerizos. Diez milisegundos parecen ridículamente cortos para algo útil. La consecuencia es que muchos equipos pasan inmediatamente al plan pagado por los 30 segundos de CPU, sin comprender que el modelo de medición es fundamentalmente diferente al de un servidor tradicional y que a la mayoría de los trabajadores les sobra CPU incluso en los 10 ms libres. El límite de memoria de 128 MB, por el contrario, se subestima sistemáticamente y es responsable de toda una categoría de fallos silenciosos en la producción.
Cómo funciona realmente el temporizador de la CPU
El tiempo de ejecución de Workers mide el "tiempo de CPU": el tiempo que el subproceso de JavaScript ejecuta código de forma activa. El temporizador se detiene durante cualquier operación de E/S asíncrona: await fetch(), await env.KV.get(), await db.query(), await env.BUCKET.get(). Durante estas esperas, el aislamiento está inactivo y el temporizador no avanza.
El efecto práctico es genial. Un trabajador que realiza cinco llamadas secuenciales fetch() a API externas, cada una con 100 ms de latencia de red, tiene un tiempo de reloj de pared de 500 ms pero usa quizás 6 ms de CPU: solo la serialización de los encabezados, el análisis del JSON de respuesta y la lógica de negocios entre las llamadas. Para la mayoría de los trabajadores que son esencialmente orquestadores de E/S, el límite de 10 ms es generoso.
Lo que realmente usa la CPU son operaciones sincrónicamente intensas: expresiones regulares aplicadas a cadenas grandes, objetos con jerarquía profunda o matrices grandes, operaciones criptográficas incluso cuando la API es asincrónica (el trabajo de hash ocurre en la CPU durante la ejecución) y codificación/decodificación base64 en binarios grandes. Un trabajador que recibe una carga útil JSON de 500 KB y realiza JSON.parse() en ella gastará un tiempo de CPU mensurable en esta operación; el análisis es sincrónico.
En el plan pago, el límite llega hasta 30 segundos de tiempo de CPU, lo cual es suficiente para casos de uso computacionalmente intensivos: compresión, generación de imágenes, inferencia de modelos pequeños. Pero incluso en pagos, las operaciones de CPU que duran más de unos pocos segundos son un síntoma de un diseño deficiente para el borde: los trabajadores fueron optimizados para baja latencia, no para procesamiento pesado.
El temporizador de la CPU no es lo que mata a los trabajadores en producción
Lo que realmente derriba a los trabajadores en producción sin previo aviso es la memoria. El límite de 128 MB parece razonable hasta que comprenda lo que cuenta: el script sin comprimir en el montón V8, todos los cierres de módulos que existen en el ámbito global desde el inicio de la invocación, además de todo lo que se asigna durante el procesamiento de solicitudes en curso.
El guión en sí puede consumir más de lo que cree. Un trabajador comprimido de 500 KB puede ocupar entre 3 y 4 MB después de ser descomprimido y analizado por V8. Las dependencias importadas en el alcance del módulo (bibliotecas de validación, analizadores, SDK) permanecen en la memoria mientras dure el aislamiento, incluso si no se utilizan en la solicitud actual. El alcance global se comparte entre solicitudes que tienen los mismos procesos aislados de forma secuencial en el mismo PoP.
La asignación que con mayor frecuencia activa el límite es response.arrayBuffer() o request.arrayBuffer(). Llamar a este método en una respuesta de 40 MB asigna 40 MB de montón inmediatamente. Si el trabajador luego crea más estructuras de datos a partir de este búfer (objetos analizados, copias transformadas), el uso de memoria puede exceder los 128 MB antes de que finalice el procesamiento. El tiempo de ejecución elimina el aislamiento en este punto, el cliente recibe un error 1101 y no hay ningún seguimiento de la pila, solo un error de tiempo de ejecución reportado como una excepción genérica en Wrangler Tail.
El streaming como estrategia de supervivencia de la memoria
La solución al problema de la memoria con cargas útiles grandes es no materializar nunca todo el contenido como un búfer. En lugar de response.arrayBuffer(), use response.body, que es un ReadableStream, y procese los datos en fragmentos con TransformStream.
La API de Workers Streams sigue la especificación WHATWG Streams, la misma disponible en los navegadores modernos. Un TransformStream tiene un writable y un readable: conectas el readable de la respuesta ascendente al writable de la transformación y conectas el readable de la transformación a la respuesta que envías al cliente. Los fragmentos fluyen a través de la tubería sin tener números enteros completos en la memoria al mismo tiempo.
Esta arquitectura tiene una consecuencia importante: ya no se puede leer todo el contenido para tomar decisiones que dependen de todo el archivo antes de empezar a responder. Para los casos en los que necesita acceso aleatorio (por ejemplo, procesar un CSV que requiere un pedido global), Workers no es el lugar para estar. Para las transformaciones que operan fragmento por fragmento (recompresión, reemplazo de texto, filtrado de líneas), la canalización de flujo lo resuelve sin presión de memoria.
Para cargas que van a R2, el enlace acepta directamente un ReadableStream:
await env.BUCKET.put(key, request.body, { httpMetadata }) — sin almacenar nada en el búfer. El flujo del cuerpo de la solicitud va directamente al R2 en fragmentos.
Operaciones que sorprenden por su uso de CPU
La codificación y decodificación Base64 son operaciones que requieren un uso intensivo de la CPU proporcionales al tamaño de los datos y el resultado ocupa un 33 % más de memoria. Si transporta archivos binarios, vale la pena preguntarse si es necesaria la codificación; Muchos usos de base64 son limitaciones de HTTP heredadas que ya no se aplican a los tipos binarios nativos y de recuperación.
Las operaciones de hash a través de Web Crypto API tienen una firma asíncrona (await crypto.subtle.digest('SHA-256', data)), pero el hash consume CPU proporcional al tamaño de los datos. Un trabajador que realiza HMAC-SHA256 en cada solicitud para verificar un webhook está gastando CPU real en él, lo cual es irrelevante para cargas pequeñas pero significativo por encima de unos pocos cientos de KB.
Las expresiones regulares en cadenas largas pueden resultar sorprendentemente caras. Los patrones con retroceso catastrófico (múltiples cuantificadores anidados en el mismo conjunto de caracteres) pueden triplicar el tiempo de CPU en cadenas de unos pocos kilobytes. Mida con cadenas realistas, no con entradas de 10 bytes que funcionen en la prueba.
Qué buscar para detectar la presión de recursos
wrangler tail devuelve cpuTime en cada evento: el tiempo de CPU medido en milisegundos para esa invocación. El registro estructurado de este valor nos permite detectar regresiones: una implementación que aumenta la CPU p99 de 3 ms a 12 ms significa que se ha introducido alguna nueva operación sincrónica.
El uso de la memoria no se expone directamente mediante la invocación en cola. La forma de detectar la presión de la memoria antes de exceder el límite es observar el comportamiento del aislado: si el tiempo de ejecución comienza a crear nuevos aislados con más frecuencia de lo normal para el mismo PoP (visible como un aumento en los arranques en frío), podría ser una señal de que el recolector de basura está descartando los aislados antes o debido a la presión de la memoria.
La capacidad de reutilizar un aislamiento entre solicitudes en el mismo PoP es una optimización importante: el costo del arranque en frío (descomprimir el script, inicializar V8, ejecutar el código del módulo de nivel superior) ocurre una vez, y las solicitudes posteriores reutilizan el aislamiento ya calentado. Los cierres de módulos que se almacenan en la memoria entre solicitudes son el mecanismo de almacenamiento en caché más rápido disponible en Workers: más rápido que KV, más rápido que cualquier subsolicitud.
Lea también
- Trabajadores de Cloudflare en producción: qué cambia después de hello world
- D1 en producción: rendimiento, límites y lo que no escala por sí solo
- Trabajadores de Cloudflare: Guía práctica para la informática perimetral sin servidor
- Objetos duraderos en producción: cómo será el billete y los límites que sorprenden
- [WebAssembly en el borde: por qué es importante comenzar de forma rápida y aislada28
- Trabajadores + D1 + KV + R2: componer vinculaciones en un mismo servicio
