effidevFlutter · Edge de Cloudflare · Optimización de costes en la nube

Cloudflare Workers vs AWS Lambda: comparativa de costes para un backend Flutter entre 1 millón y 100 millones de peticiones al mes

Cloudflare Workers vs AWS Lambda: comparativa de costes para un backend Flutter entre 1 millón y 100 millones de peticiones al mes

Si alguna vez elegiste serverless como backend para tu app Flutter y buscaste “Cloudflare Workers vs AWS Lambda comparativa de costes”, seguramente te encontraste con la mayoría de artículos rematando con conclusiones vagas del tipo “Workers es más barato” o “el ecosistema de Lambda es más grande”. En la práctica, el ganador cambia según la naturaleza de la carga de trabajo y el volumen de tráfico. Este artículo toma los tres casos de uso más habituales en apps Flutter — un servidor de envío de push (llamadas a FCM/APNs), un manejador de webhooks (verificación de RevenueCat, Stripe, App Store Server Notifications) y un servidor de API genérico —, y calcula la factura real en los tramos de 1 millón, 10 millones y 100 millones de peticiones mensuales, incorporando tanto la diferencia estructural de facturación entre ambas plataformas como el coste adicional de API Gateway.

Resumen clave

  • Cloudflare Workers solo cobra el tiempo de CPU consumido, y el tiempo de espera mientras aguarda la respuesta de una API externa no entra en la factura. AWS Lambda cobra el tiempo de ejecución total (wall-clock), incluida la espera, multiplicado por la memoria asignada.
  • Las peticiones y el tiempo de ejecución de Lambda tienen un nivel Always Free permanente de 1 millón de peticiones + 400.000 GB-segundos al mes. El plan gratuito de Workers solo cubre 100.000 peticiones al día (unos 3 millones al mes) y 10 ms de CPU por invocación; en cuanto se supera ese límite de CPU hay que pasar al plan de pago, con un mínimo de 5 $/mes.
  • Cuando expones Lambda por HTTP, añadir API Gateway suma 1,00 $ por millón de peticiones con HTTP API, o 3,50 $ con REST API, como coste aparte. Si solo necesitas un único endpoint, Lambda Function URL elimina por completo este coste. Workers, al llevar el enrutado integrado en el runtime, ni siquiera tiene esta partida.
  • Con los supuestos de este artículo (300 ms de wall-clock por petición, 20 ms de CPU real, 256 MB de memoria), Workers empieza a salir más barato a partir de unos 5,5 millones de peticiones al mes si usas API Gateway, o de unos 11 millones si usas Function URL. Por debajo de esos umbrales, Lambda es más ventajoso.
  • En cambio, para funciones de cómputo intensivo como el redimensionado de imágenes, Lambda resulta más barato en todo el rango de tráfico con una configuración de memoria baja (256 MB). Ahora bien, si la carga es pesada y obliga a subir la memoria a 1,1-1,4 GB o más para conseguir suficiente potencia de CPU, esta ventaja se diluye.

La diferencia fundamental de facturación: tiempo de CPU vs tiempo de ejecución total

Lo primero que hay que entender al comparar ambas plataformas es que difieren en “qué se mide como tiempo para cobrar”.

Según la documentación oficial de precios de Cloudflare, Workers solo factura el número de peticiones y el tiempo de CPU. Mientras un Worker hace un fetch a una API externa y espera la respuesta, el isolate de V8 no está ejecutando instrucciones de verdad, sino esperando, así que ese tramo no aparece en la factura en absoluto. Aunque encadenes cinco llamadas a APIs externas y el conjunto tarde 2 segundos en responder, si el código JS que realmente se ejecuta ahí dentro (parseo, serialización, ramas condicionales, etc.) suma 20 ms, lo único que se cobra son esos 20 ms. El límite de CPU por invocación es de 30 segundos por defecto, ampliable hasta 5 minutos (300.000 ms) mediante configuración en el plan de pago, y los Cron Triggers y los consumidores de Queue admiten hasta 15 minutos.

