IP Messaging Protocols · Chapter 3 · 14 min
Delivery semantics and ordering
At-least-once delivery, idempotent clients and server-assigned sequence numbers.
The reality of delivery guarantees
In distributed systems, networks only provide two basic delivery guarantees: at-most-once (fire and forget) or at-least-once (retry until acknowledged). Achieving "exactly-once" delivery is actually a combination of at-least-once delivery and idempotent processing at the receiving end. Every message must carry a unique, client-generated ID that the server uses to deduplicate any redundant arrivals. This ensures that even if a message is sent multiple times due to a network glitch, it is only processed and stored once. Idempotency is the foundation of reliable distributed messaging and a core principle of our architecture.
Establishing an ordering authority
Because client clocks are notoriously unreliable and often disagree, they cannot be used to determine the definitive order of messages in a shared channel. Instead, the server must assign a monotonically increasing sequence number per channel at the exact moment a message is successfully written to the database. This sequence number becomes the absolute truth for message ordering across all clients and all sessions. Clients can then render messages optimistically and reconcile their local state when the official sequence number arrives from the server. This model provides a consistent and reliable view of the world for every participant.
−messages.sort((a, b) => a.sentAt - b.sentAt);render(messages);
interface Message {
clientId: string;
serverSeq: number;
text: string;
status: 'pending' | 'confirmed';
}
const onServerAck = (msg: Message, seq: number) => {
msg.serverSeq = seq;
msg.status = 'confirmed';
};Gap detection and synchronization
The use of dense, per-channel sequence numbers provides a powerful mechanism for detecting and recovering from missed messages. If a client receives a message with sequence 41 immediately after receiving sequence 38, it knows instantly that it has missed messages 39 and 40. Upon reconnection, the client simply asks the server for all messages after seq N, where N is the last sequence it received successfully. This efficient gap detection is a primary reason to prefer dense counters over timestamps or random UUIDs. Gap detection is essential for maintaining a coherent and complete conversation history.
Managing these gaps requires a robust synchronization protocol that can handle both small and large discrepancies. For small gaps, a simple request for missing messages is usually sufficient. However, for larger gaps, you might need to implement a more complex sync strategy that prioritizes the most recent information. By carefully designing your synchronization logic, you can ensure that your users always have a seamless experience, even when their network connection is unreliable.
Achieving eventual consistency
Ultimately, the goal of your delivery and ordering logic is to ensure that all members of a channel eventually see the same sequence of messages. By combining at-least-once delivery, server-side sequencing, and robust gap detection, you build a system that is both reliable and consistent. This architectural pattern is fundamental to the messaging experience we strive to build every day. A consistent experience is vital for user trust, system reliability, and the long-term viability of your messaging platform.
Discussion (3)
Ran the playground with three concurrent sends — receipts came back out of order, exactly as the text says. Great way to make it stick.
Good point — we're adding a short section on this in the next revision.
We learnt the idempotency lesson the expensive way: duplicate OTPs during an incident. This chapter is now required reading on my team.