effidevFlutter · Cloudflare-Edge · Cloud-Kostenoptimierung

Cloudflare Workers vs. AWS Lambda Kostenvergleich: Flutter-Backend, real durchgerechnet von 1 Mio. bis 100 Mio. Requests pro Monat

Cloudflare Workers vs. AWS Lambda Kostenvergleich: Flutter-Backend, real durchgerechnet von 1 Mio. bis 100 Mio. Requests pro Monat

Wer beim Backend für eine Flutter-App auf Serverless setzt und dabei nach “Cloudflare Workers vs. AWS Lambda Kostenvergleich” gesucht hat, kennt vermutlich das Muster: Die meisten Artikel enden mit vagen Pauschalaussagen wie “Workers ist günstiger” oder “Lambda hat das größere Ökosystem”. In Wirklichkeit hängt der Sieger stark davon ab, welche Art von Workload man hat und wie hoch das Traffic-Volumen ist. Dieser Beitrag rechnet anhand von drei typischen Use-Cases aus dem Flutter-Umfeld – einem Push-Versand-Server (FCM/APNs-Aufrufe), einem Webhook-Handler (Verifizierung von RevenueCat, Stripe, App Store Server Notifications) und einem allgemeinen API-Server – die tatsächliche Rechnung für 1 Mio., 10 Mio. und 100 Mio. Requests pro Monat durch, inklusive der unterschiedlichen Abrechnungsmodelle beider Plattformen und der zusätzlichen Kosten durch API Gateway.

Kernaussagen

  • Cloudflare Workers berechnet ausschließlich CPU-Zeit – die Wartezeit auf Antworten externer APIs fließt nicht in die Rechnung ein. AWS Lambda dagegen berechnet die gesamte Ausführungszeit inklusive Wartezeit (Wall-Clock), multipliziert mit der zugewiesenen Speicherkapazität.
  • Bei Lambda sind Requests und Ausführungszeit bis 1 Mio. Aufrufe + 400.000 GB-Sekunden pro Monat dauerhaft kostenlos (Always Free). Der kostenlose Workers-Plan ist dagegen nur bis 100.000 Requests pro Tag (ca. 3 Mio. pro Monat) und 10 ms CPU-Zeit pro Invocation kostenlos – wird dieses CPU-Limit überschritten, ist ein Wechsel auf den Paid-Plan ab 5 $/Monat unumgänglich.
  • Wird Lambda per HTTP freigegeben, kostet API Gateway zusätzlich 1,00 $ pro 1 Mio. Requests bei HTTP-API bzw. 3,50 $ bei REST-API. Bei einem einzelnen Endpunkt lässt sich dieser Posten komplett vermeiden, indem man eine Lambda Function URL verwendet. Bei Workers gibt es dieses Problem gar nicht erst, da Routing fest im Runtime-Layer eingebaut ist.
  • Unter den Annahmen dieses Artikels (300 ms Wall-Clock pro Request, tatsächlich 20 ms CPU, 256 MB Speicher) wird Workers bei Verwendung von API Gateway ab ca. 5,5 Mio. Requests/Monat, bei Verwendung einer Function URL erst ab ca. 11 Mio. Requests/Monat günstiger. Unterhalb dieser Schwellen liegt Lambda vorn.
  • Umgekehrt war bei CPU-intensiven Funktionen wie Bildgrößenänderungen Lambda bei niedriger Speicherkonfiguration (256 MB) über den gesamten betrachteten Bereich günstiger. Bei rechenintensiveren Aufgaben, die 1,1–1,4 GB Speicher oder mehr benötigen, um genug CPU-Leistung zu bekommen, schrumpft dieser Vorteil allerdings deutlich.

Der grundlegende Unterschied bei der Abrechnung: CPU-Zeit vs. Gesamtausführungszeit

Beim Vergleich beider Plattformen muss man zuerst verstehen, dass sich “was überhaupt als Zeit gemessen und in Rechnung gestellt wird” fundamental unterscheidet.

Laut offizieller Preisdokumentation von Cloudflare werden bei Workers nur die Anzahl der Requests und die CPU-Zeit abgerechnet. Während ein Worker per fetch eine externe API aufruft und auf die Antwort wartet, führt die V8-Isolate in dieser Zeit keine Instruktionen aus, sondern befindet sich im Wartezustand – dieser Zeitraum taucht auf der Rechnung überhaupt nicht auf. Selbst wenn eine Kette aus fünf externen API-Aufrufen insgesamt zwei Sekunden bis zur vollständigen Antwort braucht: Wenn der eigentliche JS-Code darin (Parsing, Serialisierung, Verzweigungen etc.) nur 20 ms Laufzeit hat, werden auch nur diese 20 ms berechnet. Das Standard-CPU-Limit pro Invocation liegt bei 30 Sekunden, im Paid-Plan lässt es sich per Konfiguration auf bis zu 5 Minuten (300.000 ms) erhöhen, und für Cron Trigger sowie Queue-Consumer sind bis zu 15 Minuten erlaubt.

