In this lesson, you will learn how to design WebSocket event handlers that safely handle duplicate messages, ensuring your system state remains consistent even when retries occur.
The Problem with Retries
In distributed real-time systems, network jitters or acknowledgment failures often cause clients to resend events. If your event handler simply performs an operation like database.increment('balance', 10), a single user action accidentally sent twice will result in an incorrect balance. This violates the principle of idempotency: the property that performing an operation multiple times has the same effect as performing it once.
Designing Idempotent Handlers
To make an event handler idempotent, you must move away from "relative" operations (like increment) and move toward "deterministic" state transitions, typically by using a unique event ID sent by the client.
When an event arrives, check if you have already processed its unique identifier. You can store these IDs in a high-speed cache like Redis with a Time-to-Live (TTL) that matches your deduplication window.
async function handlePayment(event, socket) {
const { eventId, amount, userId } = event;
// 1. Check if the event ID has been processed
const alreadyProcessed = await redis.get(`event:${eventId}`);
if (alreadyProcessed) {
console.log(`Duplicate event ${eventId} ignored.`);
return;
}
// 2. Perform the business logic
await db.payments.create({ userId, amount, eventId });
// 3. Mark as processed with an expiration
await redis.set(`event:${eventId}`, 'true', 'EX', 3600);
socket.emit('payment_confirmed', { eventId });
}
Why This Works
By requiring a unique eventId for every significant state change, you treat the event as a signal of intent rather than a command to perform a raw calculation. Even if the network hangs and the client sends the same payload five times, your database will only record the transaction once because the cache blocks the subsequent attempts. This pattern is essential for reliable messaging over unstable WebSocket connections where strict ordering and delivery guarantees are difficult to enforce.
Key Takeaway: Never assume a message is unique; always implement deduplication by validating a unique event identifier against a fast, transient storage layer before executing state-changing logic.