Polling vs SSE vs WebSocket — Realtime

Perbandingan Teknologi Real-time Short Polling // Client request setiap N detik setInterval(async () => { const res = await fetch("/api/messages?since=" +…

Ada empat cara membuat papan Bu Rini tetap segar: short polling, long polling, SSE, dan WebSocket. Bedanya ada di siapa yang bicara, berapa koneksi yang dipakai, dan apa yang terjadi saat jaringan putus. Bayangin empat cara Bu Rini dapat kabar dari Mas Eko. Short polling: Bu Rini jalan ke belakang tiap 30 detik dan bertanya. Long polling: Bu Rini bertanya, lalu Mas Eko menahan jawabannya sampai ada yang berubah (atau 30 detik lewat), dan Bu Rini langsung bertanya lagi. SSE: Mas Eko punya pengeras suara ke depan, dia bisa bicara, Bu Rini cuma mendengar. WebSocket: telepon yang terus tersambung, dua-duanya bisa bicara kapan saja.

🧪 Tebakan cepat: Bu Rini membuka papan di 7 tab Chrome. Tiap tab memakai SSE ke server yang masih HTTP/1.1. Apa yang terjadi di tab ketujuh: tetap update, muncul error, atau macet menunggu? Jawabannya ada di bagian "Batas 6 koneksi per domain".

Short polling

setInterval(async () => {
  const res = await fetch('/papan')                              // tanya tiap 30 detik
  document.querySelector('#papan').innerHTML = await res.text()
}, 30000)

Kelemahan utamanya: boros request. Browser terus bertanya walaupun gak ada data baru. 120 request per jam per tab, dan kalau dalam sejam cuma ada 6 perubahan, 114 request pulang membawa papan yang sama. Tiap request juga membawa header lengkap: di pelajaran sebelumnya satu pertanyaan diukur 1.023 byte, padahal kabarnya cuma satu kata. Jedanya bisa dari hampir 0 sampai selebar interval, rata-rata setengahnya (15 detik).

Long polling

async function tungguKabar() {
  for (;;) {
    try {
      const res = await fetch('/papan/tunggu')          // server menahan jawaban sampai 30 detik
      if (res.status === 200) tampilkan(await res.text())
      // 204: 30 detik lewat tanpa kabar, langsung tanya lagi
    } catch {
      await new Promise((r) => setTimeout(r, 3000))    // jaringan putus: jeda dulu
    }
  }
}
tungguKabar()

Server-Sent Events (SSE)

const sumber = new EventSource('http://localhost:3000/papan/stream')

sumber.addEventListener('status', (event) => {
  const { id, status } = JSON.parse(event.data)
  document.querySelector(`#status-${id}`).textContent = status
})

Satu response HTTP yang gak pernah selesai: server terus menulis kabar ke dalamnya. Arahnya cuma dari server ke browser. Kalau putus, browser menyambung lagi sendiri (diukur: Chromium 147 dan Safari menunggu 3 detik, Firefox 146 menunggu 5 detik). Isinya teks. Detailnya di pelajaran Server-Sent Events.

WebSocket

const ws = new WebSocket('ws://localhost:3000/papan')

ws.onmessage = (event) => {
  const { id, status } = JSON.parse(event.data)
  document.querySelector(`#status-${id}`).textContent = status
}
tombolSetrika.onclick = () => ws.send(JSON.stringify({ type: 'status', id: 12, status: 'Disetrika' }))

Satu koneksi, dua arah: tablet Mas Eko bisa mengirim lewat koneksi yang sama dengan yang menerima. Tiap pesan dibungkus frame dengan header 2 sampai 14 byte, bukan header HTTP. Kalau putus, browser gak menyambung lagi sendiri: kamu yang menulis kodenya. Servernya harus bisa bicara protokol WebSocket. Detailnya di pelajaran WebSocket Fundamentals.

Dibandingkan dengan angka

Satu tab, satu jam, 6 perubahan status. Ukuran per request, per koneksi, dan per kabar diukur di Chromium 147 terhadap server lokal tanpa cookie (papan di port 8000, server realtime di port 3000). Lab di halaman ini menghitung ulang tabel ini untuk jumlah perubahan dan tab pilihanmu.

Short polling 30 dtk Long polling, tahan 30 dtk SSE WebSocket
Arah browser bertanya browser bertanya, server menahan server ke browser dua arah
Request per jam 120 120 1 1
Byte per jam 122.760 90.042 1.209 904
Rata-rata tunggu 15 dtk 1 perjalanan jaringan 1 perjalanan jaringan 1 perjalanan jaringan
Kalau putus request berikutnya jalan lagi loop-mu yang mengulang otomatis tulis sendiri
Slot koneksi HTTP/1.1 dipakai sebentar 1 slot terus 1 slot terus gak memakai slot

Batas 6 koneksi per domain

Di HTTP/1.1, browser cuma membuka 6 koneksi sekaligus ke satu domain, dan batas itu dibagi semua tab. SSE dan long polling memegang satu koneksi terus-menerus. Diuji di Chromium 147, Firefox 146, dan WebKit 26.4 (mesin Safari): dengan 6 stream SSE terbuka, request ketujuh ke domain yang sama masih menunggu setelah 2 detik. Jadi tab ketujuh macet menunggu, tanpa error, dan request biasa ke domain itu ikut antre. Itu jawaban tebakannya.

Di HTTP/2, semua request ke satu domain berbagi satu koneksi sebagai stream-stream terpisah. Batasnya dari server, biasanya 100 stream (nilai minimal yang disarankan RFC 9113). Jadi SSE di HTTP/2 aman untuk banyak tab. WebSocket gak memakai slot itu: di ketiga browser, 10 WebSocket terbuka sekaligus dan request biasa tetap lewat.

Proxy juga perlu diperhatikan. nginx secara bawaan menutup koneksi yang diam 60 detik (proxy_read_timeout) dan menampung response sebelum diteruskan, jadi SSE butuh header X-Accel-Buffering: no dan kabar kecil berkala supaya gak dianggap diam. WebSocket butuh proxy yang meneruskan header Upgrade dan Connection.

Satu lagi: WebTransport

WebTransport berjalan di atas HTTP/3 (QUIC). Dia membuka banyak stream sekaligus dan bisa mengirim datagram yang boleh hilang, cocok untuk game. Menurut data MDN browser-compat-data 8.1.5, dukungannya: Chrome dan Edge sejak versi 97, Firefox sejak 114, Safari sejak 26.4. Server dan library-nya masih sedikit, jadi jalur ini tetap memakai WebSocket.

Cara memilih

⚠️ Jebakan yang sering ketemu

TL;DR: Short polling bertanya tiap N detik: kelemahan utamanya boros request (120 per jam per tab, 122.760 byte, padahal cuma 6 yang membawa kabar) dan rata-rata telat setengah interval. Long polling menahan request sampai ada kabar: jedanya kecil, tapi tiap kabar tetap satu request HTTP lengkap (90.042 byte per jam). SSE adalah satu response yang terus mengalir dari server ke browser, dengan reconnect otomatis: pilih SSE kalau data cuma mengalir satu arah dari server ke client (1.209 byte per jam). WebSocket adalah satu koneksi dua arah dengan frame kecil, tanpa reconnect otomatis (904 byte per jam). Di HTTP/1.1 browser cuma membuka 6 koneksi per domain untuk semua tab, jadi SSE ketujuh macet. HTTP/2 menghapus batas itu. WebTransport (HTTP/3) sudah ada di Chrome 97, Firefox 114, dan Safari 26.4, tapi masih muda.

Yang akan kamu pelajari