Optimizar el coste: lo gratis, el almacén y el trabajo repetido
Tres palancas de coste ordenadas por rentabilidad, y ninguna consiste en escribir código más listo. La primera es cobrar cero por lo que ya es gratis: en 2026 las peticiones a assets estáticos no consumen cuota, de modo que un sitio entero puede servirse sin gastar una sola petición facturable. La segunda es elegir el almacén por la forma de su curva de coste: KV es barato leyendo, R2 no cobra egress, D1 factura filas. La tercera es dejar de recomputar en cada invocación lo que podría calcularse una sola vez.
La factura del edge rara vez sube por una razón interesante. No sube porque alguien escribiera un algoritmo cuadrático ni porque el tráfico se disparara de golpe; sube porque miles de peticiones que podían no haber llegado nunca al Worker llegaron, porque un dato que se lee un millón de veces al día vive en el almacén cuya curva de precio castiga precisamente las lecturas, y porque cada invocación repite un cálculo idéntico que solo hacía falta hacer una vez. Las tres palancas más rentables del coste en el edge no exigen escribir código más listo: exigen decidir mejor dónde ocurre cada cosa, y su orden importa: primero lo que ya es gratis, después dónde vive cada dato y solo al final qué se calcula.
- Aprovechar que las peticiones a assets estáticos no consumen cuota facturable en 2026.
- Elegir almacén por la forma de su curva de coste y no por su ficha técnica.
- Reconocer y eliminar el trabajo que se repite idéntico en cada invocación.
- Estimar el coste de una arquitectura antes de construirla, con aritmética de servilleta.
Lo que ya es gratis y casi nadie aprovecha
El plan gratuito de 2026 ofrece 100.000 peticiones al día y 10 ms de CPU por petición, y el de pago arranca en 5 dólares al mes. Pero el número que de verdad cambia las cuentas es otro y pasa desapercibido: las peticiones a assets estáticos son gratis. No consumen la cuota diaria, no suman al contador facturable y no gastan CPU, porque las sirve la infraestructura de la plataforma sin invocar tu código.
La consecuencia es grande. Un sitio con cien archivos entre HTML, CSS, tipografías, imágenes y JavaScript puede servirse íntegro sin gastar una sola petición de las cien mil. Solo cuando una ruta necesita lógica —una API, una página que depende del usuario, un formulario— se invoca el Worker y empieza a contar. La regla práctica es contundente: lo que no cambie por petición debe ser un asset, no una respuesta generada. Y su alcance es mayor de lo que sugiere la palabra asset: no habla solo de imágenes y hojas de estilo, sino también de páginas enteras que hoy se generan en cada visita porque el marco de trabajo las trata como dinámicas cuando su contenido solo cambia al desplegar. Cada una de esas páginas convertida en archivo estático es tráfico que abandona el contador para siempre.
De ahí sale un anti-patrón muy común y muy caro. Alguien monta un Worker que intercepta absolutamente todo el tráfico del dominio para añadir una cabecera de seguridad, y con esa decisión convierte cada imagen y cada fuente en una petición facturable. Un sitio con cuarenta recursos por página multiplica su consumo por cuarenta a cambio de una cabecera que podría declararse en la configuración de assets. Diseñar el enrutado para que el Worker solo se despierte cuando aporta algo es, en la práctica, la optimización de coste más rentable que existe.
La configuración de assets permite precisamente eso: declarar qué rutas se sirven directamente desde la plataforma y cuáles deben pasar antes por tu código, de modo que la frontera entre lo gratis y lo facturable se decide en un archivo y no por accidente. Vale la pena revisarla con la misma seriedad con la que se revisa una consulta lenta, porque su efecto es del mismo orden de magnitud y no cuesta ni una línea de lógica.
Esta palanca tiene además una propiedad de la que carecen las otras dos: no tiene contrapartida. Elegir un almacén distinto obliga a reescribir accesos y a razonar sobre consistencia; recortar trabajo repetido exige entender qué es seguro compartir entre peticiones. Sacar los assets del Worker no degrada nada, no cambia la semántica de nada y además reduce la latencia, porque la plataforma los sirve desde su propia caché. Cuando una optimización mejora las tres magnitudes a la vez y no tiene coste asociado, lo raro es no haberla hecho ya.
flowchart TD A[peticion entrante] --> B[coincide con un asset estatico] B -->|si| C[servido por la plataforma sin coste] B -->|no| D[se invoca el Worker] D --> E[suma una peticion y su CPU] style C fill:#a6e3a1,color:#11111b style E fill:#f9e2af,color:#11111b
Antes de optimizar nada, lista las rutas de tu proyecto y marca cuáles producen una respuesta distinta según quién pregunta o cuándo. Las que no lo hacen no deberían pasar por el Worker en absoluto. En proyectos con mucho contenido esa criba suele reducir el volumen facturable en un orden de magnitud sin tocar una línea de lógica.
Elegir el almacén por la forma de su curva
Cada pieza de almacenamiento cobra por una dimensión distinta, y esa dimensión —no la latencia, ni la comodidad de su API— es lo que determina si tu patrón de acceso sale barato o ruinoso. Dos sistemas con el mismo precio nominal pueden diferir en dos órdenes de magnitud si uno cobra por lectura y el otro por byte servido.
| Pieza | Dónde duele el precio | Patrón que le sienta bien |
|---|---|---|
KV |
Escrituras y almacenamiento | Leer muchísimo lo que cambia poco |
R2 |
Operaciones y almacenamiento, sin cargos de egress | Servir bytes grandes al mundo entero |
D1 |
Filas leídas y filas escritas | Consultas que devuelven conjuntos pequeños |
Durable Objects |
Peticiones y tiempo con estado activo | Coordinación puntual sobre una entidad |
Caché del edge |
Nada por acierto | Todo lo que tolere unos segundos de desfase |
Nótese que la caché aparece en esa tabla como una pieza más, y no por capricho tipográfico: para muchos datos, la decisión correcta no es qué almacén usar sino si hace falta consultarlo en cada petición. Una lectura que acierta en la caché no paga operación en ningún almacén, y por eso la caché suele abaratar más que cualquier cambio de pieza. Colocarla mentalmente junto a las demás evita el error de elegir almacén para algo que no debía consultarse tantas veces.
Esa tabla se lee mejor si se invierte la pregunta habitual. En vez de preguntar cuál es el almacén más barato, que no tiene respuesta, hay que preguntar cuál es la dimensión que tu carga usa de forma intensiva y descartar las piezas que la cobran caro. Una carga de lectura masiva descarta lo que cobra por lectura; una carga de escritura constante descarta lo que cobra por escritura; una carga de bytes servidos al público descarta lo que cobra el tráfico de salida. La elección correcta emerge por eliminación, y por eso es tan robusta frente a cambios de tarifas.
Tres consecuencias merecen enunciarse por separado. La primera: KV es barato leyendo y caro escribiendo, de modo que encaja perfectamente con banderas de funcionalidad, configuración global, tablas de rutas y traducciones, y encaja fatal con contadores o con cualquier cosa que se actualice por petición. La segunda: R2 no cobra egress, lo que lo convierte en la respuesta correcta para vídeo, imágenes originales, descargas y copias de seguridad, un terreno donde los proveedores tradicionales facturan el tráfico de salida a precios que dominan la factura entera. La tercera: D1 factura filas, así que la diferencia entre una consulta con índice que toca diez filas y un recorrido completo que toca cien mil no es solo de latencia, es directamente de dinero, y un índice ausente se paga en cada petición para siempre.
Hay además un error de asignación que se repite con una frecuencia notable: guardar en D1 un dato que se lee un millón de veces al día y se escribe una vez por semana. Funciona, la latencia parece aceptable y la factura crece linealmente con el tráfico sin que nadie relacione ambas cosas. Ese dato pertenece a KV, o mejor aún a la caché del edge, porque su patrón es de lectura masiva y escritura rarísima. El síntoma para detectarlo es sencillo: si la relación entre lecturas y escrituras supera el millar a uno, el motor relacional está de más.
El caso de R2 merece un párrafo aparte porque su ventaja no es marginal sino de categoría. En la mayoría de proveedores tradicionales, servir bytes al público factura el tráfico de salida, y en un producto con vídeo o descargas ese concepto acaba dominando la factura hasta el punto de condicionar decisiones de producto —limitar calidades, restringir descargas, meter todo detrás de un muro—. Al desaparecer ese cargo, la conversación cambia por completo: el coste pasa a depender del almacenamiento y de las operaciones, dos magnitudes que crecen despacio y de forma predecible, y decisiones que antes eran económicamente imposibles vuelven a estar sobre la mesa.
Cuando el precio se cobra por fila leída, un plan de ejecución mediocre no produce solo lentitud, produce gasto proporcional al tamaño de la tabla en cada petición. Revisar con EXPLAIN QUERY PLAN las consultas de las rutas más transitadas y comprobar que usan índice es una de las pocas tareas de optimización cuyo resultado se puede leer directamente en dinero. Y su efecto es permanente: un índice bien puesto sigue ahorrando cada día, sin que nadie tenga que recordarlo.
// La configuracion global vive en KV y se lee con cache de la propia lectura.
const flags = await env.CONFIG.get("flags", { type: "json", cacheTtl: 300 });
// Los bytes grandes viven en R2 y se sirven en streaming, sin egress.
const obj = await env.MEDIA.get(clave);
if (obj) return new Response(obj.body, { headers: { etag: obj.httpEtag } });
// D1 responde preguntas sobre conjuntos, con indice y con limite.
const { results } = await env.DB.prepare(
"SELECT id, titulo FROM pedidos WHERE estado = ?1 ORDER BY creado DESC LIMIT 20",
).bind("pendiente").all();
cacheTtl en una lectura de KV le dice al punto de presencia cuántos segundos puede conservar el valor sin volver a consultarlo. Sin él, cada punto de presencia repite la lectura con más frecuencia de la necesaria; con él, un dato que cambia una vez al día se lee de la memoria local prácticamente siempre. Es una línea que reduce simultáneamente latencia, operaciones facturadas y carga del sistema.
Un último apunte sobre esta palanca: los cambios de almacén son los más difíciles de deshacer de los tres tipos de decisión de esta lección, porque arrastran migración de datos y cambios de semántica. Conviene por tanto ejecutarlos con una prueba previa acotada a un subconjunto del tráfico y con la aritmética hecha de antemano, en lugar de a raíz de un susto en la factura. Mover un dato al almacén correcto es excelente; moverlo dos veces en un trimestre indica que la decisión se tomó sin medir.
No repetir lo que ya calculaste
La tercera palanca es la más conceptual y la que más se olvida: el contador de CPU es estrictamente lineal en el número de invocaciones, de modo que cualquier trabajo constante que se repita por petición se multiplica por el tráfico. Diez milisegundos de una operación que podría haberse hecho una sola vez, multiplicados por un millón de peticiones, son casi tres horas de procesador facturadas para producir siempre exactamente el mismo resultado.
Los sospechosos habituales son sorprendentemente mundanos: compilar expresiones regulares en cada llamada, construir tablas de traducción o de configuración a partir de un objeto grande, validar un esquema recreando el validador, instanciar clientes y codificadores, ordenar una lista que era la misma para todos. Ninguna de esas líneas parece cara al leerla, y todas comparten el mismo defecto: producen un valor que no depende de la petición dentro del ámbito de la petición.
El trabajo repetido tiene una cualidad que lo hace especialmente escurridizo: es invisible en desarrollo. Con una petición cada pocos segundos, un cálculo de ocho milisegundos no molesta a nadie ni aparece en ninguna gráfica; con tráfico real, ese mismo cálculo es la partida principal del recibo. Ninguna prueba local lo detecta, ninguna revisión de código lo señala si no se está buscando, y por eso suele descubrirse tarde y por sorpresa.
Hay cuatro sitios donde ese trabajo puede mudarse, y conviene recorrerlos en orden. La regla que ordena esos cuatro destinos es una sola: mover el cálculo lo más lejos posible del momento de la petición, sin salirse de lo que su frescura permite. Cuanto antes ocurra, entre más peticiones se reparte y menos veces se repite; el único freno es cuánto puede envejecer el resultado antes de dejar de ser cierto. Formulada así, la decisión sobre dónde va cada cálculo se reduce a estimar su caducidad, que suele ser una pregunta fácil.
El primero es el tiempo de construcción: todo lo que sea derivable del código o del contenido —índices de búsqueda, sitemaps, listados, HTML de páginas que no dependen del usuario— debe existir ya cuando se despliega. El segundo es el ámbito del módulo: lo que se computa fuera del fetch se ejecuta una vez por isolate y se reutiliza en todas las peticiones que ese isolate atienda, lo que en la práctica significa muchísimas. El tercero es un cron o una cola, para lo que hay que recalcular cada cierto tiempo pero no en la ruta crítica. El cuarto es la caché, para lo que sí depende de la petición pero se repite entre usuarios.
// Se ejecuta una vez por isolate, no una vez por peticion.
const rutas = compilarRutas(TABLA);
const codificador = new TextEncoder();
export default {
async fetch(req: Request, env: Env) {
const destino = rutas.match(new URL(req.url).pathname); // sin recompilar nada
return destino ? destino.handle(req, env) : new Response("no encontrado", { status: 404 });
},
} satisfies ExportedHandler<Env>;
La memorización en el ámbito del módulo tiene una condición que hay que respetar con disciplina: solo vale para datos inmutables o inocuos. Guardar ahí una tabla de rutas, un esquema compilado o un codificador es correcto porque son iguales para todos; guardar ahí la sesión del último usuario es una fuga de datos entre peticiones, porque el isolate se reutiliza entre clientes distintos. La frontera es nítida: en el módulo va lo que no depende de quién pregunta.
Hay una forma más ambiciosa de la misma idea que casi nadie explora hasta que ve la factura: precalcular agregados con un cron. Si una portada muestra los diez artículos más leídos y ese cálculo recorre una tabla entera, hacerlo por petición cuesta filas leídas multiplicadas por el tráfico; hacerlo cada cinco minutos y guardar el resultado en KV cuesta doce ejecuciones a la hora, con independencia de que la portada la visiten mil personas o diez millones. La transformación convierte un coste proporcional al tráfico en un coste constante, que es exactamente el tipo de cambio estructural que sobrevive al crecimiento.
El precio de esa transformación es siempre el mismo y hay que pagarlo con los ojos abiertos: el resultado puede estar desactualizado durante el intervalo elegido, y ahora existe una pieza más que puede fallar en silencio. Un precálculo que deja de ejecutarse no rompe nada de forma visible, simplemente sirve un dato viejo cada vez más viejo, así que conviene vigilar su última ejecución con la misma seriedad con la que se vigila una ruta.
Conviene cerrar con una advertencia de método, porque estas tres palancas invitan a aplicarse a ciegas. Ninguna de ellas debe ejecutarse sin haber medido antes qué parte del gasto explica cada una, ya que mover el almacén equivocado o memorizar en el módulo un dato que no era compartible cuesta bastante más que el ahorro que se persigue. La lección siguiente trata precisamente de eso: cómo saber, con datos y no con intuiciones, cuál de estas tres palancas te toca a ti.
Lo gratis primero
Los assets estáticos no consumen cuota. Que el Worker solo despierte cuando aporta lógica vale más que cualquier microoptimización.
Curvas, no precios
Cada almacén cobra por una dimensión distinta. El almacén correcto es aquel cuya dimensión cara tú apenas usas.
Una vez, no un millon
El coste de CPU es lineal en invocaciones. Todo trabajo constante repetido se multiplica por el tráfico.
Existe una idea muy extendida y bastante estéril según la cual optimizar el coste consiste en apretar el código hasta que consuma menos, como si el gasto fuera una consecuencia de la calidad de las instrucciones. En una plataforma que factura trabajo efectivo esa idea explica una fracción minúscula de la factura real, porque el código de un Worker típico ya es rápido y porque los milisegundos que se ganan reescribiendo un bucle son irrelevantes frente a los que se ganan no ejecutando ese bucle en absoluto. Lo que de verdad determina el coste es una serie de decisiones de asignación: qué respuestas se atienden sin invocar código, qué datos viven en el almacén cuya dimensión cara no usas, qué cálculos ocurren en el despliegue en vez de en la petición y qué resultados se comparten entre usuarios en lugar de recomputarse para cada uno. Todas esas decisiones son estructurales, se toman una sola vez, y su efecto es multiplicativo en lugar de aditivo: mover un cálculo del fetch al ámbito del módulo no ahorra diez milisegundos, ahorra diez milisegundos por cada petición que exista mientras el servicio viva, lo cual es una magnitud completamente distinta. Hay además un rasgo que hace especialmente sano este modelo y que conviene apreciar: casi todas las decisiones que abaratan también aceleran. Servir un asset sin invocar el Worker es más barato y más rápido; leer de KV con cacheTtl es más barato y más rápido; acertar en la caché es más barato y más rápido; poner un índice en D1 es más barato y más rápido. Esa alineación entre economía y experiencia no es habitual —en muchas plataformas ahorrar significa degradar— y significa que el trabajo de optimizar coste no es un peaje que se paga a regañadientes, sino el mismo trabajo que mejora el producto, contado con otra unidad. Cuando esto se entiende, revisar la factura deja de ser un ejercicio contable de fin de mes y se convierte en lo que realmente es: una lectura, en dinero, de lo bien o mal asignadas que están las piezas de tu arquitectura.
- Enumera qué rutas de tu proyecto invocan el Worker sin necesitarlo y calcula qué porcentaje del tráfico son.
- Busca un dato guardado en
D1cuya relación entre lecturas y escrituras supere el millar a uno, y decide su nuevo hogar. - Añade
cacheTtla tus lecturas deKVy razona qué valor tolera cada una según su frecuencia de cambio. - Encuentra un cálculo idéntico repetido por petición y muévelo al ámbito del módulo, justificando por qué es seguro hacerlo.
- Estima en una servilleta el coste mensual de tu proyecto con un millón de peticiones diarias, separando peticiones, CPU y almacenamiento.