AWS Lambda, por el contrario, mide el tiempo total desde que arranca el código del handler hasta que devuelve la respuesta, en base wall-clock, y lo multiplica por la memoria asignada a la función (en GB) para facturar en GB-segundos. Mientras espera la respuesta de una API externa, ese entorno de ejecución se sigue contando como “en ejecución”, así que ese tiempo de espera también se factura tal cual. Es decir, en el mismo manejador de webhooks, da igual que la API externa responda en 250 ms o en 800 ms: para Lambda esa diferencia se traduce directamente en una diferencia de coste en GB-segundos. El tiempo de ejecución de Lambda se redondea a milisegundos, el timeout máximo de la función es de 900 segundos (15 minutos) y la memoria se puede configurar entre 128 MB y 10.240 MB, en incrementos de 1 MB. Como referencia, se sabe que el punto de 1.769 MB equivale a la potencia de cómputo de 1 vCPU completa, por lo que las funciones intensivas en CPU suelen desplegarse con una memoria cercana o superior a ese umbral.

La conclusión práctica de esta diferencia estructural es sencilla. Cuanto más tiempo pase una carga de trabajo esperando respuestas de APIs externas o de bases de datos (carga ligada a E/S), más favorece a Workers; cuanto más tiempo de cómputo puro consuma (carga ligada a CPU), más se estrecha o se invierte la brecha entre ambas plataformas. Los servidores de envío de push (llamadas a la API HTTP v1 de FCM) o los manejadores de webhooks (verificación de firma seguida de una llamada a una API externa) que se suelen construir en un backend Flutter son casos típicos de carga ligada a E/S.

Misma lógica, distinto esqueleto de código

En la práctica, el código que se despliega en ambas plataformas es casi idéntico. A continuación se muestra la versión Worker, escrita con Hono, de un handler que recibe un webhook de tipo RevenueCat/Stripe, verifica la firma y dispara un push por FCM.

// Cloudflare Worker (Hono)
import { Hono } from "hono";

type Bindings = { WEBHOOK_SECRET: string; FCM_PROJECT_ID: string };
const app = new Hono<{ Bindings: Bindings }>();

app.post("/webhooks/push", async (c) => {
  const rawBody = await c.req.text();
  const signature = c.req.header("X-Signature") ?? "";

  if (!(await verifyHmacSignature(rawBody, signature, c.env.WEBHOOK_SECRET))) {
    return c.text("invalid signature", 401);
  }

  const event = JSON.parse(rawBody);
  const accessToken = await getFcmAccessToken(c.env); // 서비스 계정 JWT 교환

  await fetch(
    `https://fcm.googleapis.com/v1/projects/${c.env.FCM_PROJECT_ID}/messages:send`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ message: buildFcmMessage(event) }),
    },
  );

  return c.json({ ok: true });
});

export default app;

Al migrarlo a Lambda + Function URL, solo cambia el mecanismo de disparo; la lógica se mantiene igual.

// AWS Lambda (Node.js, Function URL 트리거)
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";

export const handler = async (
  event: APIGatewayProxyEventV2,
): Promise<APIGatewayProxyResultV2> => {
  const rawBody = event.body ?? "";
  const signature = event.headers["x-signature"] ?? "";

  if (!(await verifyHmacSignature(rawBody, signature, process.env.WEBHOOK_SECRET!))) {
    return { statusCode: 401, body: "invalid signature" };
  }

  const payload = JSON.parse(rawBody);
  const accessToken = await getFcmAccessToken();

  await fetch(
    `https://fcm.googleapis.com/v1/projects/${process.env.FCM_PROJECT_ID}/messages:send`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ message: buildFcmMessage(payload) }),
    },
  );

  return { statusCode: 200, body: JSON.stringify({ ok: true }) };
};

La experiencia de desarrollo a nivel de código es prácticamente idéntica. La diferencia que aborda este artículo es enteramente una cuestión de quién mide, y cómo, el tiempo empleado en procesar cada petición, y cómo se traduce eso en la factura.

Comparativa de niveles gratuitos: dos ofertas aparentemente generosas, con trampa

Concepto Cloudflare Workers (Free) AWS Lambda (Always Free)
Límite de peticiones 100.000/día (~3 millones/mes) 1 millón/mes
Límite de cómputo 10 ms de CPU por invocación 400.000 GB-segundos/mes
Facturación del tiempo de espera No aplica (no forma parte del cómputo) Sí (incluida en el wall-clock)
Vigencia Gratuito permanente Gratuito permanente (independiente del crédito de 12 meses para cuentas nuevas)
Al superar el límite Hay que pasar al plan de pago (desde 5 $/mes) Se factura por consumo a partir del excedente

