Skip to content

Instantly share code, notes, and snippets.

@ephes
Last active January 7, 2026 10:05
Show Gist options
  • Select an option

  • Save ephes/48d963c8d40d64a73670b209a52bee60 to your computer and use it in GitHub Desktop.

Select an option

Save ephes/48d963c8d40d64a73670b209a52bee60 to your computer and use it in GitHub Desktop.
Stadion-Gewinnspiel

Stadion-Gewinnspiel / Live-Farbsteuerung (50k Clients) – Optionen, Machbarkeit, offene Punkte

Zielbild / Anforderungen (aus dem Chat abgeleitet)

  • Bis zu 50.000 Smartphones gleichzeitig im Stadion.
  • Admin klickt im Dashboard → sofortige “Live”-Kontrolle (Farbwechsel / Muster / Gewinner grün).
  • Akzeptable End-to-End-Latenz: ≤ 10 Sekunden (1 Minute ist zu viel).
  • Ideal: gleichzeitige Wahrnehmung (“alle werden gelb/rot”), keine hörbaren/visuellen Asynchronitäten.
  • PWA bevorzugt (kein nativer App-Zwang), funktioniert über WLAN und/oder LTE/5G.
  • Gewinnerlogik: “alle Farbe X, aber einige wenige sind grün”.

Zentrale technische Realität (Constraints)

  1. SSE und WebSockets sind beide “lange offene Verbindungen” (eine Connection pro Client).
    → Skalierung: möglich, aber Betrieb & Load-Balancing & Backpressure sind anspruchsvoll.
  2. “Exakt gleichzeitig” ist über Mobilfunk/WLAN nicht garantierbar (Funk, QoS, Energiesparen, Browser Throttling, Paketverlust, Reconnects).
  3. Das eigentliche Problem ist nicht nur “50k Verbindungen halten”, sondern:
    • wie schnell können 50k Clients die neue Instruktion sicher erhalten?
    • wie synchron zeigen sie sie an (selbst wenn sie sie zu unterschiedlichen Zeiten empfangen)?

Best-practice (pragmatisch) – Empfehlung

Empfehlung A: Polling + CDN/Edge-Caching + Zeitstempel (“scheduled execution”)

Kernidee: Alle Clients holen dieselbe, kleine JSON-Anweisung aus einem Cache.
Die Anweisung enthält einen effective_at-Zeitpunkt in der nahen Zukunft (z.B. now + 2s), damit Geräte trotz unterschiedlicher Empfangszeit synchron anzeigen.

Response ist für alle gleich, z.B.:

  • { "version": 4711, "mode": "solid", "color": "yellow", "effective_at": 1736240000123, "winners": [] }
  • { "version": 4712, "mode": "solid", "color": "red", "effective_at": 1736240002123, "winners": [77, 666] }

Client-Loop (PWA):

  • Polling z.B. alle 1s (adaptiv: 250–500ms nur während “Show”, sonst 10–30s).
  • Wenn version neu: lokal “timer” setzen bis effective_at, dann Farbe umschalten.
  • Gewinner-ID lokal prüfen (eigene ID in winners? → grün, sonst Basisfarbe).

Warum das gut ist:

  • Server-seitig minimaler Aufwand pro Request (kein per-user DB read).
  • Caching skaliert brutal gut: 50k Clients können denselben Edge-Cache treffen.
  • 10 Sekunden Latenz ist machbar: bei Polling 1s + Netzwerk + Cache-Hit.
  • Synchronität entsteht über scheduled execution, nicht über “gleichzeitigen Versand”.

Wichtige technische Details:

  • Client braucht eine “brauchbar korrekte” Uhr. Lösung: bei jedem Poll die Serverzeit mitschicken
    und Client-Offset schätzen (server_now - client_now), geglättet.
  • Browser-Timer können im Hintergrund throttlen → die PWA muss “Screen on / im Vordergrund” sein (was bei “Handy hochhalten” meist gegeben ist).

