Learn WebSocket - Message Queuing & Reliability
Episode 14 of 34

Learn WebSocket - Message Queuing & Reliability

This episode covers reliable message delivery: guarantees from at-most-once to exactly-once, the acknowledgement mechanism, offline message queues, persistence to databases and message brokers, and keeping messages ordered.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

WebSocket only guarantees that a message reaches the network, not that it reaches its destination. If the client is offline when the message is sent, or the server crashes after receiving it, the message is simply lost. For critical applications such as fund transfers or orders, that loss is unacceptable.

Episode 14 covers reliability: guaranteeing messages are not lost or duplicated, and queuing: storing messages while the receiver is not ready. You will learn the delivery guarantee models, acknowledgement, offline queues, and persistence.

Delivery Guarantees

Three Levels of Guarantee

Message systems generally offer three guarantees at different costs.

Delivery guarantee levels
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)

WebSocket's default is at-most-once: the message is sent once; if it fails, it is lost. For stronger guarantees, the application must add acknowledgement and retry on top of the protocol.

The Trade-Off of Each Level

  • At-most-once: fast and light, fits updates like mouse positions that may be lost.
  • At-least-once: retries until ack, but can produce duplicates that must be deduplicated.
  • Exactly-once: requires ID-based deduplication and persistence, expensive in complexity.

Choose the level that matches the consequences of losing a message in your application.

Acknowledgement

Ack and Timeout

The client tells the server the message was received by sending back an ack.

JSAck with a message ID on the server
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) stores the message before the ack is sent. If the server crashes after the ack, the message is already saved; if before the ack, the client resends and the server detects the duplicate via the ID.

Retry with Deduplication

The sender must retry when the ack does not arrive, and the receiver must reject duplicates.

JSResend until acknowledged
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) marks messages already confirmed by the receiver. The 3-second timer triggers a resend until the ack arrives, with an attempt limit to prevent endless sending.

Message Queuing

Offline Queues

When the receiver is offline, messages must be stored until they return.

JSOffline message queue
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 stores messages per user. When the user comes back online, the whole queue is sent and cleared.

Priority Queue

Not all messages are equally important. A priority queue sends important messages first.

JSQueue with priorities
const antrean = [];
 
function tambah(pesan, prioritas) {
  antrean.push({ pesan, prioritas });
  antrean.sort((a, b) => b.prioritas - a.prioritas);
}

antrean.sort(...) keeps the order by priority. System alerts are always picked up before promo notifications.

Persistence

Database and Message Broker

JSSaving a message to the database
const tersimpan = await db.pesan.create({
  data: {
    id: pesan.id,
    dari: pesan.dari,
    ke: pesan.ke,
    teks: pesan.teks,
  },
});

Storing in a database makes messages survive restarts. For high throughput, a message broker such as Redis or RabbitMQ handles queues with battle-tested delivery guarantees. The Redis list structure and Pub/Sub patterns will be covered more deeply in episode 16.

Keeping Messages Ordered

Sequence Numbers

The network does not guarantee messages arrive in order. Add a sequence number so the receiver can detect swapped or lost messages.

JSDetecting lost messages
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 signals that a message was missed or arrived out of order. This pattern is known as gap detection: the client requests a resync only from the missing number, then resumes processing. It saves bandwidth compared to resending the whole history.

Closing

Episode 14 equipped WebSocket with the reliability the protocol does not provide out of the box: a delivery guarantee chosen to fit the need, acks preventing loss, queues holding messages for offline receivers, and persistence making messages survive restarts.

Key takeaways:

  • WebSocket defaults to at-most-once; stronger guarantees require extra work.
  • Acknowledgement with IDs enables retry and deduplication.
  • Messages for offline users are queued and sent when they return online.
  • In-memory queues vanish on restart, so persist to a database or broker.
  • Sequence numbers detect lost or reordered messages.
  • Gap detection saves bandwidth by requesting only the missing part.

In the next episode we move into the scaling phase: load balancing WebSocket connections — sticky sessions, Layer 4 and Layer 7 load balancers, and health checks for zero-downtime deployments.

Learn WebSocket - Message Queuing & Reliability | Learn WebSocket