Si solo se miran los números, el “1 millón de peticiones + 400.000 GB-segundos gratis para siempre” de Lambda parece mucho más generoso, pero la verdadera trampa del plan Free de Workers no está en el número de peticiones, sino en el límite de 10 ms de CPU por invocación. Un parseo y respuesta JSON simple entra sin problema en esos 10 ms, pero en cuanto se añade verificación de firma HMAC, validación de esquema con algo como Zod, o decodificación de JWT, el código real supera esos 10 ms con más facilidad de lo que parece. Al superar este límite, la propia petición se corta con un error de exceso de CPU, así que cualquier API o manejador de webhooks con algo de lógica real prácticamente obliga a pasar al plan de pago de 5 $/mes. Lambda, en cambio, puede operar de verdad a 0 $ si el tráfico cabe dentro del nivel gratuito, incluso reservando memoria y tiempo con holgura.

La trampa de API Gateway: el enrutado de Lambda trae una factura escondida

La propia función Lambda no entiende HTTP de forma nativa. Para invocarla por HTTPS desde un navegador o una app Flutter hay que poner algo delante, y esa elección afecta al coste total más de lo que parece.

El manejador de webhooks o el servidor de envío de push de una app Flutter suele ser una función de propósito único a la que le basta con uno o pocos endpoints. En ese caso, las funciones de enrutado y gestión de uso de API Gateway no hacen falta desde el principio, así que Function URL es suficiente. En cambio, si necesitas agrupar varias funciones Lambda bajo una misma API por rutas, emitir API keys a socios externos y gestionar cuotas —un servidor de API genérico—, entonces sí necesitas API Gateway.

Cloudflare Workers ni siquiera tiene esta distinción. Da igual cuántas rutas manejes, ya sea mediante la configuración de routes en wrangler.toml o con un router dentro del propio código del Worker (Hono, itty-router, etc.): el coste por petición es el mismo.

# wrangler.toml — Workers는 라우팅에 별도 과금 항목이 없다
name = "push-webhook-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"

routes = [
  { pattern = "api.example.com/webhooks/*", zone_name = "example.com" },
  { pattern = "api.example.com/push/*", zone_name = "example.com" }
]

Simulación de la factura por tramo de tráfico: 1 millón, 10 millones y 100 millones de peticiones al mes

Los cálculos siguientes parten de estos supuestos. Los valores reales varían según cada servicio, así que se recomienda sustituirlos por los tuyos propios en las fórmulas que aparecen más adelante.

Escenario A: ligado a E/S (manejador de webhooks, servidor de envío de push)

Peticiones/mes Workers (Paid, desde 5 $) Solo cómputo Lambda Lambda + Function URL Lambda + API Gateway (HTTP API)
1 millón 5,00 $ 0,00 $ 0,00 $ 1,00 $
10 millones 8,40 $ 7,63 $ 7,63 $ 17,63 $
100 millones 71,40 $ 138,13 $ 138,13 $ 238,13 $

Escenario B: ligado a CPU (redimensionado de imágenes, firma, cifrado, etc.)

Peticiones/mes Workers (Paid, desde 5 $) Solo cómputo Lambda Lambda + Function URL Lambda + API Gateway (HTTP API)
1 millón 8,00 $ 0,00 $ 0,00 $ 1,00 $
10 millones 40,40 $ 3,47 $ 3,47 $ 13,47 $
100 millones 391,40 $ 96,47 $ 96,47 $ 196,47 $

Al poner las dos tablas una junto a otra aparece un punto interesante. En el escenario A (ligado a E/S), Lambda sale muchísimo más barato en el tramo de bajo tráfico, pero a medida que crece el tráfico Workers acaba imponiéndose. En cambio, en el escenario B (ligado a CPU), con el supuesto de 256 MB, Lambda es más barato en todo el rango. La siguiente sección explica por qué, y señala el punto de equilibrio exacto.

Por qué el ganador cambia según la naturaleza de la carga de trabajo

