Skip to content
ZERONE
Zurück zu Insights
Verteilte Systeme2026-07-06 · 8 Min Lesezeit

Vier Schichten Kostenschutz: Wie unser Auto-Scaler nicht in die Rechnung explodiert

Ein Kunde uploaded eine kaputte Szene, der Orchestrator startet einen Pod, der Pod läuft ins Endlos-OOM, versucht drei Retries, jeder Pod kostet uns 0,80 US-Dollar pro Minute. Ohne Schutz macht das in einer Stunde eine dreistellige Rechnung. Vier unabhängige Schichten müssen versagen, damit wir das erleben.

Bei einem Cloud-Rendering-Setup ist der teuerste Fehler nicht der langsame Render — es ist der Render, der endlos läuft. Ein GPU-Pod bei US$0.80/Minute wird bei 60 Minuten zu $48, bei 12 Stunden zu $576, bei einem Wochenende zu $2.300. Wenn die Software crashen kann und der Orchestrator „einfach neu starten" macht, ist das nicht Auto-Recovery — das ist eine Rechnung, die den Kunden nicht mehr wiederkommen lässt.

Unser cinema-orch Backend hat vier unabhängige Schichten von Cost-Protection. Nach Sprint 3.5 (2026-06-15) sind alle vier live. Jede Schicht macht etwas anderes falsch als die andere — damit müssen mindestens zwei versagen bevor wir überzogene Rechnungen sehen.

Schicht 1: Ex-ante Estimate + Client-Confirm

Bevor irgendein Pod spawnt, berechnet der Orchestrator einen Kosten-Estimate basierend auf:

  • Sample-Count × Tile-Count × Frame-Count × geschätzte Zeit/Frame auf der gewählten GPU
  • Baseline aus historischen Jobs derselben Szene-Signatur (Hash aus Blender-Version + Render-Settings)
  • Puffer von 20 % für Unbekannte

Der Estimate wird dem Kunden vor dem Start angezeigt. Der Kunden muss aktiv „Bestätigen" klicken. Ist der Estimate über dem Kunden-Budget (jeder User setzt ein Hard-Cap in seinem Account), wird der Job automatisch abgewiesen — nicht queued, nicht bestätigt-mit-Warnung, sondern rejected.

Effekt: ~70 % aller theoretisch teuren Jobs kommen nie in den Orchestrator, weil der Kunde vorher sieht dass es zu teuer wird und die Szene optimiert.

Schicht 2: Per-Job Budget-Cap mit Live-Ticker

Jeder Job hat einen max_budget_cents-Feld, gesetzt vom User (typisch der Estimate + 20 %). Der Orchestrator läuft einen Ticker in Postgres — jede Minute wird der aktuell akkumulierte Cost berechnet und gegen max_budget_cents geprüft.

Wenn der Job den Cap erreicht, wird der Pod hart terminated. Der User bekommt eine Notification, kann den Cap manuell anheben oder den partiellen Output nehmen.

Effekt: Ein Runaway-Render kann maximal ~5-10 % über dem Cap landen (bis der nächste Ticker-Tick anschlägt). Bei einem $50-Job sind das $2.50 Overshoot statt $2.300.

Schicht 3: Pod-Timeout auf systemd-Level

Jeder GPU-Pod hat eine harte Zeit-Grenze, die im Pod-Init gesetzt wird — analog zu einem Kubernetes-ActiveDeadline. Bei einer Standard-Render-Session: 4 Stunden. Nach Ablauf: der Pod stirbt, der Orchestrator sieht den Abbruch und markiert den Job als budget_exceeded.

Warum das nötig ist obwohl Schicht 2 existiert: der Orchestrator selbst könnte crashen (Netzwerk-Partitionierung, Postgres-Ausfall, Prozess-OOM). Der Pod-Timeout ist die Fallback-Garantie — er ist auf dem Pod selbst konfiguriert und braucht keinen externen Trigger.

Effekt: Selbst bei einem komplett stummen Orchestrator kostet ein einzelner Pod maximal die 4-Stunden-Marke, danach terminated er sich selbst.

Schicht 4: Provider-seitiges Spending-Alert

Runpod (unser primary GPU-Provider) hat eigene Budget-Alerts pro Account. Bei $50/Tag Spend kommt eine E-Mail an [email protected]; bei $200/Tag ein Slack-Alert an unseren Ops-Channel. Beide sind konservativ eingestellt.

Wenn die drei anderen Schichten alle versagen, holt uns spätestens dieser Alert manuell hinein. Bisher (4 Wochen Beta) hat er nie ausgelöst, weil Schichten 1-3 alles fangen.

Warum vier

Ein einziger Schutz-Layer ist kein Schutz — es ist ein Single Point of Failure. Wenn wir uns auf „der Kunde bestätigt den Estimate" verlassen und die UI hat einen Bug, der den Estimate zu niedrig anzeigt, sind wir dran. Vier unabhängige Schichten heißt: ein Bug in irgendeiner Software (unsere UI, unser Orchestrator, systemd, Runpods Portal) fängt keine unerwartete Rechnung ein — sondern die anderen drei tun's.

Der Trick ist die Unabhängigkeit. Schicht 1 lebt im Frontend (Next.js). Schicht 2 im Orchestrator (Python/asyncpg). Schicht 3 auf dem Pod (systemd). Schicht 4 beim Provider (Runpods API). Wenn eine Software crashen sollte, ist es unwahrscheinlich dass die anderen drei gleichzeitig genauso versagen.

Was wir gelernt haben

  • Estimate-Genauigkeit ist der wichtigste Hebel für Schicht 1. Wenn der Estimate 30 % daneben liegt, ist der Cap-Puffer erschöpft bevor der Job fertig ist. Wir haben viel Zeit in Sample-Count × Zeit/Frame-Kalibrierung investiert.
  • Ticker-Intervall in Schicht 2 ist ein Trade-off zwischen Overshoot und DB-Last. 60 Sekunden ist unser Kompromiss — 30 Sekunden reduziert Overshoot, aber verdoppelt die DB-Load pro laufendem Job.
  • Pod-Timeout in Schicht 3 darf nicht zu kurz sein. Wir hatten in einer frühen Version 60 Minuten — hat einen legitimen 4K-Job zerschossen der 90 Minuten gebraucht hätte. Jetzt 4 Stunden default, konfigurierbar pro Job.

Wenn du selbst ein Auto-Scaling-System mit externem GPU-Rechnung-Risiko baust: fange nicht mit einem einzigen Cap an. Bau drei unabhängige an, plus einen Provider-Alert. Der zusätzliche Aufwand ist klein, das Risiko-Delta ist enorm.

Wir dokumentieren diese Architektur öffentlich weil wir denken dass Cloud-Rendering ehrlich sein muss über die eigenen Risiken. Wenn du willst dass ich dir das Muster für dein eigenes System anschaue: [email protected]ZER0ONE Studio macht diese Art Distributed-Systems-Beratung als Engineering-Service.

Ähnlicher Brand bei dir?

Wir haben vermutlich schon etwas ähnliches gesehen. Sprich mit uns.

Gespräch starten