Desarrollar con Cloudflare Workers localmente es una de las experiencias sin servidor más fluidas que ofrece hoy en día. El wrangler dev sube en segundos, los registros aparecen en la terminal y todo parece funcionar exactamente como esperas. El problema es que esta comodidad enmascara diferencias fundamentales que sólo aparecen cuando el código realmente llega al límite. Muchos equipos descubren tarde que console.log en producción no llega a ninguna parte a menos que haya alguien haciendo activamente wrangler tail, que almacenar en el búfer una respuesta de 50 MB elimina el aislamiento en silencio y que ese controlador que realiza 15 consultas en D1 más 10 lecturas en KV es una bomba de tiempo contra el límite de subrequest.
¿Qué sucede con sus registros en producción?
Localmente, wrangler dev muestra cada console.log en el terminal en tiempo real. En producción, los aislados V8 de Cloudflare no tienen un proceso persistente para capturar este resultado: cada invocación se ejecuta de forma aislada, se ejecuta y desaparece. Solo se puede acceder a los registros a través de wrangler tail, que abre una sesión de transmisión con trabajadores en producción y retransmite metadatos de solicitud, salida console.log, excepciones no detectadas y duración de la CPU.
El punto crítico: esta sesión no persiste. Si no hay nadie siguiendo cuando ocurre un error, el registro se pierde. Para la retención, la solución es Logpush, que exporta registros a R2, S3, Datadog o Splunk a $0,05 por millón de filas para R2. Pero Logpush trabaja con los campos estructurados que emite el tiempo de ejecución del Worker, no con console.log texto libre. La consecuencia práctica es que los registros útiles en producción deben estructurarse desde el principio: JSON con campos como requestId, duration, statusCode, error, campos que tanto wrangler tail pueden filtrar como Logpush pueden exportar fielmente.
El patrón que funciona es crear un contenedor de registro mínimo al comienzo del proyecto, antes de que lo necesite. Algo que serializa a JSON y escribe en console.log, con un campo level que diferencia info de error. Cuando llega Logpush, estos campos están disponibles para filtros y alertas en el objetivo.
Memoria: 128 MB es más pequeño de lo que parece
El límite de 128 MB por aislamiento incluye el script sin comprimir en V8, todos los cierres de módulos y todo el montón de la invocación actual. No son 128 MB para "tus datos", son 128 MB para todo, incluido el tiempo de ejecución en sí.
El error más común es decir response.arrayBuffer() en respuestas grandes. Un archivo de 30 MB descargado de otro servicio para ser procesado y reenviado ocupa 30 MB de montón al instante. Si el procesamiento crea más asignaciones intermedias, el aislamiento excede los 128 MB y se elimina: no hay excepción detectable, no hay respuesta al cliente, solo un error 1101 en el exterior.
La solución es usar response.body como ReadableStream y procesar mediante TransformStream. En lugar de almacenar en búfer y transformar, se crea una canalización donde fluyen fragmentos: leídos desde el origen, transformados en tránsito, escritos en respuesta sin que existan números enteros en la memoria. Para respuestas de más de 1 MB, se debe suponer que se trata de transmisión; El almacenamiento en búfer debe ser una opción explícita y justificada, no la ruta predeterminada.
El mismo razonamiento se aplica a las cargas. El cuerpo máximo de la solicitud es 100 MB, pero request.arrayBuffer() intenta asignar todo a la vez. Para cargas grandes, el procesamiento también debe realizarse en flujo, o el archivo debe ir directamente a R2 a través de put(), que acepta un ReadableStream.
Subsolicitudes: el límite que aparece en el peor momento
Cada fetch(), cada operación en KV, cada consulta en D1, cada lectura en R2 cuenta como una subsolicitud. En el plan pago, el límite es 1000 por invocación. Gratis, 50.
Un controlador que parece razonable (busca al usuario en D1, lee sus preferencias en KV, extrae el documento de R2, llama a una API externa, guarda el resultado en D1) ya tiene 5 subsolicitudes en la ruta feliz. Si este controlador entra en un bucle porque procesa una lista de elementos, el contador aumenta rápidamente. Cincuenta elementos con dos operaciones cada uno ya están cerca de 100. Un error que realiza N+1 consultas en D1 (buscando cada registro secundario individualmente en lugar de con JOIN) puede fácilmente desbordar 1000 antes del final de una sola solicitud compleja.
El error cuando se alcanza el límite no es obvio: el Trabajador recibe un error de red en la subsolicitud que excedió el límite, lo que puede confundirse con inestabilidad de la red o del servicio descendente. El diagnóstico correcto requiere ver los registros de excepciones a través de wrangler tail y correlacionarlos con el patrón de llamada del controlador.
Mitigación: utilice db.batch() en D1 para agrupar varias consultas en una única subsolicitud. Lea las configuraciones de KV una vez durante la inicialización del módulo y el caché en el ámbito global: el aislamiento se puede reutilizar entre solicitudes en el mismo PoP, y la lectura de KV que ocurrió en la primera invocación no necesita repetirse en invocaciones posteriores mientras el aislamiento esté vivo.
Secretos, entornos y el wrangler.toml que va al repositorio
Las variables declaradas en wrangler.toml bajo [vars] son texto sin cifrar en el archivo de configuración, que normalmente va al repositorio. Para cualquier valor confidencial (claves API, tokens bancarios, secretos de webhooks), la única opción correcta es Workers Secrets, declarado como [secrets] en wrangler.toml y almacenado a través de wrangler secret put. El valor se cifra en reposo y se inyecta en tiempo de ejecución como una propiedad de env, sin aparecer en ningún registro o artefacto de compilación.
El wrangler.toml de un servicio que va a producción debe tener entornos explícitos: [env.staging] y [env.production] con sus propios enlaces, rutas y secretos separados. Mezclar la puesta en escena y la producción en el mismo conjunto de encuadernaciones es un accidente a punto de ocurrir, especialmente cuando D1 y KV tienen datos reales en producción y datos de prueba en la puesta en escena. La separación de entornos en wrangler.toml también permite que la canalización de CI se implemente automáticamente en el ensayo y requiera aprobación manual para la producción, sin ninguna lógica adicional en el script de implementación.
Cómo se ve una configuración de producción mínima
Un wrangler.toml listo para producción hace referencia explícitamente a compatibility_date (para no recibir cambios importantes del tiempo de ejecución sin previo aviso), define [observability] con enabled = true para exponer métricas básicas en el tablero y separa [env.staging] de [env.production] con diferentes rutas. Los secretos se enumeran por nombre sin valor; el valor solo existe en Cloudflare, nunca en el repositorio.
El controlador principal tiene un try/catch en el nivel más alto que detecta cualquier excepción no controlada, la registra como JSON estructurado con console.error y devuelve una respuesta HTTP 500 con un requestId rastreable. Sin este límite, una excepción inesperada podría devolver un 200 con cuerpo truncado al cliente mientras que el error real aparece solo en la cola, y solo si alguien está mirando.
Lea también
- Trabajadores de Cloudflare: Guía práctica para la informática perimetral sin servidor
- D1 en producción: rendimiento, límites y lo que no escala por sí solo
- WebAssembly en el borde: Por qué es importante comenzar de forma rápida y aislada
- Trabajadores: depuración, registros y Workers Tail: observabilidad en el borde sin un servidor de registros
- Trabajadores: límites de CPU y memoria, que la documentación no explica bien
- Desarrollando aplicaciones sin servidor con AWS Lambda y Cloudflare Workers en 2025