Se muestran a continuación las fórmulas usadas en los cálculos, tal cual, para que puedas sustituir tus propias cifras y recalcular por tu cuenta.

Coste mensual de Workers (USD) = 5
  + max(0, peticiones_millones - 10) × 0,30
  + max(0, CPU_ms_por_peticion × peticiones_millones - 30) × 0,02

Coste de cómputo de Lambda (USD) = max(0, peticiones_millones - 1) × 0,20
  + max(0, GBs_totales - 400.000) × 0,0000166667

GBs_totales = peticiones × memoria(GB) × wall_clock_promedio_segundos

Al sustituir en esta fórmula las cifras del escenario A y resolver el punto donde se cruzan ambas curvas, se obtiene que, comparando solo el coste de cómputo, Workers empieza a superar a Lambda en torno a los 11 millones de peticiones al mes. Si a eso se le suma API Gateway (HTTP API, 1,00 $ por millón de peticiones), la pendiente de la curva de coste de Lambda se vuelve más pronunciada y el punto de equilibrio se adelanta a aproximadamente 5,5 millones de peticiones al mes, casi la mitad. Dicho de otro modo: la decisión de “usar API Gateway o usar Function URL” afecta al coste total casi tanto como la diferencia estructural de fondo entre facturar por CPU o por wall-clock.

La razón por la que Lambda sigue ganando en el escenario B (ligado a CPU) es sencilla. Con una configuración de memoria baja como 256 MB (0,25 GB), la tarifa por GB-segundo es tan pequeña (0,2 s × 0,25 GB × 0,0000166667 $ ≈ 0,00000083 $ por petición) que termina saliendo más barata que la tarifa de Workers por ms de CPU (180 ms × 0,02 $/millón de ms ≈ 0,0000036 $ por petición). Pero este resultado depende fuertemente del supuesto de 256 MB de memoria. Si se invierte la misma fórmula, en cargas que necesitan subir la memoria a aproximadamente 1,1-1,4 GB o más (algo habitual en el redimensionado de imágenes con librerías como sharp, donde suele usarse entre 512 MB y 1.769 MB para conseguir suficiente potencia de CPU), esta ventaja se diluye o desaparece, y a medida que crece el tráfico la balanza vuelve a inclinarse hacia Workers. Por eso, antes de asumir que “si es una carga ligada a CPU, Lambda gana seguro”, conviene sustituir en la fórmula la configuración de memoria real que vayas a desplegar.

Costes adicionales fáciles de pasar por alto: el límite de subpeticiones y la tarifa de transferencia de datos

Un servidor de envío de push suele llamar varias veces a APIs externas como FCM o APNs dentro de una misma petición. Cloudflare no factura esto por separado como “subpeticiones” (solo cuenta como petición la petición entrante única), pero sí existe un límite al número de subpeticiones que pueden salir de forma concurrente. Según la documentación oficial, el plan Free permite 50 por petición, y el plan Paid 1.000 por petición por defecto (tras el cambio de febrero de 2026, el valor por defecto subió hasta un máximo de 10.000, ampliable por configuración hasta 10 millones). Si tu arquitectura hace fan-out enviando peticiones individuales a cientos de tokens de dispositivo, conviene revisar este límite de antemano.

Otra partida que se olvida con frecuencia en la práctica es el coste de transferencia de datos (egress). AWS cobra 0,09 $ por GB, aparte, por el tráfico de respuesta que sale de la región (una tarifa general de transferencia de datos de AWS que aplica tanto a API Gateway como a Lambda). En el esquema de precios de Cloudflare Workers no existe esta partida de egress de respuesta como concepto separado. En casos de uso como webhooks o push, donde el payload de respuesta no suele ser grande, la diferencia es mínima; pero en un servidor de API que devuelve JSON voluminoso, esta partida también se va acumulando a medida que crece el tráfico.

Conclusión: qué usar para el servidor de push y el manejador de webhooks de una app Flutter

En definitiva, no hay una respuesta única a “Cloudflare Workers vs AWS Lambda, comparativa de costes”. El volumen de tráfico, la proporción entre tiempo de espera y tiempo de CPU por petición, y la elección de la capa de enrutado —estas tres variables, sustituidas directamente en las fórmulas de este artículo, son la única respuesta realmente precisa.