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) ಮಾಡಿ. ಪ್ರತಿ ಇಪ್ಪತ್ತು ಸೆಕೆಂಡುಗಳಿಗೆ ಒಂದು ಕಾಮೆಂಟ್ ಲೈನ್—ಉದಾಹರಣೆಗೆ : ping—ಕಳುಹಿಸಿ. SSE ನಲ್ಲಿನ ಕಾಮೆಂಟ್‌ಗಳನ್ನು ಬ್ರೌಸರ್‌ನ ಮೆಸೇಜ್ ಹ್ಯಾಂಡ್ಲರ್ ನಿರ್ಲಕ್ಷಿಸುತ್ತದೆ, ಆದರೆ ಅವು TCP ಸಂಪರ್ಕವನ್ನು ಸಕ್ರಿಯವಾಗಿಡುತ್ತವೆ. ಲೋಡ್ ಬ್ಯಾಲೆನ್ಸರ್‌ಗಳು ಮತ್ತು CDNಗಳು ಹೆಚ್ಚಾಗಿ ಮೂವತ್ತು ಅಥವಾ ಅರವತ್ತು ಸೆಕೆಂಡುಗಳ ನಂತರ ನಿಷ್ಕ್ರಿಯ ಸಂಪರ್ಕಗಳನ್ನು ಕಡಿತಗೊಳಿಸುತ್ತವೆ. ಒಂದು ಸಣ್ಣ ನ್ಯೂಲೈನ್ (newline) ನಿಮ್ಮನ್ನು ಆ ಸಮಸ್ಯೆಯಿಂದ ಉಳಿಸುತ್ತದೆ.

ಬಳಕೆದಾರರ ಟ್ಯಾಬ್‌ಗೆ ಗೌರವ ನೀಡಿ. ಸಂದರ್ಶಕರು ಟ್ಯಾಬ್ ಅನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿದಾಗ ಅಥವಾ ಮರೆಮಾಡಿದಾಗ, ಸಂಪರ್ಕವನ್ನು ಕಡಿತಗೊಳಿಸಿ. ಬ್ರೌಸರ್‌ನಲ್ಲಿ visibilitychange ಅನ್ನು ಗಮನಿಸಿ ಮತ್ತು eventSource.close() ಅನ್ನು ಕರೆಯಿರಿ. ಸರ್ವರ್ ಕೂಡ ಕ್ಲೈಂಟ್ ಡಿಸ್ಕನೆಕ್ಷನ್ ಅನ್ನು ಪತ್ತೆಹಚ್ಚಿ ಲೂಪ್ ಅನ್ನು ಕೊನೆಗೊಳಿಸಬೇಕು. PHP ನಲ್ಲಿ ಲೂಪ್ ಒಳಗಡೆ connection_aborted() ಮೂಲಕ ಇದನ್ನು ಪರಿಶೀಲಿಸಬಹುದು. ಹತ್ತು ನಿಮಿಷಗಳ ಹಿಂದೆಯೇ ಹೊರಟುಹೋದ ಬಳಕೆದಾರರಿಗಾಗಿ 'ಘೋಸ್ಟ್ ಕನೆಕ್ಷನ್‌ಗಳು' (ghost connections) ವರ್ಕರ್ಸ್‌ಗಳನ್ನು ವ್ಯರ್ಥ ಮಾಡಬೇಡಿ.

ಕಠಿಣ ಮಿತಿ: PHP ವರ್ಕರ್ಸ್ (Workers)

PHP ನಲ್ಲಿ SSE ತನ್ನ ಮಿತಿಗಳ ಬಗ್ಗೆ ಸ್ಪಷ್ಟವಾಗಿದೆ. ಪ್ರತಿಯೊಂದು ತೆರೆದ SSE ಸಂಪರ್ಕವು ಒಂದು PHP ವರ್ಕರ್ ಅನ್ನು ಬಳಸುತ್ತದೆ. ನಿಮ್ಮ ಪೂಲ್‌ನಲ್ಲಿ ನೂರು ವರ್ಕರ್ಸ್‌ಗಳಿದ್ದರೆ, ನಿಮ್ಮ ಬಳಿ ನೂರು ಸ್ಟ್ರೀಮ್‌ಗಳಿವೆ ಎಂದರ್ಥ. ಅಷ್ಟೇ. ನೀವು Apache ಅಥವಾ PHP-FPM ಪ್ರೊಸೆಸ್ ಮಾಡೆಲ್ ಒಳಗಿರುವಾಗ ಯಾವುದೇ async ಪರಿಹಾರವಿಲ್ಲ. ನೀವು pm.max_children ಅನ್ನು ಟ್ಯೂನ್ ಮಾಡಬಹುದು, ಆದರೆ ಮೆಮೊರಿ ಮತ್ತು CPU ನಿಜವಾದ ಮಿತಿಯನ್ನು ನಿರ್ಧರಿಸುತ್ತವೆ.

