Episode ini membahas perlindungan server dari penyalahgunaan: algoritma rate limiting seperti token bucket dan sliding window, pembatasan per pengguna dan per koneksi, throttling di sisi klien, serta penanganan backpressure.

Satu klien nakal yang mengirim seribu pesan per detik bisa melumpuhkan server yang melayani ribuan pengguna normal. Rate limiting adalah pagar yang membatasi seberapa banyak permintaan yang boleh dilakukan dalam satu periode waktu.
Episode 13 membahas rate limiting: membatasi frekuensi aksi, dan throttling: memperlambat pengiriman data. Kalian akan mempelajari algoritma klasik seperti token bucket, implementasi di server Node.js, serta cara klien menghormati batas yang diberikan server.
Tanpa rate limiting, server rentan terhadap berbagai masalah:
const state = new Map();
function izinkan(userId, kapasitas = 10, perDetik = 1) {
const sekarang = Date.now() / 1000;
const s = state.get(userId) || { token: kapasitas, waktu: sekarang };
s.token = Math.min(
kapasitas,
s.token + (sekarang - s.waktu) * perDetik
);
s.waktu = sekarang;
if (s.token < 1) {
return false;
}
s.token -= 1;
state.set(userId, s);
return true;
}izinkan(userId) mengembalikan true jika token tersisa, false jika klien dibatasi. Token bertambah seiring waktu dengan laju perDetik dan dibatasi maksimal kapasitas.
Alternatif yang umum dipakai adalah window yang bergeser berdasarkan waktu.
bun add express-rate-limitLibrary express-rate-limit mengimplementasikan sliding window untuk REST. Untuk WebSocket, kalian mengaplikasikan logika yang sama di handler pesan, bukan di middleware HTTP.
Rate limit diterapkan di dua level: per koneksi dan per pengguna terautentikasi.
wss.on("connection", (ws) => {
ws.limit = { jumlah: 0, mulai: Date.now() };
ws.on("message", (data) => {
const kini = Date.now();
if (kini - ws.limit.mulai > 10000) {
ws.limit = { jumlah: 0, mulai: kini };
}
ws.limit.jumlah += 1;
if (ws.limit.jumlah > 30) {
ws.close(1008, "terlalu banyak pesan");
return;
}
prosesPesan(data);
});
});Window 10 detik dengan maksimal 30 pesan per koneksi di atas. Setelah lewat batas, ws.close(1008, "terlalu banyak pesan") memutus koneksi dengan alasan policy violation.
Saat server lebih dari satu, Map di memori tidak lagi cukup karena setiap instance punya hitungan sendiri. Simpan counter di Redis dengan TTL.
const redis = require("redis");
const client = redis.createClient();
async function batasi(userId) {
const kunci = "rl:" + userId;
const jumlah = await client.incr(kunci);
if (jumlah === 1) {
await client.expire(kunci, 10);
}
return jumlah > 30;
}client.incr(kunci) menaikkan counter atomik, dan client.expire(kunci, 10) membersihkannya setelah 10 detik. Semua instance server membaca counter yang sama, sehingga pembatasan konsisten lintas instance.
Server bukan satu-satunya tempat untuk memperlambat. Klien yang baik menahan input pengguna yang berlebihan.
let timer;
input.oninput = () => {
clearTimeout(timer);
timer = setTimeout(() => {
ws.send(JSON.stringify({ type: "ketik", teks: input.value }));
}, 300);
};clearTimeout(timer) membatalkan pengiriman sebelumnya, sehingga hanya pesan terakhir dalam 300 ms yang dikirim. Debounce sangat berguna untuk fitur seperti status mengetik dan pencarian live.
Saat offline, pesan yang dikirim harus diantrekan, tapi antrean tidak boleh tumbuh tanpa batas.
const antrean = [];
function kirim(pesan) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(pesan));
} else {
if (antrean.length >= 50) {
antrean.shift();
}
antrean.push(pesan);
}
}antrean.shift() membuang pesan paling tua saat antrean melebihi 50 item. Ini mencegah memori klien membengkak saat koneksi lama mati.
Server yang menerima data lebih cepat daripada memprosesnya membutuhkan backpressure. Library ws menyediakan bufferedAmount untuk mendeteksinya.
function kirimAman(ws, pesan) {
if (ws.bufferedAmount > 1024 * 1024) {
console.warn("antrean keluar penuh, menunda pengiriman");
return false;
}
ws.send(JSON.stringify(pesan));
return true;
}ws.bufferedAmount menunjukkan berapa byte yang belum terkirim. Jika melebihi ambang, kalian bisa menjatuhkan pesan non-kritis, memberi tahu klien, atau mengurangi laju produksi data.
Episode 13 memberi kalian perlindungan berlapis: server membatasi dengan algoritma yang adil, penyimpanan counter yang bekerja lintas instance, dan klien yang menahan diri dengan debounce dan antrean berbatas.
Inti yang harus dibawa pulang:
Di episode 14 berikutnya kita membahas message queuing & reliability: jaminan pengiriman, acknowledgement, antrean pesan, dan strategi menjaga urutan serta mencegah kehilangan data.