A view count is the first number a visitor trusts. It tells them whether a video is worth thirty seconds or thirty minutes of their time. At TopVideoHub, that number moves fast. A trending clip can rack up 40,000 views in ten minutes. If the counter on the page freezes, the room feels empty. Users bounce.

Getting that number to the browser sounds trivial. It is not. The first solution most teams reach for is polling. It is easy to wire up and it works fine in staging. But staging lies.

When Polling Becomes a DDoS Against Yourself

The TopVideoHub team built a simple JavaScript poller. It fetched the latest view count every five seconds. In a test environment with three browsers open, it looked great. In production, it cratered the platform.

Eight thousand concurrent viewers refreshing every five seconds generated 1,600 requests per second. Every request drilled into the database. Replication lag spiked. Read replicas groaned. Cache layers got bypassed. The team was not serving video. They were serving self-inflicted load.

Polling is innocent until it is not. For low-traffic dashboards or admin panels, it is fine. For a viral video page, it is a ticking bomb. The team needed a persistent pipe from server to browser, but they did not need the complexity of a full-duplex protocol.

Why SSE Fits One-Way Pipes

Server-Sent Events (SSE) is built for exactly this shape of problem: the server has data, and the browser only needs to listen.

Unlike WebSockets, SSE rides on plain HTTP. That matters more than it sounds. You do not need new proxy rules, upgrade headers, or load-balancer gymnastics. If your server speaks HTTP/1.1 or HTTP/2, SSE works. Debugging is painless because the stream is just text. You can point curl at the endpoint and watch numbers scroll by in real time, which beats guessing why a binary socket frame went sideways.

The browser handles the tedious parts for free. If the connection drops, SSE reconnects automatically with the Last-Event-ID header so the server knows where to resume. The API surface in JavaScript is tiny: create an EventSource, attach an onmessage handler, and you are done.

Caching Is the Real Architecture

The biggest architectural mistake in live counters is treating every browser connection as a reason to query the database. If 8,000 people watch the same video, running 8,000 queries every two seconds is madness. Your database will not survive viral traffic.

TopVideoHub solved this with APCu, PHP’s in-memory opcode and user cache. The flow is simple. A background process—or a lightweight endpoint hit on a timer—writes the current view count into APCu once every two seconds. The SSE endpoint, which thousands of browsers may be holding open, reads exclusively from APCu.

The result: the database takes one hit every two seconds, no matter how many viewers are tuned in. The cache becomes the shock absorber. APCu is not exotic. It ships with PHP, lives in shared memory, and reads faster than any network round-trip. For a single number that changes frequently but not instantaneously, it is the right tool.

If you are not running APCu, Redis or Memcached work too. The principle stays the same. Separate the hot read path from the database.

Convincing PHP, LiteSpeed, and Cloudflare to Stream

PHP wants to finish and go home. Web servers want to buffer output and deliver a neat response. SSE needs the opposite: a connection that stays open, flushing bytes as they arrive. Without care, your "stream" arrives as a single blob thirty seconds later, defeating the purpose.

Here is how TopVideoHub kept the pipe unclogged.

Kill output buffering. At the start of the SSE script, disable every layer of buffering PHP might have enabled. Call ob_end_flush() if a buffer is active, and turn off implicit flushing with ob_implicit_flush(true) after your headers are sent.

Tell proxies to back off. Send the X-Accel-Buffering: no header. Nginx listens to it. LiteSpeed listens to it. It signals that the response should not be buffered or compressed into a cacheable lump.

Set a short lifetime. Each SSE connection ties up a PHP worker. TopVideoHub caps streams at 55 seconds. When the timer hits, the server sends a final comment, closes the stream, and the browser reconnects automatically. That reconnection lands on a fresh worker, preventing any single process from squatting forever.

Ping para manter-se vivo. Envie uma linha de comentário — algo como : ping — a cada vinte segundos. Comentários em SSE são ignorados pelo manipulador de mensagens do navegador, mas mantêm a conexão TCP ativa. Load balancers e CDNs frequentemente derrubam conexões silenciosas após trinta ou sessenta segundos. Uma simples quebra de linha evita que você sofra esse corte.

Respeite a aba do usuário. Quando um visitante minimizar ou ocultar a aba, encerre a conexão. Escute o visibilitychange no navegador e chame eventSource.close(). O servidor também deve detectar a desconexão do cliente e encerrar o loop. O PHP pode verificar connection_aborted() dentro de um loop. Não deixe conexões fantasmas consumirem workers de pessoas que saíram há dez minutos.

O Limite Inflexível: PHP Workers

O SSE no PHP é honesto sobre seus limites. Cada conexão SSE aberta consome um worker do PHP. Se o seu pool tem cem workers, você tem cem streams. Ponto final. Não há contorno assíncrono enquanto você estiver dentro de um modelo de processo Apache ou PHP-FPM. Você pode ajustar o pm.max_children, mas a memória e a CPU definem o limite real.

Esse limite aparece rapidamente se você também estiver usando workers para carregamentos de páginas regulares, chamadas de API e geração de assets. Monitore a saturação dos seus workers cuidadosamente. Se o seu endpoint de SSE começar a enfileirar porque todos os workers estão presos em streams de vinte minutos, seu site inteiro ficará lento.

Quando os números não couberem mais, mude. Go é o próximo passo usual, embora Rust, Node.js ou Erlang possam desempenhar o mesmo papel. As goroutines do Go são a chave. Uma goroutine custa apenas alguns kilobytes. Você pode manter dezenas de milhares de streams em um hardware modesto sem esforço. A lógica central permanece idêntica — ler do cache, escrever no socket — mas o runtime muda de processos pesados para threads leves.

Mas não comece por aí. O PHP leva você surpreendentemente longe. Valide o produto primeiro. Quando a página de métricas mostrar exaustão de workers em vez de sobrecarga do banco de dados, significa que você superou a stack. Esse é um bom problema.

Conclusão

Contadores em tempo real não são sobre tecnologia bruta. São sobre proteger seu banco de dados dos seus próprios usuários. Comece com SSE porque é mais simples do que parece. Faça cache agressivo entre o stream e o banco de dados para que a contagem de conexões não se torne uma contagem de queries. Vigie seus limites de workers como um falcão. E comece simples. O PHP é suficiente até que não seja, e a essa altura você saberá exatamente por que está reescrevendo.

Para o seu próximo projeto:

  • Use SSE quando os dados fluírem em uma única direção, do servidor para o navegador.
  • Coloque uma camada de cache à frente do banco de dados. Uma query a cada poucos segundos supera milhares.
  • Limite as conexões SSE para menos de um minuto e deixe o navegador se reconectar.
  • Feche os streams ao ocultar a aba. Não financie conexões ociosas com workers ativos.
  • Monitore o uso de workers do PHP. Quando atingir o limite, migre a camada de streaming para Go.