Episode ini membahas keandalan pengiriman pesan: jaminan at-most-once hingga exactly-once, mekanisme acknowledgement, antrean pesan offline, persistence ke database dan message broker, serta menjaga urutan pesan.

WebSocket hanya menjamin pesan terkirim ke jaringan, bukan sampai ke tujuan. Jika klien offline saat pesan dikirim, atau server crash setelah menerima, pesan itu hilang begitu saja. Untuk aplikasi kritikal seperti transfer dana atau order, kehilangan itu tidak bisa diterima.
Episode 14 membahas reliability: jaminan pesan tidak hilang atau terduplikasi, dan queuing: menyimpan pesan saat penerima belum siap. Kalian akan mempelajari model jaminan pengiriman, acknowledgement, antrean offline, dan persistence.
Sistem pesan umumnya menawarkan tiga jaminan dengan biaya yang berbeda.
at-most-once : pesan mungkin hilang, tidak pernah duplikat
at-least-once : pesan pasti sampai, mungkin duplikat
exactly-once : pasti sampai dan tidak duplikat (paling mahal)Default WebSocket adalah at-most-once: pesan dikirim sekali, jika gagal maka hilang. Untuk jaminan lebih tinggi, aplikasi harus menambahkan acknowledgement dan retry di atas protokol.
Pilih level yang sesuai dengan konsekuensi kehilangan pesan di aplikasi kalian.
Klien memberi tahu server bahwa pesan sudah diterima dengan mengirim balasan ack.
wss.on("connection", (ws) => {
ws.on("message", (data) => {
const pesan = JSON.parse(data.toString());
simpanPesan(pesan.id, pesan);
ws.send(JSON.stringify({
type: "ack",
id: pesan.id,
}));
});
});simpanPesan(pesan.id, pesan) menyimpan pesan sebelum ack dikirim. Jika server crash setelah ack, pesan sudah tersimpan; jika sebelum ack, klien akan mengirim ulang dan server mendeteksi duplikat lewat ID.
Sisi pengirim harus mencoba ulang saat ack tidak datang, dan penerima harus menolak duplikat.
let idBerikutnya = 1;
const terack = new Set();
function kirimDenganAck(ws, pesan) {
const id = idBerikutnya++;
pesan.id = id;
ws.send(JSON.stringify(pesan));
setTimeout(() => {
if (!terack.has(id)) {
kirimDenganAck(ws, Object.assign({}, pesan));
}
}, 3000);
}terack.has(id) menandai pesan yang sudah dikonfirmasi penerima. Timer 3 detik memicu pengiriman ulang sampai ack tiba, dengan batas percobaan untuk mencegah pengiriman tanpa henti.
Saat penerima offline, pesan harus disimpan sampai dia kembali.
const antreanOffline = new Map();
socket.on("message:send", (pesan) => {
const tujuan = penggunaOnline(pesan.to);
if (tujuan) {
tujuan.emit("message:new", pesan);
} else {
const q = antreanOffline.get(pesan.to) || [];
q.push(pesan);
antreanOffline.set(pesan.to, q);
}
});
socket.on("presence:online", (userId) => {
const q = antreanOffline.get(userId) || [];
q.forEach((pesan) => socket.emit("message:new", pesan));
antreanOffline.delete(userId);
});antreanOffline menyimpan pesan per pengguna. Saat pengguna kembali online, seluruh antrean dikirim dan dibersihkan.
Tidak semua pesan sama pentingnya. Antrean prioritas mengirim pesan penting lebih dulu.
const antrean = [];
function tambah(pesan, prioritas) {
antrean.push({ pesan, prioritas });
antrean.sort((a, b) => b.prioritas - a.prioritas);
}antrean.sort(...) menjaga urutan berdasarkan prioritas. Peringatan sistem selalu diambil sebelum notifikasi promo.
const tersimpan = await db.pesan.create({
data: {
id: pesan.id,
dari: pesan.dari,
ke: pesan.ke,
teks: pesan.teks,
},
});Penyimpanan di database membuat pesan bertahan melewati restart. Untuk throughput tinggi, message broker seperti Redis atau RabbitMQ menangani antrean dengan jaminan pengiriman yang sudah teruji. Struktur list dan pola Pub/Sub Redis akan dibahas lebih dalam di episode 16.
Jaringan tidak menjamin pesan sampai berurutan. Tambahkan nomor urut agar penerima bisa mendeteksi pesan yang tertukar atau hilang.
let diharapkan = 1;
function proses(pesan) {
if (pesan.seq !== diharapkan) {
socket.emit("sync:request", { mulai: diharapkan });
return;
}
tampilkan(pesan);
diharapkan = pesan.seq + 1;
}pesan.seq !== diharapkan menandakan ada pesan yang terlewat atau di luar urutan. Pola ini dikenal sebagai gap detection: klien meminta sinkronisasi ulang hanya dari nomor yang hilang, lalu melanjutkan pemrosesan. Ini hemat bandwidth dibanding mengirim ulang seluruh history.
Episode 14 melengkapi WebSocket dengan keandalan yang tidak diberikan protokol secara bawaan: jaminan pengiriman dipilih sesuai kebutuhan, ack mencegah kehilangan, antrean menahan pesan untuk penerima yang offline, dan persistence membuat pesan bertahan dari restart.
Inti yang harus dibawa pulang:
Di episode 15 berikutnya kita masuk ke fase scaling: load balancing WebSocket connections — sticky sessions, load balancer Layer 4 dan Layer 7, serta health check untuk zero-downtime deployment.