Skalierungsrechnung (grob):

  • 50k Clients bei 1s Polling → 50k req/s (nicht 10k).
    Bei 5s Polling → 10k req/s.
  • ABER: Mit Edge-Caching sind das überwiegend Cache-Hits, Origin sieht viel weniger.
  • “10 Sekunden max” spricht eher für 2–5s Polling + Zeitstempel.
    Viele finden 2–5s “gleichzeitig genug”, wenn es visuell im Stadion passiert.

Empfohlene Ausgestaltung:

  • “Show-Mode”: Polling 1–2s, sonst 10–30s.
  • Responses klein halten (gzip/brotli), winners als kurze Liste oder Bloom/bitset nur falls nötig.
  • Per-Device-ID nur für Gewinner; alles andere ist global.

Cloudflare-Nutzen hierbei:

  • Sehr guter Fit für Caching + Edge + optional Workers/Durable Objects als “state holder”.
  • Der “PR-Dschungel” ist egal: braucht nur “eine JSON-URL, die sehr gut cached”.

Alternativen (mit Machbarkeit & Schwierigkeit)

Option B: WebSockets/SSE (dauerhaft offene Verbindungen)

Machbarkeit: Mittel bis hoch (mit richtiger Infrastruktur), aber “gleichzeitig” bleibt trotzdem schwer.
Schwierigkeit: Hoch (Ops, Load-Balancing, Reconnect-Stürme, Backpressure, Observability).

Pro:

  • Niedrigere Latenz “im Mittel” als Polling.
  • “Push”-Charakter: Admin klickt, Server sendet.

Contra:

  • 50k gleichzeitige offene Verbindungen sind machbar, aber: Betrieb ist anspruchsvoll.
  • Synchronität ist weiterhin nicht garantiert (Nachrichten kommen nicht alle gleich an).
  • Django “klassisch” (sync gunicorn) ungeeignet; async stack nötig.

Wann sinnvoll:

  • Wenn ihr wirklich Patterns in hoher Frequenz (z.B. 10–20 Hz) fahren wollt.
  • Oder wenn “Show Control” sehr interaktiv ist und Polling zu grob wirkt.

Option C: Cloudflare Durable Objects / Workers als Fanout-Hub

Machbarkeit: Hoch für “viele Verbindungen halten” + “State zentral”.
Schwierigkeit: Mittel (neue Plattform), dafür weniger eigene Ops.

Pro:

  • Durable Object kann als “Single Source of Truth” für aktuellen Show-State dienen.
  • Connections können “am Edge” enden; Origin entlastet.
  • Preis/Skalierung potenziell attraktiv.

Contra:

  • Neue Architektur + Vendor Lock-in.
  • Sync/Simultanität bleibt trotzdem: auch CF kann Physik nicht aushebeln.

Sweet Spot:

  • Kombi aus A + C: Durable Object hält State, Worker liefert gecachte JSON an alle.

Option D: Web Push Notifications

Machbarkeit: Niedrig bis mittel, abhängig von OS/Browser, oft nicht “instant/guaranteed”.
Schwierigkeit: Mittel (VAPID, Subscriptions, Zustelllogik), Ergebnis unsicher.

Pro:

  • Keine dauerhafte Verbindung.
  • Funktioniert auch wenn App nicht aktiv ist (theoretisch).

Contra:

  • Zustellung kann verzögert sein (OS entscheidet), im Stadion-Usecase riskant.
  • UX: Permission-Hürden, evtl. nicht zuverlässig genug für “Live Show”.

Fazit: Für “Gewinner später informieren” ok, für “alle jetzt GELB” eher nein.


Option E: Lokales WLAN + UDP Multicast/Broadcast (native App / spezielle Lösung)

Machbarkeit: Potenziell sehr hoch im lokalen Netz, aber praktisch schwierig mit Smartphones/Browsers.
Schwierigkeit: Hoch (Netzdesign im Stadion + Client-Tech).

