WebSocket vs Server-Sent Events vs Long Polling: Panduan Memilih Komunikasi Real-Time
Perbandingan praktis WebSocket, Server-Sent Events, dan long polling: cara kerja masing-masing, biaya operasionalnya, dan mana yang cocok untuk chat, notifikasi, live feed, dan dashboard.
Panji Ihsanudin Fajri
16 min read1 reads

Contents
- Apa arti "real-time" di web
- Kenapa pilihan transport itu penting
- Cara kerja long polling
- Short polling vs long polling
- Cara kerja Server-Sent Events
- Cara kerja WebSocket
- Fitur yang sama, tiga cara membangunnya
- Versi long polling
- Versi Server-Sent Events
- Versi WebSocket
- Perbandingan langsung
- Scaling dan operasional masing-masing transport
- Memilih untuk use case nyata
- Chat dan kolaborasi: WebSocket
- Notifikasi dan live feed: Server-Sent Events
- Dashboard dan metrik: SSE, atau polling yang jujur
- Update jarang dan lingkungan terbatas: long polling
- Studi kasus singkat: satu produk, dua jawaban
- Kesalahan umum
- Best practice
- FAQ
- Apakah WebSocket lebih cepat daripada Server-Sent Events?
- Bisakah SSE mengirim data dari klien ke server?
- Kenapa event SSE saya datang dalam semburan, atau tidak datang sama sekali, di production?
- Haruskah seluruh aplikasi pindah ke WebSocket setelah saya menambahkan chat?
- Bagaimana dengan WebTransport dan HTTP/3?
- Kesimpulan
Dashboard kamu menampilkan angka yang berubah dua detik lalu, tapi halaman baru mengetahuinya setelah kamu menekan refresh. Pesan chat sudah sampai di server, lalu diam di sana sementara lawan bicara menatap layar yang tidak bergerak. WebSocket vs Server-Sent Events vs long polling adalah keputusan di balik semua fitur semacam itu, dan kebanyakan developer web harus mengambilnya tanpa gambaran yang jelas tentang perbedaan ketiganya.
Ketiga teknik ini sama-sama mengantarkan data dari server ke browser tanpa reload halaman. Yang membedakan adalah arah alirannya (satu arah atau dua arah), perilaku koneksinya, dan seberapa besar tuntutan infrastrukturnya kepadamu. Artikel ini membahas cara kerja ketiganya, membangun fitur notifikasi yang sama dengan tiga cara, membandingkannya secara langsung, dan menutupnya dengan panduan memilih untuk chat, notifikasi, live feed, dan dashboard.
Ringkasan: Long polling memalsukan perilaku real-time dengan request HTTP yang ditahan — sederhana dan universal, tapi boros saat skala membesar. Server-Sent Events (SSE) adalah stream satu arah di atas HTTP biasa dengan reconnect bawaan browser, cocok untuk notifikasi, feed, dan dashboard. WebSocket adalah koneksi persisten dua arah untuk trafik seperti chat dan kolaborasi, dengan harga berupa state koneksi yang harus kamu kelola sendiri. Pilih berdasarkan arah dan frekuensi datamu.

