Das 10-ms-CPU-Limit des kostenlosen Workers-Plans macht Erstlesern Angst. Zehn Millisekunden scheinen für irgendetwas Nützliches lächerlich kurz zu sein. Die Folge ist, dass viele Teams sofort zu dem Plan wechseln, für den die 30 Sekunden CPU bezahlt werden – ohne zu verstehen, dass sich das Messmodell grundlegend von einem herkömmlichen Server unterscheidet und dass die meisten Worker auch in den kostenlosen 10 ms noch CPU übrig haben. Das Speicherlimit von 128 MB hingegen wird systematisch unterschätzt und ist für eine ganze Kategorie stiller Ausfälle in der Produktion verantwortlich.
Wie der CPU-Timer tatsächlich funktioniert
Die Workers-Laufzeit misst die „CPU-Zeit“ – die Zeit, die der JavaScript-Thread aktiv Code ausführt. Der Timer stoppt während eines asynchronen E/A-Vorgangs: await fetch(), await env.KV.get(), await db.query(), await env.BUCKET.get(). Während dieser Wartezeiten ist Isolate im Leerlauf und der Timer läuft nicht weiter.
Der praktische Effekt ist großartig. Ein Worker, der fünf fetch() sequentielle Aufrufe an externe APIs durchführt, die jeweils 100 ms Netzwerklatenz benötigen, hat eine Gesamtzeit von 500 ms, verbraucht aber vielleicht 6 ms CPU – nur die Serialisierung der Header, das Parsen des Antwort-JSON und die Geschäftslogik zwischen den Aufrufen. Für die meisten Worker, die im Wesentlichen E/A-Orchestratoren sind, ist die 10-ms-Grenze großzügig.
Was die CPU wirklich beansprucht, sind synchron intensive Vorgänge: Regex, der auf große Zeichenfolgen, Objekte mit tiefer Hierarchie oder großen Arrays angewendet wird, kryptografische Vorgänge, selbst wenn die API asynchron ist (die Hashing-Arbeit findet während der Ausführung auf der CPU statt) und Base64-Kodierung/Dekodierung bei großen Binärdateien. Ein Worker, der eine 500-KB-JSON-Nutzlast empfängt und JSON.parse() damit ausführt, verbraucht messbare CPU-Zeit für diesen Vorgang – die Analyse erfolgt synchron.
Beim kostenpflichtigen Plan beträgt die Grenze bis zu 30 Sekunden CPU-Zeit, was für rechenintensive Anwendungsfälle ausreicht: Komprimierung, Bildgenerierung, kleine Modellinferenz. Aber selbst bei kostenpflichtigen Systemen sind CPU-Vorgänge, die länger als ein paar Sekunden dauern, ein Zeichen für ein schlechtes Design des Edge – Worker wurden für niedrige Latenzzeiten und nicht für schwere Verarbeitung optimiert.
Der CPU-Timer ist nicht das, was Arbeiter in der Produktion tötet
Was Arbeiter in der Produktion ohne klare Vorwarnung wirklich niederschlägt, ist das Gedächtnis. Die 128-MB-Grenze scheint angemessen, bis Sie verstehen, worauf es ankommt: das unkomprimierte Skript auf dem V8-Heap, alle Modulabschlüsse, die seit Beginn des Aufrufs im globalen Bereich vorhanden sind, sowie alles, was während der laufenden Anforderungsverarbeitung zugewiesen wird.
Das Skript selbst kann aufwändiger sein, als Sie denken. Ein komprimierter 500-KB-Worker kann 3–4 MB belegen, nachdem er von V8 dekomprimiert und analysiert wurde. Auf Modulebene importierte Abhängigkeiten – Validierungsbibliotheken, Parser, SDKs – bleiben so lange im Speicher, wie das Isolat lebt, auch wenn sie in der aktuellen Anfrage nicht verwendet werden. Der globale Bereich wird von Anfragen gemeinsam genutzt, die von denselben isolierten Prozessen nacheinander im selben PoP verarbeitet werden.
Die Zuteilung, die am häufigsten das Limit auslöst, ist response.arrayBuffer() oder request.arrayBuffer(). Wenn Sie diese Methode für eine 40-MB-Antwort aufrufen, werden sofort 40 MB Heap zugewiesen. Wenn der Worker dann weitere Datenstrukturen aus diesem Puffer erstellt – analysierte Objekte, transformierte Kopien – kann die Speichernutzung 128 MB überschreiten, bevor die Verarbeitung endet. Die Laufzeit-Kills werden zu diesem Zeitpunkt isoliert, der Client erhält einen Fehler 1101 und es gibt keinen Stack-Trace – nur einen Laufzeitfehler, der als generische Ausnahme im Wrangler-Tail gemeldet wird.
Streaming als Erinnerungsüberlebensstrategie
Die Lösung des Speicherproblems bei großen Nutzlasten besteht darin, niemals den gesamten Inhalt als Puffer zu materialisieren. Verwenden Sie statt response.arrayBuffer() response.body – also ReadableStream – und verarbeiten Sie die Daten in Blöcken mit TransformStream.
Die Workers-Streams-API folgt der WHATWG-Streams-Spezifikation, der gleichen, die auch in modernen Browsern verfügbar ist. Ein TransformStream hat einen writable und einen readable: Sie verbinden den readable der Upstream-Antwort mit dem writable der Transformation und den readable der Transformation mit der Antwort, die Sie an den Client senden. Chunks fließen durch die Pipeline, ohne jemals gleichzeitig ganze Ganzzahlen im Speicher zu haben.
Diese Architektur hat eine wichtige Konsequenz: Sie können nicht mehr den gesamten Inhalt lesen, um Entscheidungen zu treffen, die von der gesamten Datei abhängen, bevor Sie mit der Reaktion beginnen. Für Fälle, in denen Sie Direktzugriff benötigen – beispielsweise die Verarbeitung einer CSV-Datei, die eine globale Reihenfolge erfordert – ist Workers nicht der richtige Ort. Bei Transformationen, die Stück für Stück ablaufen – Neukomprimierung, Textersetzung, Zeilenfilterung – löst die Stream-Pipeline das Problem ohne Speicherdruck.
Für Uploads nach R2 akzeptiert die Bindung direkt einen ReadableStream:
await env.BUCKET.put(key, request.body, { httpMetadata }) – ohne etwas zu puffern. Der Request-Body-Stream geht in Blöcken direkt an R2.
Vorgänge, die aufgrund ihrer CPU-Auslastung überraschend sind
Base64-Kodierung und -Dekodierung sind CPU-intensive Vorgänge proportional zur Datengröße – und das Ergebnis beansprucht 33 % mehr Speicher. Wenn Sie Binärdateien transportieren, lohnt es sich zu fragen, ob eine Kodierung erforderlich ist. Bei vielen Verwendungen von Base64 handelt es sich um veraltete HTTP-Einschränkungen, die für Fetch- und native Binärtypen nicht mehr gelten.
Hashing-Vorgänge über die Web Crypto API sind asynchron in der Signatur – await crypto.subtle.digest('SHA-256', data) – aber Hashing verbraucht CPU proportional zur Datengröße. Ein Worker, der HMAC-SHA256 bei jeder Anfrage zur Überprüfung eines Webhooks ausführt, verbraucht echte CPU dafür, was für kleine Nutzlasten irrelevant, aber über ein paar hundert KB von Bedeutung ist.
Regex für lange Strings kann überraschend teuer sein. Muster mit katastrophalem Backtracking – mehrere über denselben Zeichensatz verschachtelte Quantoren – können die CPU-Zeit bei Zeichenfolgen von einigen Kilobyte verdreifachen. Messen Sie mit realistischen Zeichenfolgen, nicht mit 10-Byte-Eingaben, die im Test funktionieren.
Worauf Sie achten müssen, um Ressourcendruck zu erkennen
wrangler tail gibt bei jedem Ereignis cpuTime zurück – die in Millisekunden gemessene CPU-Zeit für diesen Aufruf. Durch die strukturierte Protokollierung dieses Werts können wir Regressionen erkennen: Eine Bereitstellung, die CPU p99 von 3 ms auf 12 ms erhöht, bedeutet, dass ein neuer synchroner Betrieb eingeführt wurde.
Die Speichernutzung wird durch den Aufruf in tail nicht direkt offengelegt. Die Möglichkeit, den Speicherdruck zu erkennen, bevor das Limit überschritten wird, besteht darin, das Verhalten des Isolats zu beobachten: Wenn die Laufzeit häufiger als normal für denselben PoP mit der Erstellung neuer Isolate beginnt – sichtbar an einer Zunahme von Kaltstarts – könnte dies ein Zeichen dafür sein, dass Isolate früher vom Garbage Collector oder aufgrund von Speicherdruck verworfen werden.
Die Möglichkeit, ein Isolat zwischen Anfragen im selben PoP wiederzuverwenden, ist eine wichtige Optimierung: Die Kosten für den Kaltstart – Dekomprimieren des Skripts, Initialisieren von V8, Ausführen des Modulcodes auf höherer Ebene – fallen einmal an, und nachfolgende Anfragen verwenden das bereits aufgewärmte Isolat wieder. Modulabschlüsse, die zwischen Anfragen im Speicher verbleiben, sind der schnellste in Workers verfügbare Caching-Mechanismus – schneller als KV, schneller als jede Unteranfrage.
Lesen Sie auch
- Cloudflare Workers in der Produktion: Was sich nach Hello World ändert
- D1 in der Produktion: Leistung, Grenzen und was sich nicht alleine skalieren lässt
- Cloudflare Workers: Praktischer Leitfaden für serverloses Edge Computing
- Langlebige Objekte in der Produktion: Wie die Rechnung aussehen wird und welche Grenzen überraschen
- [WebAssembly am Rande: Warum ein schneller und isolierter Start wichtig ist28
- Arbeiter + D1 + KV + R2: Zusammenstellen von Bindungen im selben Dienst