AWS Lambda misst umgekehrt “die gesamte Zeit vom Start des Handler-Codes bis zur Rückgabe der Antwort” als Wall-Clock-Zeit und multipliziert diese mit der dem Funktionsspeicher zugewiesenen Kapazität (in GB), um daraus GB-Sekunden zu berechnen. Auch während des Wartens auf eine externe API-Antwort gilt die Ausführungsumgebung durchgehend als “in Ausführung”, weshalb diese Wartezeit ebenfalls voll in Rechnung gestellt wird. Bei ein und demselben Webhook-Handler macht es für die Lambda-Rechnung also einen erheblichen Unterschied, ob eine externe API in 250 ms oder in 800 ms antwortet – dieser Unterschied schlägt sich unmittelbar in den GB-Sekunden-Kosten nieder. Die Ausführungszeit von Lambda wird auf 1 ms genau aufgerundet, das maximale Funktions-Timeout liegt bei 900 Sekunden (15 Minuten), und der Speicher lässt sich zwischen 128 MB und 10.240 MB in 1-MB-Schritten konfigurieren. Bekanntlich entspricht die Speichermarke von 1.769 MB in etwa der Rechenleistung eines vollen vCPU-Kerns – CPU-intensive Funktionen werden daher häufig mit einer Speicherkonfiguration in dieser Größenordnung oder darüber deployt.

Aus diesem strukturellen Unterschied ergibt sich eine einfache praktische Schlussfolgerung: Je länger ein Workload auf externe APIs oder Datenbankantworten wartet (I/O-lastig), desto vorteilhafter wird Workers. Je mehr reine Rechenzeit ein Workload benötigt (CPU-lastig), desto kleiner wird der Abstand zwischen beiden Plattformen – oder er kehrt sich sogar um. Push-Versand-Server (FCM-HTTP-v1-API-Aufrufe) und Webhook-Handler (Signaturverifizierung gefolgt von externen API-Aufrufen), wie sie in Flutter-Backends üblich sind, sind typische I/O-lastige Workloads.

Gleiche Logik, unterschiedliches Code-Gerüst

In der Praxis sieht der auf beide Plattformen deployte Code fast identisch aus. Nachfolgend die Worker-Version eines Handlers, der mit Hono geschrieben wurde, einen RevenueCat-/Stripe-artigen Webhook entgegennimmt, dessen Signatur verifiziert und anschließend per FCM eine Push-Benachrichtigung verschickt.

// 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;

Beim Umzug auf Lambda + Function URL ändert sich nur der Trigger-Mechanismus, die Logik bleibt unverändert.

// AWS Lambda (Node.js, Function-URL-Trigger)
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 }) };
};

Auf Code-Ebene ist die Entwicklungserfahrung praktisch identisch. Der Unterschied, um den es in diesem Artikel geht, liegt ausschließlich darin, wer wie misst, wie lange die Bearbeitung eines einzelnen Requests gedauert hat, und danach abrechnet.

Vergleich der Free-Tiers: Großzügig auf den ersten Blick, aber mit Fallstricken bei beiden Anbietern

Kriterium Cloudflare Workers (Free) AWS Lambda (Always Free)
Request-Limit 100.000/Tag (ca. 3 Mio./Monat) 1 Mio./Monat
Compute-Limit 10 ms CPU pro Invocation 400.000 GB-Sekunden/Monat
Abrechnung der Wartezeit Keine (ohnehin nicht Teil der Abrechnung) Ja (in Wall-Clock enthalten)
Gültigkeit Dauerhaft kostenlos Dauerhaft kostenlos (unabhängig vom separaten 12-Monats-Guthaben für neue Konten)
Bei Überschreitung Wechsel auf Paid-Plan (ab 5 $/Monat) nötig Überschreitender Anteil wird nutzungsbasiert berechnet

Auf den ersten Blick wirkt Lambdas “1 Mio. Requests + 400.000 GB-Sekunden dauerhaft kostenlos pro Monat” deutlich großzügiger. Die eigentliche Falle im Workers-Free-Plan liegt aber nicht bei der Request-Zahl, sondern beim Limit von 10 ms CPU pro Invocation. Reines JSON-Parsing samt Antwort schafft man locker innerhalb von 10 ms, doch sobald HMAC-Signaturverifizierung, Schema-Validierung mit etwas wie Zod oder JWT-Decoding hinzukommen, überschreitet praxisnaher Code dieses Limit erstaunlich leicht. Wird das Limit überschritten, bricht der jeweilige Request mit einem CPU-Überschreitungsfehler ab – bei einem API- oder Webhook-Handler mit echter Geschäftslogik ist der Paid-Plan für 5 $/Monat faktisch also Pflicht. Bei Lambda dagegen lässt sich auch mit großzügig bemessenem Speicher und Zeitbudget echter Null-Kosten-Betrieb erreichen, solange der Traffic innerhalb des Free-Tiers bleibt.

Die API-Gateway-Falle: Lambda-Routing bringt eine versteckte Rechnung mit

Eine Lambda-Funktion versteht HTTP nicht von sich aus. Um sie per HTTPS aus dem Browser oder einer Flutter-App aufrufbar zu machen, muss davor etwas vorgeschaltet werden – und diese Entscheidung wirkt sich stärker auf die Gesamtkosten aus, als man zunächst vermuten würde.

Webhook-Handler oder Push-Versand-Server in Flutter-Apps sind meist Single-Purpose-Funktionen, für die ein einzelner (oder wenige) Endpunkt(e) ausreicht/ausreichen. In so einem Fall werden die Routing- und Nutzungsverwaltungsfunktionen von API Gateway ohnehin nicht benötigt, weshalb eine Function URL völlig ausreicht. Wer dagegen mehrere Lambda-Funktionen unter einer gemeinsamen API mit pfadbasiertem Routing bündeln und externen Partnern API-Keys mit Quoten ausstellen muss – etwa bei einem allgemeinen API-Server –, kommt um API Gateway nicht herum.

Bei Cloudflare Workers gibt es diese Unterscheidung gar nicht erst. Egal ob über die Routen-Konfiguration in wrangler.toml oder einen Router im Worker-Code selbst (Hono, itty-router usw.) beliebig viele Pfade verarbeitet werden – die Kosten pro Request bleiben identisch.

# wrangler.toml — Bei Workers gibt es keinen eigenen Kostenpunkt fürs Routing
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" }
]

Simulation der Rechnung nach Traffic-Stufen: 1 Mio./10 Mio./100 Mio. Requests pro Monat

Die folgenden Berechnungen basieren auf diesen Annahmen. Da die tatsächlichen Werte von Service zu Service variieren, empfiehlt es sich, die eigenen Zahlen direkt in die weiter unten aufgeführte Formel einzusetzen.

Szenario A: I/O-lastig (Webhook-Handler, Push-Versand-Server)

Requests/Monat Workers (Paid, ab 5 $) Nur Lambda-Compute Lambda + Function URL Lambda + API Gateway (HTTP API)
1 Mio. 5,00 $ 0,00 $ 0,00 $ 1,00 $
10 Mio. 8,40 $ 7,63 $ 7,63 $ 17,63 $
100 Mio. 71,40 $ 138,13 $ 138,13 $ 238,13 $

Szenario B: CPU-lastig (Bildgrößenänderung, Signatur/Verschlüsselung etc.)

Requests/Monat Workers (Paid, ab 5 $) Nur Lambda-Compute Lambda + Function URL Lambda + API Gateway (HTTP API)
1 Mio. 8,00 $ 0,00 $ 0,00 $ 1,00 $
10 Mio. 40,40 $ 3,47 $ 3,47 $ 13,47 $
100 Mio. 391,40 $ 96,47 $ 96,47 $ 196,47 $

Legt man beide Tabellen nebeneinander, fällt ein interessantes Muster auf: In Szenario A (I/O-lastig) ist Lambda bei niedrigem Traffic-Volumen deutlich günstiger, mit steigendem Traffic dreht sich das Bild aber zugunsten von Workers. In Szenario B (CPU-lastig) hingegen bleibt Lambda unter der Annahme von 256 MB Speicher über den gesamten betrachteten Bereich günstiger. Der nächste Abschnitt erklärt, woran das liegt, und zeigt den genauen Break-even-Punkt.

Warum der Sieger je nach Workload-Charakter wechselt

Hier die Formel, mit der oben gerechnet wurde, im Klartext – sie lässt sich mit den eigenen Workload-Zahlen direkt nachrechnen.

Workers-Kosten/Monat (USD) = 5
  + max(0, Requests_in_Mio - 10) × 0,30
  + max(0, CPU_ms_pro_Request × Requests_in_Mio - 30) × 0,02