Apa arti "real-time" di web
"Real-time" di web jarang berarti real-time dalam arti ketat. Maksudnya: ketika sesuatu berubah di server, semua browser yang berkepentingan mengetahuinya cukup cepat sehingga antarmuka terasa hidup — dalam hitungan puluhan atau ratusan milidetik, bukan setelah klik atau refresh berikutnya.
Batasan yang membentuk ketiga teknik ini adalah HTTP yang dirancang sebagai request–response. Klien bertanya, server menjawab, dan server tidak punya cara untuk memulai kontak. Server tidak bisa membuka koneksi ke browser yang berada di balik NAT, firewall, dan jaringan operator seluler. Setiap teknik real-time adalah jalan memutar dari asimetri itu: klien membuka sesuatu lebih dulu, lalu server memakai celah tersebut untuk mengirim data kembali — sekali per request (polling), sebagai stream berkelanjutan (SSE), atau lewat koneksi yang beralih menjadi protokol yang sama sekali berbeda (WebSocket). Memahami satu batasan ini menjelaskan sebagian besar trade-off di bawah.
Kenapa pilihan transport itu penting
Memilih transport bukan keputusan kosmetik. Pilihan ini menentukan berapa banyak koneksi yang harus ditahan servermu pada satu waktu, apakah load balancer dan proxy perlu konfigurasi khusus, bagaimana fitur berperilaku saat ponsel kehilangan sinyal di lift, dan seberapa banyak kode heartbeat, retry, dan sinkronisasi ulang yang akan kamu tulis dan rawat.
Pilihan yang keliru biasanya tidak merusak apa pun di hari pertama. Masalahnya muncul berbulan-bulan kemudian: batas koneksi browser yang tidak kamu ketahui, proxy yang diam-diam menahan (buffer) stream event kamu, atau deretan server yang sebagian besar memorinya habis untuk socket yang menganggur. Kabar baiknya, keputusan ini bertumpu pada dua pertanyaan yang bisa dijawab sebelum menulis kode apa pun. Ke arah mana data mengalir? Dan seberapa sering?
Cara kerja long polling
Long polling adalah trik tertua dari ketiganya, dan tidak membutuhkan apa pun di luar HTTP biasa. Klien mengirim request normal. Alih-alih langsung menjawab, server menahan request itu tetap terbuka sampai ada data baru atau batas waktu habis — umumnya setelah 20 sampai 30 detik. Begitu respons tiba, klien memprosesnya dan langsung mengirim request berikutnya. Bagi pengguna, update muncul pada saat diterbitkan. Bagi server, setiap klien yang menunggu menempati satu request terbuka dan sedikit pencatatan.
Short polling vs long polling
Short polling bertanya "ada yang baru?" pada interval tetap, setiap beberapa detik, dan jawabannya biasanya "tidak ada". Teknik ini mudah dibuat dan sah-sah saja untuk data yang jarang berubah. Biayanya ada pada latensi (rata-rata setengah interval polling) dan pemborosan: dengan interval 5 detik, satu tab yang terbuka menghasilkan lebih dari 17.000 request sehari, hampir semuanya kosong.
Long polling menghilangkan sebagian besar pemborosan itu. Request hanya selesai saat ada data atau batas waktu tercapai, jadi respons kosong jarang terjadi dan pengiriman bersifat seketika saat sesuatu benar-benar terjadi. Harganya adalah koneksi yang tertahan. Seribu klien yang menunggu berarti seribu request terbuka, yang dengan mudah diserap server berbasis event — Node.js, Go, atau Python async — sementara stack thread-per-request merasakan setiap request tertahan sebagai satu thread yang terblokir.
Cara kerja Server-Sent Events
Server-Sent Events menahan satu respons HTTP tetap terbuka dan menulis event ke dalamnya saat peristiwa terjadi. Browser membuat EventSource, server membalas dengan Content-Type: text/event-stream, dan setiap event adalah blok teks kecil: satu baris data: atau lebih, diakhiri baris kosong. Field opsional menambah struktur — event: memberi nama tipe event, id: melekatkan nomor urut, dan retry: memberi tahu browser berapa lama menunggu sebelum reconnect.
Dua detail membuat SSE nyaman di production. Pertama, reconnect adalah bagian dari platform: kalau koneksi putus, EventSource mencoba lagi dengan sendirinya dan mengirim ID event terakhir yang diterimanya lewat header Last-Event-ID, sehingga server bisa memutar ulang persis bagian yang terlewat oleh klien itu. Kedua, SSE tidak pernah meninggalkan HTTP biasa. Tidak ada upgrade protokol, autentikasi bekerja dengan cookie yang sudah kamu punya, dan satu-satunya aturan proxy yang perlu diingat adalah menonaktifkan buffering respons pada endpoint stream.
Batasnya sama jelasnya. Kanal ini satu arah: kalau klien perlu mengirim data, ia memakai request fetch biasa di samping stream. Payload-nya teks UTF-8, jadi data biner perlu di-encode. Dan pada HTTP/1.1, browser membatasi koneksi terbuka per host sekitar enam, sehingga beberapa tab SSE bisa saling berebut — masalah yang sebagian besar hilang pada HTTP/2, di mana stream dimultipleks dalam satu koneksi.
Cara kerja WebSocket
WebSocket, yang distandardisasi dalam RFC 6455, memulai hidupnya sebagai request HTTP yang membawa header Upgrade. Server menjawab 101 Switching Protocols, dan sejak saat itu koneksi berhenti berbicara HTTP. Kedua pihak saling bertukar frame kecil melalui koneksi TCP yang sama, ke dua arah, kapan pun. Framing-nya ramping — pesan kecil hanya membawa header serendah dua byte — dan protokolnya menyediakan frame kontrol ping dan pong agar kedua pihak bisa memeriksa apakah lawan bicaranya masih ada.
Kebebasan itu adalah tujuan sekaligus tagihannya. WebSocket API di browser memberimu send() dan onmessage, dan tidak ada yang lain: tidak ada reconnect otomatis, tidak ada konfirmasi penerimaan pesan, tidak ada room, tidak ada presence. Apa pun yang dibutuhkan fiturmu di luar pesan mentah, kamu rancang sendiri atau ambil dari library. Sebagai gantinya, WebSocket adalah satu-satunya dari ketiganya yang memperlakukan pesan klien-ke-server sebagai trafik kelas satu yang simetris — persis bahan pembentuk chat, game multiplayer, dan pengeditan kolaboratif.

