Die Aufrufzahl ist die erste Zahl, der ein Besucher vertraut. Sie sagt ihm, ob ein Video dreißig Sekunden oder dreißig Minuten seiner Zeit wert ist. Bei TopVideoHub bewegt sich diese Zahl schnell. Ein Trend-Clip kann in zehn Minuten 40.000 Aufrufe sammeln. Wenn der Zähler auf der Seite einfriert, fühlt sich der Raum leer an. Die Nutzer springen ab.
Diese Zahl an den Browser zu übertragen, klingt trivial. Ist es aber nicht. Die erste Lösung, nach der die meisten Teams greifen, ist Polling. Es ist einfach zu implementieren und funktioniert in der Staging-Umgebung einwandfrei. Aber Staging lügt.
Wenn Polling zu einem DDoS gegen sich selbst wird
Das TopVideoHub-Team baute einen einfachen JavaScript-Poller. Er rief alle fünf Sekunden die neueste Aufrufzahl ab. In einer Testumgebung mit drei geöffneten Browsern sah das großartig aus. In der Produktion brachte es die Plattform zum Einsturz.
Achttausend gleichzeitige Zuschauer, die alle fünf Sekunden aktualisierten, erzeugten 1.600 Anfragen pro Sekunde. Jede Anfrage griff direkt auf die Datenbank zu. Die Replikationsverzögerung schoss in die Höhe. Die Read-Replicas ächzten. Cache-Ebenen wurden umgangen. Das Team lieferte keine Videos aus. Sie lieferten selbst verursachte Last.
Polling ist unschuldig, bis es das nicht mehr ist. Für Dashboards mit geringem Traffic oder Admin-Panels ist es völlig in Ordnung. Für eine virale Videoseite ist es eine tickende Zeitbombe. Das Team benötigte eine dauerhafte Verbindung vom Server zum Browser, aber nicht die Komplexität eines Full-Duplex-Protokolls.
Warum SSE perfekt für Einweg-Verbindungen ist
Server-Sent Events (SSE) wurde genau für diese Art von Problem entwickelt: Der Server hat Daten, und der Browser muss sie nur empfangen.
Im Gegensatz zu WebSockets basiert SSE auf einfachem HTTP. Das ist wichtiger, als es klingt. Man benötigt keine neuen Proxy-Regeln, Upgrade-Header oder Load-Balancer-Akrobatik. Wenn Ihr Server HTTP/1.1 oder HTTP/2 spricht, funktioniert SSE. Das Debugging ist mühelos, da der Stream einfach nur Text ist. Man kann curl auf den Endpunkt richten und zusehen, wie die Zahlen in Echtzeit durchlaufen – das ist besser, als zu raten, warum ein binärer Socket-Frame fehlgeschlagen ist.
Der Browser übernimmt die mühsamen Aufgaben kostenlos. Wenn die Verbindung abbricht, stellt SSE mithilfe des Last-Event-ID-Headers automatisch wieder eine Verbindung her, sodass der Server weiß, wo er fortfahren muss. Die API-Oberfläche in JavaScript ist minimal: Erstellen Sie eine EventSource, hängen Sie einen onmessage-Handler an, und fertig.
Caching ist die eigentliche Architektur
Der größte Architekturfehler bei Live-Zählern besteht darin, jede Browserverbindung als Grund zu behandeln, die Datenbank abzufragen. Wenn 8.000 Menschen dasselbe Video schauen, sind 8.000 Abfragen alle zwei Sekunden Wahnsinn. Ihre Datenbank wird viralen Traffic nicht überleben.
TopVideoHub löste dies mit APCu, dem In-Memory-Opcode- und User-Cache von PHP. Der Ablauf ist einfach. Ein Hintergrundprozess – oder ein leichtgewichtiger Endpunkt, der per Timer aufgerufen wird – schreibt alle zwei Sekunden die aktuelle Aufrufzahl in APCu. Der SSE-Endpunkt, der von tausenden Browsern offen gehalten werden kann, liest ausschließlich aus APCu.
Das Ergebnis: Die Datenbank erhält alle zwei Sekunden genau einen Zugriff, egal wie viele Zuschauer zuschauen. Der Cache wird zum Stoßdämpfer. APCu ist nicht exotisch. Es wird mit PHP ausgeliefert, lebt im Shared Memory und liest schneller als jeder Netzwerk-Roundtrip. Für eine einzelne Zahl, die sich häufig, aber nicht instantan ändert, ist es das richtige Werkzeug.
Falls Sie kein APCu verwenden, funktionieren auch Redis oder Memcached. Das Prinzip bleibt dasselbe: Trennen Sie den Hot Read Path von der Datenbank.
PHP, LiteSpeed und Cloudflare zum Streamen überreden
PHP möchte fertig werden und Feierabend machen. Webserver wollen die Ausgabe puffern und eine saubere Antwort liefern. SSE benötigt das Gegenteil: eine Verbindung, die offen bleibt und Bytes flasht, sobald sie eintreffen. Ohne Vorsicht kommt Ihr „Stream“ erst dreißig Sekunden später als ein einziger Block an, was den Zweck vereitelt.
So hielt TopVideoHub die Leitung frei.
Output-Buffering deaktivieren. Deaktivieren Sie zu Beginn des SSE-Skripts jede Pufferungsebene, die PHP aktiviert haben könnte. Rufen Sie ob_end_flush() auf, falls ein Puffer aktiv ist, und schalten Sie das implizite Flashen mit ob_implicit_flush(true) aus, nachdem Ihre Header gesendet wurden.
Proxys anweisen, Abstand zu halten. Senden Sie den Header X-Accel-Buffering: no. Nginx respektiert ihn. LiteSpeed respektiert ihn. Er signalisiert, dass die Antwort nicht gepuffert oder zu einem cachebaren Block komprimiert werden soll.
Eine kurze Lebensdauer festlegen. Jede SSE-Verbindung bindet einen PHP-Worker. TopVideoHub begrenzt Streams auf 55 Sekunden. Wenn der Timer abläuft, sendet der Server einen abschließenden Kommentar, schließt den Stream, und der Browser stellt automatisch die Verbindung wieder her. Diese Wiederverbindung landet auf einem frischen Worker, was verhindert, dass ein einzelner Prozess ewig besetzt bleibt.
Ping to stay alive. Send a comment line—something like : ping—every twenty seconds. Comments in SSE are ignored by the browser’s message handler, but they keep the TCP connection warm. Load balancers and CDNs often drop silent connections after thirty or sixty seconds. A cheap newline saves you from that axe.
Respect the user’s tab. When a visitor minimizes or hides the tab, bail out. Listen for visibilitychange in the browser and call eventSource.close(). The server should also detect a client disconnect and terminate the loop. PHP can check connection_aborted() inside a loop. Do not let ghost connections burn workers for people who left ten minutes ago.
The Hard Ceiling: PHP Workers
SSE in PHP is honest about its limits. Every open SSE connection consumes one PHP worker. If your pool has a hundred workers, you have a hundred streams. Full stop. There is no async workaround while you are inside an Apache or PHP-FPM process model. You can tune pm.max_children, but memory and CPU set the real boundary.
That limit bites fast if you are also using workers for regular page loads, API calls, and asset generation. Monitor your worker saturation carefully. If your SSE endpoint starts queuing because all workers are locked in twenty-minute streams, your entire site slows down.
When the numbers no longer fit, move. Go is the usual next step, though Rust, Node.js, or Erlang can play the same role. Go’s goroutines are the key. A goroutine costs a few kilobytes. You can hold tens of thousands of streams on modest hardware without breaking a sweat. The core logic stays identical—read from cache, write to socket—but the runtime switches from heavy processes to lightweight threads.
Do not start there, though. PHP gets you surprisingly far. Validate the product first. When the metrics page shows worker exhaustion instead of database overload, you have outgrown the stack. That is a good problem.
Takeaway
Live counters are not about raw technology. They are about protecting your database from your own users. Start with SSE because it is simpler than it looks. Cache aggressively between the stream and the database so connection count does not become query count. Watch your worker limits like a hawk. And start simple. PHP is enough until it is not, and by then you will know exactly why you are rewriting.
For your next project:
- Use SSE when the data flows one way, from server to browser.
- Put a cache layer in front of the database. One query every few seconds beats thousands.
- Cap SSE connections to under a minute and let the browser reconnect.
- Close streams on tab hide. Do not fund idle connections with live workers.
- Monitor PHP worker usage. When you max out, port the streaming layer to Go.