Lambda-Compute-Kosten (USD) = max(0, Requests_in_Mio - 1) × 0,20
  + max(0, GB-Sekunden_gesamt - 400.000) × 0,0000166667

GB-Sekunden_gesamt = Requests × Speicher(GB) × durchschnittliche_Wall-Clock-Zeit(s)

Setzt man die Zahlen aus Szenario A in diese Formel ein und löst nach dem Schnittpunkt beider Kurven auf, ergibt sich: Vergleicht man ausschließlich die Compute-Kosten, zieht Workers ab etwa 11 Mio. Requests/Monat an Lambda vorbei. Kommt zusätzlich API Gateway (HTTP API, 1,00 $ pro 1 Mio. Requests) ins Spiel, wird die Kostenkurve von Lambda steiler, und der Break-even-Punkt verschiebt sich auf etwa 5,5 Mio. Requests/Monat – fast eine Halbierung. Anders gesagt: Die Entscheidung “API Gateway oder Function URL” beeinflusst die Gesamtkosten fast genauso stark wie der grundlegende Unterschied zwischen CPU-Zeit-Abrechnung und Wall-Clock-Abrechnung.

Dass Lambda in Szenario B (CPU-lastig) durchgehend vorn liegt, hat einen einfachen Grund: Bei der niedrigen Speicherkonfiguration von 256 MB (0,25 GB) ist der GB-Sekunden-Preis so gering (0,2 s × 0,25 GB × 0,0000166667 $ ≈ 0,00000083 $ pro Request), dass er unter dem CPU-ms-Preis von Workers liegt (180 ms × 0,02 $/1 Mio. ms ≈ 0,0000036 $ pro Request). Dieses Ergebnis hängt allerdings stark von der Annahme eines 256-MB-Speichers ab. Rechnet man mit derselben Formel rückwärts, zeigt sich: Bei Workloads, die etwa 1,1–1,4 GB Speicher oder mehr benötigen (in der Praxis wird beim Bildgrößenändern mit Bibliotheken wie sharp häufig zwischen 512 MB und 1.769 MB konfiguriert, um genug CPU-Leistung zu bekommen), schwächt sich dieser Vorteil ab oder verschwindet ganz, und mit steigendem Traffic verschiebt sich das Gewicht wieder zugunsten von Workers. Das ist der Grund, warum man nicht pauschal “CPU-lastig, also automatisch Lambda” sagen sollte, sondern die tatsächlich geplante Speicherkonfiguration in die Rechnung einsetzen muss.

Leicht übersehene Nebenkosten: Subrequest-Limits und Datenübertragungsgebühren

Push-Versand-Server rufen pro eingehendem Request häufig mehrfach externe APIs wie FCM oder APNs auf. Cloudflare berechnet solche “Subrequests” nicht separat (abgerechnet wird nur der eine eingehende Request), begrenzt aber die Anzahl gleichzeitig ausgehender Subrequests. Laut offizieller Dokumentation gilt: Im Free-Plan 50 pro Request, im Paid-Plan standardmäßig 1.000 pro Request (seit einer Änderung im Februar 2026 wurde der Standardwert auf bis zu 10.000 angehoben, per Konfiguration lässt er sich auf bis zu 10 Millionen erweitern). Bei einer Fan-out-Architektur, die einzelne Requests an Hunderte Geräte-Tokens verschickt, sollte dieses Limit vorab geprüft werden.

Ein weiterer Punkt, der in der Praxis oft vergessen wird, sind die Kosten für ausgehende Datenübertragung (Egress). AWS berechnet für Antwort-Traffic, der die Region verlässt, zusätzlich 0,09 $ pro GB (die allgemeine AWS-Datenübertragungsgebühr, die sowohl für API Gateway als auch für Lambda gilt). Im Preismodell von Cloudflare Workers gibt es einen solchen separaten Egress-Kostenpunkt für Antworten überhaupt nicht. Bei Webhook- und Push-Use-Cases mit kleinen Antwort-Payloads fällt dieser Unterschied kaum ins Gewicht – bei einem API-Server, der große JSON-Antworten zurückgibt, summiert sich dieser Posten mit wachsendem Traffic jedoch zunehmend.

Fazit: Was eignet sich für Push-Versand-Server und Webhook-Handler in Flutter-Apps?

Am Ende gibt es auf die Frage “Cloudflare Workers vs. AWS Lambda Kostenvergleich” keine pauschale Antwort. Traffic-Volumen, das Verhältnis von Wartezeit zu CPU-Zeit pro Request und die Wahl der Routing-Ebene – diese drei Variablen direkt in die Formeln dieses Artikels einzusetzen, ist der einzig verlässliche Weg zur richtigen Antwort für den eigenen Fall.