Fitur yang sama, tiga cara membangunnya
Perbedaan abstrak menjadi konkret begitu kamu membangun satu fitur dengan tiga cara. Fiturnya adalah notifikasi: server mengetahui ada event baru (komentar, mention, pembayaran) dan tab browser yang terbuka harus menampilkannya saat itu juga.
Versi long polling
Endpoint server menahan setiap request sampai ada event. Perhatikan bahwa perilaku "real-time"-nya sepenuhnya adalah kesepakatan antara handler ini dan loop klien di bawahnya.
// Server (Express): jawab hanya saat ada kabar, atau timeout dengan sopan
app.get('/poll', async (req, res) => {
const deadline = Date.now() + 25_000;
while (Date.now() < deadline) {
const events = await getEventsSince(req.query.since ?? 0);
if (events.length > 0) return res.json(events);
await sleep(500);
}
res.json([]); // timeout: klien akan bertanya lagi secepatnya
});
// Klien: proses yang datang, lalu langsung bertanya lagi
async function poll(since = 0) {
const res = await fetch(`/poll?since=${since}`);
const events = await res.json();
events.forEach(showNotification);
const next = events.length ? events.at(-1).id : since;
poll(next);
}
Versi Server-Sent Events
Server menulis ke dalam satu respons yang berumur panjang. Kliennya hanya tiga baris, karena reconnect dan parsing event menjadi urusan browser, seperti dijelaskan panduan MDN tentang server-sent events.
// Server (Express): satu respons, ditahan terbuka, event ditulis saat terjadi
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
const send = (event) => {
res.write(`id: ${event.id}\ndata: ${JSON.stringify(event)}\n\n`);
};
bus.on('notification', send);
req.on('close', () => bus.off('notification', send));
});
// Klien: EventSource mengurus reconnect dan ID event terakhir untukmu
const source = new EventSource('/events');
source.onmessage = (e) => showNotification(JSON.parse(e.data));
Versi WebSocket
Server meng-upgrade koneksi lalu memperlakukan pesan sebagai percakapan. Mengirim notifikasi adalah push; menerima balasan pengguna berjalan di jalur yang sama.
// Server (library ws): satu socket per klien, pesan dua arah
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
socket.on('message', (raw) => {
const msg = JSON.parse(raw);
for (const client of wss.clients) {
if (client !== socket) client.send(JSON.stringify(msg));
}
});
});
// Klien: socket yang sama untuk mengirim dan menerima
const socket = new WebSocket('wss://example.com/chat');
socket.onmessage = (e) => renderMessage(JSON.parse(e.data));
sendButton.onclick = () =>
socket.send(JSON.stringify({ text: input.value }));
Baca ketiga versi itu berdampingan dan polanya sulit dilewatkan. Long polling menyebar kompleksitasnya ke loop klien dan endpoint yang sengaja dibuat lambat. SSE memusatkannya di satu handler dan membiarkan browser menanggung sisanya. WebSocket berawal paling pendek lalu bertumbuh paling cepat, karena setiap urusan operasional — reconnect, keaktifan koneksi, pesan yang terlewat — menjadi kodemu sendiri.
Perbandingan langsung
| Aspek | Long Polling | Server-Sent Events | WebSocket |
|---|---|---|---|
| Arah | Server ke klien (klien bertanya) | Server ke klien saja | Dua arah |
| Koneksi | Satu request HTTP per batch | Satu respons HTTP, ditahan terbuka | Satu koneksi hasil upgrade |
| Protokol | HTTP biasa | HTTP biasa | Handshake HTTP, lalu frame WebSocket (RFC 6455) |
| Reconnect | Klien meminta lagi, memang begitu desainnya | Bawaan EventSource, dengan replay Last-Event-ID |
Kamu bangun sendiri |
| Format data | Apa pun yang bisa dibawa HTTP | Event teks UTF-8 | Frame teks dan biner |
| Dukungan browser | Universal | Semua browser modern via EventSource |
Semua browser modern via WebSocket |
| Infrastruktur | Tidak ada yang khusus | Proxy tidak boleh men-buffer stream | Load balancer harus menahan koneksi berumur panjang |
| Tekanan scaling | Banyak request pendek, state per poll | Satu koneksi tertahan per penonton | Satu koneksi tertahan plus state bersama per pengguna |
| Paling cocok | Update jarang, kompatibilitas maksimum | Notifikasi, feed, dashboard | Chat, game, kolaborasi |
Dua baris pada tabel itu menentukan sebagian besar proyek nyata. Yang pertama adalah arah: kalau fiturmu hanya pernah mendorong data ke arah pengguna, mesin dua arah WebSocket tidak memberimu apa-apa. Yang kedua adalah perilaku reconnect. Koneksi putus terus-menerus di dunia nyata — terowongan, lift, perpindahan jaringan — dan transport yang memperlakukan reconnect sebagai fitur bawaan (SSE) atau sebagai loop normal (long polling) akan terasa lebih tangguh daripada yang memaksa timmu merancang jalur pemulihannya dari nol (WebSocket).
Scaling dan operasional masing-masing transport
Ketiga teknik ini menukar request dengan koneksi, dan koneksilah sumber daya yang akhirnya harus kamu kelola. Sepuluh ribu tab terbuka berarti sepuluh ribu socket yang harus dilacak oleh server, load balancer, dan firewall kamu, apa pun protokolnya, SSE atau WebSocket. Dua kebiasaan operasional penting untuk keduanya.
Yang pertama adalah heartbeat. Proxy dan load balancer menutup koneksi yang tampak menganggur, biasanya setelah 30 sampai 60 detik. Server SSE mengirim baris komentar seperti : ping setiap 15 sampai 30 detik agar jalurnya tetap hangat; WebSocket memakai frame ping/pong bawaannya untuk pekerjaan yang sama. Lewatkan ini dan fiturmu akan tampak bekerja di development lalu mati diam-diam di production.
Yang kedua adalah fan-out. Satu server bisa menyiarkan ke koneksi-koneksinya sendiri dari memori. Begitu kamu menjalankan dua server, event yang diterbitkan di server A harus mencapai klien yang terhubung ke server B, yang berarti kamu butuh jalur bersama — kebanyakan tim memakai publish/subscribe Redis, pola yang dibahas di panduan strategi caching Redis di blog ini. Long polling menghindari sebagian besar masalah ini karena setiap poll adalah request baru yang stateless dan bisa dijawab server mana pun. Koneksi tertahan juga menguntungkan server berbasis event: runtime async menahan ribuan socket menganggur dengan murah, sifat yang sama seperti yang dibahas di panduan asyncio Python, sementara stack thread-per-connection membayar satu thread untuk setiap pengguna yang menunggu.