Pro:

  • Sehr geringe Latenz im lokalen Netz.
  • “Broadcast” ist konzeptionell passend.

Contra:

  • Browser/PWA kann i.d.R. kein UDP Multicast.
  • Stadion-WLAN muss wirklich professionell sein (AP-Planung, Roaming, Multicast-Handling).
  • LTE/5G Teilnehmer wären außen vor.

Fazit: Eher nur mit nativer App + kontrollierter Netzumgebung.


Option F: Cell Broadcast / telco-grade Lösungen

Machbarkeit: Theoretisch passend, praktisch schwer zugänglich (Provider, Regulierung, Kosten).
Schwierigkeit: Sehr hoch (Partner/Provider).

Pro:

  • “One-to-many” ist genau der Zweck.

Contra:

  • Nicht “mal eben bauen”.
  • Integrations-/Verfügbarkeitsfragen.

Fazit: Nur wenn ihr einen Telco-Partner habt und es ein großes Produkt wird.


Option G: Audio/Ultraschall/QR als Out-of-band Trigger

Audio wurde im Chat verworfen (PA-Lärm, Mikrofon, Fragilität, Nebeneffekte). Machbarkeit: Niedrig.
Fazit: Als “lustiger Hack” denkbar, als Produkt eher nein.


Offene Issues / Risiken (die unabhängig von Technologie bleiben)

  1. Synchronität / Wahrnehmung
    • Selbst mit Push kann es “Wellen” geben.
    • Lösung: time-based execution (effective_at) + lokale Offset-Kalibrierung.
  2. Browser & Energiesparen
    • Hintergrundtabs throttlen Timer/Netz.
    • Lösung: Show nur im Vordergrund; Screen-On UX; ggf. “Wake Lock API”.
  3. Netzwerk im Stadion
    • WLAN Überlast, Roaming, NAT, Paketverlust, Congestion.
    • Lösung: POC vor Ort + ggf. “stadium WiFi required” als Produktentscheidung.
  4. Load Spikes
    • Wenn 50k gleichzeitig reloaden/reconnecten (“thundering herd”).
    • Lösung: Caching, jitter, exponential backoff, serverseitige Rate-Limits, pre-warming.
  5. Gewinnerdaten
    • winners-Liste darf nicht riesig werden.
    • Lösung: wenige Gewinner (ok). Bei vielen Gewinnern: segmentierte Listen oder Bloom/bitset.
  6. Security / Manipulation
    • Clients dürfen nicht “Gewinner” faken.
    • Lösung: Gewinner erst serverseitig verifizieren (z.B. QR-Code claim) oder signierte payloads.

Konkreter Vorschlag für einen POC (1–2 Tage)

  1. Minimaler Endpoint /state liefert gecachte JSON:
    • version, effective_at, color, winners.
  2. Client-PWA:
    • Polling-Loop (1–2s im Show-Mode), Offset-Schätzung, scheduled execution.
    • UI: Vollbildfarbe, optional debug overlay (offset, last_version, RTT).
  3. Lasttest:
    • Simulieren 10k–50k req/s (k6/locust), Cache-Hit-Rate messen.
  4. Stadion-naher Test:
    • 10–30 Handys, unterschiedliche Modelle/Browser, WLAN+LTE, messen:
      • Verteilung der “switch time” (p50/p95/p99).
  5. Ergebnis-Kriterium:
    • “Für Zuschauer wirkt es gleichzeitig” = z.B. p95 innerhalb 1–2s Fenster.

Kurzfazit

  • Ohne offene Verbindungen ist Polling + Edge-Caching + Zeitstempel die robusteste Best-Practice, die mit PWA realistisch funktioniert und die 10s-Anforderung treffen kann.
  • WebSockets/SSE sind möglich, aber erhöhen Ops-Komplexität und lösen “Synchronität” nicht automatisch.
  • Cloudflare kann helfen, vor allem als Caching/Edge und optional als State-Hub (Durable Objects).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment