Jumlah tayangan adalah angka pertama yang dipercayai pengunjung. Ini memberi tahu mereka apakah sebuah video layak ditonton selama tiga puluh detik atau tiga puluh menit. Di TopVideoHub, angka tersebut bergerak cepat. Klip yang sedang tren dapat mengumpulkan 40.000 tayangan dalam sepuluh menit. Jika penghitung di halaman membeku, suasana terasa sepi. Pengguna pun pergi.
Menampilkan angka tersebut ke browser terdengar sepele. Kenyataannya tidak. Solusi pertama yang biasanya diambil oleh sebagian besar tim adalah polling. Ia mudah diimplementasikan dan berjalan lancar di lingkungan staging. Namun, staging bisa menipu.
Ketika Polling Menjadi DDoS Terhadap Diri Sendiri
Tim TopVideoHub membangun poller JavaScript sederhana. Ia mengambil jumlah tayangan terbaru setiap lima detik. Di lingkungan pengujian dengan tiga browser terbuka, semuanya tampak hebat. Di produksi, hal itu menghancurkan platform.
Delapan ribu penonton bersamaan yang melakukan refresh setiap lima detik menghasilkan 1.600 permintaan per detik. Setiap permintaan menembus langsung ke database. Lag replikasi melonjak. Read replica terbebani. Lapisan cache terlewati. Tim tersebut tidak sedang menyajikan video. Mereka justru menyajikan beban yang mereka buat sendiri.
Polling tidak berbahaya sampai akhirnya ia menjadi masalah. Untuk dashboard dengan trafik rendah atau panel admin, ini tidak masalah. Untuk halaman video viral, ini adalah bom waktu. Tim membutuhkan saluran yang persisten dari server ke browser, tetapi mereka tidak membutuhkan kompleksitas protokol full-duplex.
Mengapa SSE Cocok untuk Saluran Satu Arah
Server-Sent Events (SSE) dibangun khusus untuk jenis masalah seperti ini: server memiliki data, dan browser hanya perlu mendengarkan.
Berbeda dengan WebSockets, SSE berjalan di atas HTTP biasa. Hal ini lebih penting daripada kedengarannya. Anda tidak memerlukan aturan proxy baru, header upgrade, atau manuver load-balancer yang rumit. Jika server Anda menggunakan HTTP/1.1 atau HTTP/2, SSE akan berfungsi. Proses debugging sangat mudah karena stream tersebut hanyalah teks. Anda dapat mengarahkan curl ke endpoint dan melihat angka bergulir secara real-time, yang jauh lebih baik daripada menebak-nebak mengapa frame socket biner mengalami kesalahan.
Browser menangani bagian-bagian yang membosankan secara gratis. Jika koneksi terputus, SSE akan menyambung kembali secara otomatis dengan header Last-Event-ID sehingga server tahu di mana harus melanjutkan. API di JavaScript sangat ringkas: buat EventSource, pasang handler onmessage, dan selesai.
Caching Adalah Arsitektur yang Sebenarnya
Kesalahan arsitektur terbesar dalam penghitung langsung adalah memperlakukan setiap koneksi browser sebagai alasan untuk melakukan query ke database. Jika 8.000 orang menonton video yang sama, menjalankan 8.000 query setiap dua detik adalah kegilaan. Database Anda tidak akan mampu bertahan menghadapi trafik viral.
TopVideoHub mengatasi hal ini dengan APCu, cache opcode dan user in-memory milik PHP. Alurnya sederhana. Sebuah proses latar belakang—atau pemanggilan endpoint ringan berdasarkan timer—menulis jumlah tayangan saat ini ke APCu setiap dua detik sekali. Endpoint SSE, yang mungkin sedang dibuka oleh ribuan browser, membaca secara eksklusif dari APCu.
Hasilnya: database hanya menerima satu hit setiap dua detik, tidak peduli berapa banyak penonton yang sedang menonton. Cache menjadi penyerap guncangan. APCu bukanlah sesuatu yang eksotis. Ia disertakan bersama PHP, berada di shared memory, dan membacanya lebih cepat daripada putaran jaringan mana pun. Untuk angka tunggal yang berubah sering tetapi tidak secara instan, ini adalah alat yang tepat.
Jika Anda tidak menggunakan APCu, Redis atau Memcached juga bisa digunakan. Prinsipnya tetap sama. Pisahkan jalur pembacaan intensif dari database.
Meyakinkan PHP, LiteSpeed, dan Cloudflare untuk Melakukan Streaming
PHP ingin segera selesai dan "pulang". Web server ingin melakukan buffering pada output dan memberikan respons yang rapi. SSE membutuhkan hal sebaliknya: koneksi yang tetap terbuka, melakukan flushing byte saat data tiba. Tanpa penanganan yang tepat, "stream" Anda akan tiba sebagai satu gumpalan data tiga puluh detik kemudian, sehingga menghilangkan tujuannya.
Berikut adalah cara TopVideoHub menjaga saluran tetap lancar.
Matikan output buffering. Di awal skrip SSE, nonaktifkan setiap lapisan buffering yang mungkin diaktifkan oleh PHP. Panggil ob_end_flush() jika buffer sedang aktif, dan matikan implicit flushing dengan ob_implicit_flush(true) setelah header Anda dikirim.
Beritahu proxy untuk berhenti. Kirim header X-Accel-Buffering: no. Nginx mematuhinya. LiteSpeed mematuhinya. Ini memberi sinyal bahwa respons tidak boleh di-buffer atau dikompresi menjadi satu gumpalan yang dapat disimpan di cache.
Atur masa pakai yang singkat. Setiap koneksi SSE menyita satu PHP worker. TopVideoHub membatasi stream maksimal 55 detik. Ketika timer habis, server mengirimkan komentar terakhir, menutup stream, dan browser akan menyambung kembali secara otomatis. Penyambungan kembali tersebut akan masuk ke worker yang baru, mencegah satu proses tunggal untuk terus menempati resource selamanya.
Ping agar tetap terhubung. Kirimkan baris komentar—sesuatu seperti : ping—setiap dua puluh detik. Komentar dalam SSE diabaikan oleh penangan pesan browser, tetapi komentar tersebut menjaga koneksi TCP tetap aktif. Load balancer dan CDN sering kali memutuskan koneksi yang diam setelah tiga puluh atau enam puluh detik. Satu karakter newline sederhana dapat menyelamatkan Anda dari pemutusan tersebut.
Hargai tab pengguna. Saat pengunjung meminimalkan atau menyembunyikan tab, segera berhenti. Pantau visibilitychange di browser dan panggil eventSource.close(). Server juga harus mendeteksi diskoneksi klien dan menghentikan loop. PHP dapat memeriksa connection_aborted() di dalam loop. Jangan biarkan koneksi hantu menghabiskan worker untuk orang yang sudah pergi sepuluh menit yang lalu.
Batas Maksimal: PHP Workers
SSE di PHP jujur mengenai batasannya. Setiap koneksi SSE yang terbuka mengonsumsi satu PHP worker. Jika pool Anda memiliki seratus worker, Anda memiliki seratus stream. Titik. Tidak ada solusi async selama Anda berada di dalam model proses Apache atau PHP-FPM. Anda dapat menyetel pm.max_children, tetapi memori dan CPU menentukan batas sebenarnya.
Batasan tersebut akan terasa cepat jika Anda juga menggunakan worker untuk pemuatan halaman reguler, panggilan API, dan pembuatan aset. Pantau saturasi worker Anda dengan cermat. Jika endpoint SSE Anda mulai mengantre karena semua worker terkunci dalam stream selama dua puluh menit, seluruh situs Anda akan melambat.
Ketika kapasitasnya tidak lagi mencukupi, berpindahlah. Go biasanya menjadi langkah berikutnya, meskipun Rust, Node.js, atau Erlang dapat memainkan peran yang sama. Goroutine di Go adalah kuncinya. Sebuah goroutine hanya memakan beberapa kilobyte. Anda dapat menampung puluhan ribu stream pada perangkat keras sederhana tanpa kesulitan. Logika intinya tetap identik—baca dari cache, tulis ke socket—tetapi runtime beralih dari proses yang berat ke thread yang ringan.
Namun, jangan mulai dari sana. PHP dapat membawa Anda cukup jauh. Validasi produk terlebih dahulu. Ketika halaman metrik menunjukkan kehabisan worker alih-alih kelebihan beban database, berarti Anda telah melampaui kapasitas stack tersebut. Itu adalah masalah yang bagus.
Kesimpulan
Live counter bukan tentang teknologi mentah. Ini tentang melindungi database Anda dari pengguna Anda sendiri. Mulailah dengan SSE karena ini lebih sederhana daripada kelihatannya. Lakukan caching secara agresif antara stream dan database agar jumlah koneksi tidak menjadi jumlah query. Awasi batas worker Anda dengan sangat ketat. Dan mulailah dengan sederhana. PHP sudah cukup sampai saat ia tidak lagi mencukupi, dan pada saat itu Anda akan tahu persis mengapa Anda melakukan penulisan ulang.
Untuk proyek Anda berikutnya:
- Gunakan SSE saat data mengalir satu arah, dari server ke browser.
- Letakkan lapisan cache di depan database. Satu query setiap beberapa detik lebih baik daripada ribuan.
- Batasi koneksi SSE di bawah satu menit dan biarkan browser menyambung kembali.
- Tutup stream saat tab disembunyikan. Jangan membuang-buang worker aktif untuk koneksi yang menganggur.
- Pantau penggunaan worker PHP. Saat Anda mencapai batas maksimal, pindahkan lapisan streaming ke Go.