Memilih untuk use case nyata
Chat dan kolaborasi: WebSocket
Chat itu simetris. Pengguna mengirim kira-kira sebanyak yang ia terima, dan fitur tambahannya — indikator mengetik, tanda terbaca, presence, posisi kursor dalam dokumen bersama — semuanya adalah pesan kecil yang beterbangan ke dua arah. Memaksakan semua itu ke SSE berarti satu stream untuk membaca plus satu request fetch untuk setiap sinyal sekelas ketikan. WebSocket memang dirancang untuk bentuk trafik seperti ini.
Notifikasi dan live feed: Server-Sent Events
Notifikasi, feed aktivitas, pelacak status pesanan, dan skor langsung berbagi satu bentuk: server yang tahu, pengguna yang menonton. Itu jalan satu arah, dan SSE menanganginya dengan kode dan infrastruktur yang lebih sedikit daripada WebSocket — tanpa upgrade handshake, tanpa logika reconnect buatan sendiri, dan event yang bisa diputar ulang setelah koneksi putus berkat ID event. Pola ini juga yang berada di balik banyak antarmuka "AI sedang mengetik", di mana token mengalir dari server ke browser dan satu-satunya tulisan pengguna adalah request awalnya.
Dashboard dan metrik: SSE, atau polling yang jujur
Dashboard yang diperbarui setiap detik atau lebih cepat memang tempatnya SSE, dengan alasan yang sama seperti feed. Tapi pelankan dan jujurlah soal datanya: kalau angkanya berubah setiap beberapa menit, poll 60 detik lebih sederhana, ramah cache, dan hemat anggaran koneksimu. Menahan sepuluh ribu koneksi terbuka untuk grafik yang berubah dua kali sejam adalah kemewahan yang tidak perlu.
Update jarang dan lingkungan terbatas: long polling
Long polling tetap menjadi jawaban yang tepat ketika update jarang terjadi tapi harus terasa sigap, ketika stack tidak bisa menahan respons streaming berumur panjang, atau ketika kliennya bukan browser sama sekali — sebuah skrip, perangkat, atau integrasi mitra yang hanya berbicara request–response. Ia juga fallback yang tetap bekerja melewati proxy perusahaan paling ketat, karena bagi jaringan ia tidak bisa dibedakan dari trafik web yang lambat.