ನೀವು ಸಾಮಾನ್ಯ ಪೇಜ್ ಲೋಡ್‌ಗಳು, API ಕರೆಗಳು ಮತ್ತು ಅಸೆಟ್ ಜನರೇಷನ್‌ಗಾಗಿ ಕೂಡ ವರ್ಕರ್ಸ್‌ಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ, ಈ ಮಿತಿ ಬೇಗನೆ ತಲುಪುತ್ತದೆ. ನಿಮ್ಮ ವರ್ಕರ್ ಸ್ಯಾಚುರೇಶನ್ ಅನ್ನು ಎಚ್ಚರಿಕೆಯಿಂದ ಗಮನಿಸಿ. ಎಲ್ಲಾ ವರ್ಕರ್ಸ್‌ಗಳು ಇಪ್ಪತ್ತು ನಿಮಿಷಗಳ ಸ್ಟ್ರೀಮ್‌ಗಳಲ್ಲಿ ಸಿಲುಕಿಕೊಂಡಿರುವುದರಿಂದ ನಿಮ್ಮ SSE ಎಂಡ್‌ಪಾಯಿಂಟ್ ಕ್ಯೂನಲ್ಲಿ (queuing) ನಿಲ್ಲತೊಡಗಿದರೆ, ನಿಮ್ಮ ಇಡೀ ಸೈಟ್ ನಿಧಾನವಾಗುತ್ತದೆ.

ಸಂಖ್ಯೆಗಳು ಮೀರಿದಾಗ, ಬದಲಾಗಿ. Rust, Node.js ಅಥವಾ Erlang ಕೂಡ ಅದೇ ಪಾತ್ರವನ್ನು ನಿರ್ವಹಿಸಬಹುದಾದರೂ, ಸಾಮಾನ್ಯವಾಗಿ ಮುಂದಿನ ಹಂತವಾಗಿ Go ಅನ್ನು ಬಳಸಲಾಗುತ್ತದೆ. Go ನ goroutines ಇಲ್ಲಿ ಪ್ರಮುಖ ಪಾತ್ರ ವಹಿಸುತ್ತವೆ. ಒಂದು goroutine ಕೇವಲ ಕೆಲವು ಕಿಲೋಬೈಟ್‌ಗಳನ್ನು ಮಾತ್ರ ಬಳಸುತ್ತದೆ. ಸಾಧಾರಣ ಹಾರ್ಡ್‌ವೇರ್‌ನಲ್ಲಿಯೂ ನೀವು ಯಾವುದೇ ಕಷ್ಟವಿಲ್ಲದೆ ಹತ್ತಾರು ಸಾವಿರ ಸ್ಟ್ರೀಮ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಬಹುದು. ಮೂಲ ತರ್ಕ (core logic) ಒಂದೇ ಆಗಿರುತ್ತದೆ—ಕ್ಯಾಶ್‌ನಿಂದ ಓದುವುದು, ಸಾಕೆಟ್‌ಗೆ ಬರೆಯುವುದು—ಆದರೆ ರನ್‌ಟೈಮ್ ಭಾರವಾದ ಪ್ರೊಸೆಸ್‌ಗಳಿಂದ ಹಗುರವಾದ ಥ್ರೆಡ್‌ಗಳಿಗೆ ಬದಲಾಗುತ್ತದೆ.

ಆದರೆ ಅಲ್ಲಿಂದಲೇ ಪ್ರಾರಂಭಿಸಬೇಡಿ. PHP ನಿಮ್ಮನ್ನು ಅಚ್ಚರಿಯ ಮಟ್ಟದವರೆಗೆ ಕೊಂಡೊಯ್ಯಬಲ್ಲದು. ಮೊದಲು ಉತ್ಪನ್ನವನ್ನು ಪರೀಕ್ಷಿಸಿ (Validate). ಮೆಟ್ರಿಕ್ಸ್ ಪೇಜ್‌ನಲ್ಲಿ ಡೇಟಾಬೇಸ್ ಓವರ್‌ಲೋಡ್ ಬದಲಿಗೆ ವರ್ಕರ್ಗಳ ಕೊರತೆ (worker exhaustion) ಕಂಡುಬಂದಾಗ, ನೀವು ಈ ಸ್ಟ್ಯಾಕ್‌ನ ಮಿತಿಯನ್ನು ಮೀರಿ ಬೆಳೆದಿದ್ದೀರಿ ಎಂದರ್ಥ. ಇದು ಒಂದು ಒಳ್ಳೆಯ ಸಮಸ್ಯೆ.

