Real-time Dashboard — Realtime

Real-time Dashboard Dashboard yang update live: analytics, monitoring, trading, IoT. SSE atau WebSocket push updates, client render charts/metrics. SSE-based…

Dashboard realtime adalah layar angka yang ikut berubah begitu datanya berubah, tanpa di-refresh. Di meja kasir, Bu Rini punya dashboard: jumlah order per status, total kilo hari ini, dan omzet. Bayangin papan skor di pertandingan sepak bola. Petugasnya gak mengecat ulang seluruh papan setiap ada operan; ia cuma mengganti angka yang berubah. Penonton yang datang di babak kedua membaca papan dulu (snapshot), lalu mengikuti gol berikutnya (delta), dan menit pertandingan memberi tahu apakah ada yang terlewat (nomor urut).

🧪 Tebakan cepat: Pagi ramai: 40 perubahan datang dalam 2 detik. Kalau dashboard menggambar ulang di setiap pesan, ia menggambar 40 kali. Kalau pesannya digabung dan digambar paling banyak sekali per 250 ms, berapa kali? Jawabannya ada di bagian "Gabung dulu, gambar sekali".

Angka di dashboard dihitung dari daftar order

Dashboard menyimpan daftar order, lalu semua angkanya dihitung dari daftar itu. Tarifnya Rp 7.000 per kilo.

const STATUS = ['Antre', 'Dicuci', 'Disetrika', 'Siap diambil']

function hitung(orders) {
  const perStatus = Object.fromEntries(STATUS.map((s) => [s, 0]))
  let kg = 0
  for (const o of orders) {
    perStatus[o.status]++
    kg += o.kg
  }
  return { perStatus, kg, omzet: kg * 7000 }
}
Order Kg Status
#12 Pak Darto 3 Dicuci
#13 Mbak Wulan 2 Disetrika
#14 Mas Yoga 5 Siap diambil
#15 Bu Tini 4 Antre

Hasilnya: tiap status 1 order, 3 + 2 + 5 + 4 = 14 kg, omzet 14 × 7.000 = Rp 98.000. Angka dihitung ulang dari daftar, bukan ditambah-kurangi sendiri, jadi gak bisa melenceng.

Snapshot atau delta

Untuk grafik live, server yang mendorong data lewat SSE (cukup kalau satu arah) atau WebSocket, lalu frontend memperbarui grafiknya. Polling tiap detik membuang request walaupun gak ada yang berubah. Pertanyaannya: apa yang dikirim?

Di Lab, 40 perubahan pagi ramai (dari keadaan awal) = 1.856 byte sebagai delta, 21.387 byte sebagai snapshot. Delta membawa nilai baru (status: 'Dicuci'), bukan "+1 Dicuci, -1 Antre": delta yang sama diterima dua kali tetap menghasilkan angka yang benar.

// server realtime, namespace /staff
function ubahStatus(id, status) {
  seq += 1
  orders.get(id).status = status
  staf.emit('papan:delta', { seq, id, status })
}

Gabung dulu, gambar sekali

Menggambar ulang itu mahal (layout, chart, animasi), dan mata gak bisa membaca 20 perubahan per detik. Jadi pesan masuk ditampung, lalu digambar paling banyak sekali per 250 ms:

let tampung = []
let timer = null

staf.on('papan:delta', (d) => {
  tampung.push(d)
  if (timer) return                 // sudah ada jadwal gambar
  timer = setTimeout(() => {
    timer = null
    for (const x of tampung) terapkan(x)
    tampung = []
    gambar()                        // sekali untuk semua yang terkumpul
  }, 250)
})

Diukur di Chromium 147 dengan 40 pesan acak dalam 2.000 ms: gambar di setiap pesan = 40 kali, digabung per 250 ms = 7 kali. Itu jawaban tebakannya. 200 pesan dalam 1 detik: 200 kali lawan 4 kali. Gak ada pesan yang hilang; semuanya diterapkan, cuma digambar bersamaan. Server juga bisa menggabung sebelum mengirim (satu pesan berisi beberapa delta per 250 ms), supaya jumlah pesannya ikut turun.

Yang buka belakangan: snapshot, lalu delta bernomor

Pukul 10.00 Bu Rini membuka dashboard di HP-nya. HP butuh keadaan sekarang (snapshot) lalu perubahan sesudahnya (delta). Urutannya penting:

Waktu Snapshot dulu, baru subscribe Subscribe dulu, baru snapshot
0 ms GET /api/dashboard subscribe papan:delta, lalu GET /api/dashboard
150 ms Mas Eko: #15 Dicuci (seq 58), HP belum subscribe: terlewat delta 58 ditampung
300 ms snapshot seq 57 tiba, baru subscribe snapshot tiba (dibuat sesudah perubahan: seq 58), delta 58 dibuang karena sudah ada di dalamnya
900 ms delta seq 59 tiba: seq loncat dari 57 ke 59 delta 59 diterapkan
let orders = null      // null: snapshot belum ada
let seqTerakhir = 0
let antre = []

function terapkan(d) { // { seq, id, status } atau { seq, baru: order }
  if (d.baru) orders.push(d.baru)
  else orders.find((o) => o.id === d.id).status = d.status
  seqTerakhir = d.seq
}

staf.on('papan:delta', (d) => {
  if (!orders) return antre.push(d)                 // snapshot belum ada: tampung
  if (d.seq <= seqTerakhir) return                  // sudah termasuk di snapshot
  if (d.seq !== seqTerakhir + 1) return muatUlang() // ada yang terlewat
  terapkan(d)
  gambar()
})

async function muatUlang() {
  orders = null
  const snap = await fetch('/api/dashboard').then((r) => r.json()) // { seq, orders }
  orders = snap.orders
  seqTerakhir = snap.seq
  for (const d of antre.splice(0)) if (d.seq > seqTerakhir) terapkan(d)
  gambar()
}
muatUlang() // dipanggil sesudah listener dipasang

Grafik yang gak menggambar ulang semuanya

Delta #15 Antre ke Dicuci cuma mengubah dua batang. Simpan angka per status, dan biarkan batang yang angkanya sama gak ikut digambar ulang:

const Batang = memo(function Batang({ status, jumlah, maks }) {
  return <div className="batang" style={{ width: `${(jumlah / maks) * 100}%` }}>{status}: {jumlah}</div>
})

Dengan memo, React melewati batang yang props-nya gak berubah. Library grafik juga punya cara serupa: ubah datanya, lalu minta grafik memperbarui diri sekali per batch, bukan membuat grafik baru. Kalau tab sedang tersembunyi (document.visibilityState === 'hidden'), lewati menggambar dan gambar sekali saat tab terlihat lagi.

⚠️ Jebakan yang sering ketemu

TL;DR: Dashboard Bu Rini menghitung angkanya (order per status, total kilo, omzet = kilo × Rp 7.000) dari daftar order. Server mendorong data lewat SSE atau WebSocket, berupa delta kecil bernomor ({"seq":58,"id":15,"status":"Dicuci"}, 36 byte) alih-alih snapshot yang membesar (40 perubahan: 1.856 lawan 21.387 byte). Tampung pesan dan gambar paling banyak sekali per 250 ms (40 pesan dalam 2 detik: 40 render jadi 7). Yang buka belakangan subscribe dulu, menampung delta, mengambil snapshot ber-seq, membuang delta yang seq-nya sudah termasuk, dan mengambil snapshot lagi kalau seq loncat. Dengan memo, cuma batang yang angkanya berubah yang digambar ulang.

Yang akan kamu pelajari