Studi kasus singkat: satu produk, dua jawaban
Bayangkan sebuah platform dukungan pelanggan. Kotak masuk agen bersifat live ke dua arah: tiket baru datang, agen membalas, menugaskan, dan memberi catatan, dan semua orang melihat siapa yang sedang online. Separuh produk itu berjalan di atas WebSocket, dan tim membayar harga operasionalnya dengan senang hati — state koneksi, jalur Redis, protokol reconnect dengan pemutaran ulang pesan — karena fiturnya memang sebuah percakapan.
Halaman status publik perusahaan yang sama melayani ribuan pengunjung anonim saat terjadi insiden. Tidak ada apa pun yang mengalir dari pengunjung ke server; halaman hanya mengumumkan pembaruan. Separuh yang ini berjalan di atas SSE: satu stream per pengunjung, reconnect otomatis saat jaringan tersendat, dan pemutaran ulang untuk bagian yang terlewat. Pertanyaan penentunya tidak pernah soal benchmark. Pertanyaannya adalah arah (dua arah vs satu arah) dan audiens (belasan agen vs ribuan pengunjung). Perusahaan yang sama, minggu yang sama, dua jawaban benar yang berbeda — dipilih per fitur, bukan per stack.
Kesalahan umum
- Memakai WebSocket karena terasa modern. Data satu arah di atas WebSocket berarti kamu membangun reconnect, heartbeat, dan replay dengan tangan untuk fitur yang sebenarnya sudah ditangani SSE secara bawaan.
- Melupakan heartbeat. Stream bekerja di mesinmu, lalu mati setelah semenit di balik load balancer production. Timeout koneksi menganggur adalah perilaku default, bukan pengecualian.
- Membiarkan proxy men-buffer SSE. Dengan buffering respons menyala, event menumpuk di proxy dan keluar dalam semburan — atau tidak pernah keluar. Matikan buffering pada endpoint stream dan verifikasi lewat jalur production yang sebenarnya, bukan hanya localhost.
- Badai reconnect yang serempak. Setelah deploy atau gangguan, ribuan klien yang mencoba lagi pada interval tetap yang sama akan menghantam server yang sedang pulih. Gunakan backoff eksponensial dengan jitter acak.
- Menganggap socket adalah jaminan pengiriman. Pesan WebSocket yang dikirim saat ponsel berada di terowongan hilang begitu saja. Kalau kehilangan pesan itu penting, beri nomor pada pesanmu dan biarkan klien meminta pemutaran ulang — pola yang dibangun SSE lewat
Last-Event-ID. - Polling setiap detik "karena sederhana". Pada 10.000 pengguna yang terhubung, itu berarti 10.000 request per detik, yang sebagian besar menjawab "tidak ada perubahan". Kesederhanaan di endpoint bisa berubah menjadi kompleksitas di level armada server.
Best practice
- Putuskan arah dan frekuensi lebih dulu. Tulis seberapa sering data berubah dan siapa yang memulai. Sebagian besar perdebatan transport berhenti di situ.
- Beri setiap event sebuah ID, di transport mana pun. ID memungkinkan klien melanjutkan setelah putus, alih-alih mengambil ulang semuanya dari awal, dan membuat duplikat bisa terdeteksi.
- Pusatkan kebijakan reconnect. Satu implementasi backoff dengan jitter, dipakai bersama oleh semua fitur live, mengalahkan lima varian buatan tangan.
- Instrumentasikan jumlah koneksi dan laju pesan sejak hari pertama. Jumlah koneksi terbuka dan pesan per detik adalah dua angka yang meramalkan pekerjaan scaling kamu berbulan-bulan lebih awal.
- Sediakan fallback HTTP biasa. Jalur polling sederhana menyelamatkanmu pada hari ketika jaringan pelanggan memblokir semua cara lain, dan jalur itu juga berguna sebagai alat debug.
FAQ
Apakah WebSocket lebih cepat daripada Server-Sent Events?
Untuk satu pesan, hampir tidak. Begitu koneksi terbuka, keduanya mendorong data dengan latensi yang sebanding. Framing WebSocket yang lebih ramping dan dukungan binernya penting pada laju pesan tinggi — ratusan per detik — bukan pada beberapa notifikasi per menit.
Bisakah SSE mengirim data dari klien ke server?
Tidak lewat stream event-nya; SSE satu arah menurut desain. Klien mengirim data dengan request fetch biasa di samping stream. Sebagian besar fitur bergaya push membutuhkan trafik ke atas yang sangat sedikit, jadi pemisahan ini jarang jadi masalah.
Kenapa event SSE saya datang dalam semburan, atau tidak datang sama sekali, di production?
Biasanya proxy menahan (buffer) responsnya, atau middleware menahannya untuk kompresi. Matikan buffering pada endpoint stream (nginx menghormati header respons X-Accel-Buffering: no), flush setelah setiap event, dan kirim komentar heartbeat agar tidak ada pihak di jalur yang menganggap koneksi itu menganggur dan mati.
Haruskah seluruh aplikasi pindah ke WebSocket setelah saya menambahkan chat?
Tidak. Halaman, form, upload, dan bacaan yang bisa di-cache tetap di HTTP, tempat caching, CDN, dan semantik sederhana berada — urusan yang dibahas dalam panduan desain REST API. Simpan WebSocket untuk bagian aplikasi yang benar-benar bersifat percakapan.
Bagaimana dengan WebTransport dan HTTP/3?
WebTransport menambahkan stream dan datagram di atas HTTP/3 dan tampak menjanjikan untuk game dan media. Tapi dukungan browser dan perkakas operasionalnya masih berkembang, jadi WebSocket, SSE, dan long polling tetap merupakan transport yang benar-benar dijalankan sistem production saat ini.
Kesimpulan
Fitur real-time bukanlah satu masalah, dan tidak mempunyai satu jawaban. Long polling adalah fallback universal: stateless, tahan proxy, dan boros sebanding dengan seberapa live datamu. Server-Sent Events adalah spesialis stream satu arah, dengan reconnect dan replay yang ditangani browser. WebSocket adalah protokol percakapan serbaguna, bertenaga persis di tempat trafiknya dua arah dan menuntut di semua tempat lain.
Sebelum fitur live berikutnya, jawab dua pertanyaan dari flowchart tadi — ke arah mana, seberapa sering — dan biarkan jawabannya memilih transport-nya. Lalu beri ID pada event-eventmu, tambahkan heartbeat dan reconnect dengan jitter, dan fitur itu akan tetap terasa live saat penggunanya sepuluh ribu, bukan sepuluh.