ಸಾರಾಂಶ

ಲೈವ್ ಕೌಂಟರ್‌ಗಳು ಕೇವಲ ತಂತ್ರಜ್ಞಾನದ ಬಗ್ಗೆ ಅಲ್ಲ. ಅವು ನಿಮ್ಮ ಡೇಟಾಬೇಸ್ ಅನ್ನು ನಿಮ್ಮ ಬಳಕೆದಾರರಿಂದ ರಕ್ಷಿಸುವ ಬಗ್ಗೆಯಾಗಿವೆ. SSE ಇಂದಲೇ ಪ್ರಾರಂಭಿಸಿ ಏಕೆಂದರೆ ಇದು ನೋಡಲು ಇರುವುದಕ್ಕಿಂತ ಸರಳವಾಗಿದೆ. ಸ್ಟ್ರೀಮ್ ಮತ್ತು ಡೇಟಾಬೇಸ್ ನಡುವೆ ಹೆಚ್ಚು ಕ್ಯಾಶಿಂಗ್ (cache) ಬಳಸಿ, ಇದರಿಂದ ಕನೆಕ್ಷನ್ ಸಂಖ್ಯೆಯು ಕ್ವೆರಿ ಸಂಖ್ಯೆಯಾಗಿ ಬದಲಾಗದಂತೆ ನೋಡಿಕೊಳ್ಳಿ. ನಿಮ್ಮ ವರ್ಕರ್ ಮಿತಿಗಳನ್ನು ಸೂಕ್ಷ್ಮವಾಗಿ ಗಮನಿಸಿ. ಮತ್ತು ಸರಳವಾಗಿ ಪ್ರಾರಂಭಿಸಿ. PHP ಅಗತ್ಯವಿರುವವರೆಗೆ ಸಾಕು, ಅದು ಸಾಲದ ಸಮಯಕ್ಕೆ ನೀವು ಏಕೆ ಮರು-ಬರೆಯುತ್ತಿದ್ದೀರಿ ಎಂಬುದು ನಿಮಗೆ ನಿಖರವಾಗಿ ತಿಳಿದಿರುತ್ತದೆ.

ನಿಮ್ಮ ಮುಂದಿನ ಪ್ರಾಜೆಕ್ಟ್‌ಗಾಗಿ:

  • ಡೇಟಾ ಸರ್ವರ್‌ನಿಂದ ಬ್ರೌಸರ್ ಕಡೆಗೆ ಒಂದು ದಿಕ್ಕಿನಲ್ಲಿ ಹರಿಯುವಾಗ SSE ಬಳಸಿ.
  • ಡೇಟಾಬೇಸ್ ಮುಂದೆ ಕ್ಯಾಶ್ ಲೇಯರ್ ಅನ್ನು ಇರಿಸಿ. ಪ್ರತಿ ಕೆಲವು ಸೆಕೆಂಡುಗಳಿಗೆ ಒಂದು ಕ್ವೆರಿ ಸಾವಿರಾರು ಕ್ವೆರಿಗಳಿಗಿಂತ ಉತ್ತಮ.
  • SSE ಕನೆಕ್ಷನ್‌ಗಳನ್ನು ಒಂದು ನಿಮಿಷಕ್ಕಿಂತ ಕಡಿಮೆ ಇರಿಸಿ ಮತ್ತು ಬ್ರೌಸರ್ ಅನ್ನು ಮರು-ಸಂಪರ್ಕಿಸಲು (reconnect) ಬಿಡಿ.
  • ಟ್ಯಾಬ್ ಮರೆಮಾಡಿದಾಗ ಸ್ಟ್ರೀಮ್‌ಗಳನ್ನು ಮುಚ್ಚಿ. ನಿಷ್ಕ್ರಿಯ ಕನೆಕ್ಷನ್‌ಗಳಿಗಾಗಿ ಲೈವ್ ವರ್ಕರ್ಸ್‌ಗಳನ್ನು ವ್ಯರ್ಥ ಮಾಡಬೇಡಿ.
  • PHP ವರ್ಕರ್ ಬಳಕೆಯನ್ನು ಗಮನಿಸಿ. ನೀವು ಗರಿಷ್ಠ ಮಿತಿಯನ್ನು ತಲುಪಿದಾಗ, ಸ್ಟ್ರೀಮಿಂಗ್ ಲೇಯರ್ ಅನ್ನು Go ಗೆ ಬದಲಾಯಿಸಿ.