Loading content...
You update your profile picture on a social network. You refresh the page—and your old picture stares back at you. You refresh again. Still old. One more refresh—there's your new picture! But then you navigate to a different page and see the old picture again. What happened?
This frustrating experience is a session guarantee violation. From the system's perspective, everything is working correctly—eventually consistent replicas will converge. But from your perspective as a user, the system is exhibiting bizarre, seemingly random behavior. Your own actions appear to be undone, time seems to flow backward, and you can't trust what you see.
Session guarantees formalize the idea that a user's interaction with a distributed system should be coherent from their own perspective, even if different users might see different states at the same time.
By the end of this page, you will understand the four fundamental session guarantees—Read Your Writes, Monotonic Reads, Monotonic Writes, and Writes Follow Reads. You'll learn how these guarantees are implemented, why they matter for user experience, and how they compose to create coherent sessions in distributed systems.
Before defining session guarantees, we must clarify what a session means in the context of distributed systems.
A session is a sequence of operations performed by a single client (or on behalf of a single user) that is treated as a logical unit. Within a session, there's an expectation of continuity—each operation builds upon the previous ones, and the client expects to see a coherent progression of state.
Sessions can be implemented in various ways:
Why sessions matter:
The key insight is that users don't think about distributed systems—they think about their experience. When you post a comment on a forum, you expect to see your comment. When you mark an email as read, you expect it to appear read. When you transfer money, you expect the balance to reflect the transfer.
Without session guarantees, distributed systems can provide globally correct behavior (eventual convergence) while still delivering individually incoherent experiences (your actions appearing to be ignored, undone, or applied out of order).
Session guarantees bridge this gap. They don't require global agreement—different users can see different states—but they ensure each individual user sees a coherent narrative of their own actions.
A session is essentially a causal chain—each operation in a session potentially depends on all previous operations in that session. The user's 'context' or 'state' carries information from past operations to future ones, creating happens-before relationships throughout the session.
The most fundamental session guarantee is Read Your Writes (RYW), sometimes called read-your-own-writes.
Formal definition:
If a session performs a write operation W on a data item x, any subsequent read operation R within the same session will observe either the effect of W or the effect of a later write to x.
In simpler terms: you will always see your own updates. Your writes never 'disappear' from your perspective, even temporarily.
Implementation strategies:
Write-through with sticky sessions: Route users to the replica they wrote to for subsequent reads. The replica always has its own writes.
Read-repair with write timestamps: Track the timestamp (or vector clock) of the session's latest write. Before returning a read, verify the serving replica has seen at least that timestamp.
Synchronous write confirmation: Only confirm a write after it's replicated to enough nodes that any subsequent read is guaranteed to see it.
Session-aware routing: Encode session state in the request, allowing any replica to determine whether it can safely serve the read or must forward to a more up-to-date replica.
1234567891011121314151617181920212223242526272829303132
// Conceptual implementation of Read Your Writesinterface SessionState { sessionId: string; // Vector clock tracking writes from this session lastWriteTimestamp: VectorClock;} async function read(key: string, session: SessionState): Promise<Value> { // Get value from local replica const localValue = await localReplica.get(key); const localTimestamp = await localReplica.getTimestamp(key); // Check if local replica has seen our latest write if (localTimestamp.happensBefore(session.lastWriteTimestamp)) { // Local replica is stale - it hasn't seen our writes yet // Option 1: Wait for replication to catch up // Option 2: Forward to a replica that has our writes // Option 3: Read from the replica we wrote to return await readFromUpToDateReplica(key, session.lastWriteTimestamp); } // Local replica has seen our writes, safe to return return localValue;} async function write(key: string, value: Value, session: SessionState): Promise<void> { // Perform the write const newTimestamp = await localReplica.set(key, value); // Update session state so future reads know about this write session.lastWriteTimestamp = session.lastWriteTimestamp.merge(newTimestamp);}Read Your Writes only guarantees that YOU see your writes. Other users might not see your writes yet—that's perfectly fine under RYW. The guarantee is about individual session coherence, not global consistency.
Monotonic Reads ensures that time doesn't appear to flow backward for a client's observations.
Formal definition:
If a session reads a data item x and observes value v, any subsequent read of x in the same session will return v or a value that was written after v was written.
In simpler terms: once you've seen something, you can't 'unsee' it. State only moves forward, never backward.
The problem Monotonic Reads solves:
Imagine reading an email inbox. You see 10 emails. You refresh—now you see only 8 emails. Did 2 emails get deleted? No, you just got routed to a replica that hasn't received those 2 emails yet. From your perspective, emails have mysteriously vanished.
Or consider a bank account. You check your balance: $1000. You refresh: $500. You panic—where did $500 go? Actually, nothing happened; you just read from a stale replica that hasn't seen your latest deposit.
These experiences are disorienting and destroy user trust. Monotonic Reads prevents them.
Session Timeline WITHOUT Monotonic Reads: Time Operation Replica Resultt1 read(balance) A $1000 (Replica A has latest)t2 read(balance) B $500 (Replica B is stale!)t3 read(balance) A $1000 (Back to Replica A) User sees: $1000 → $500 → $1000User thinks: "The system is broken!" --- Session Timeline WITH Monotonic Reads: Time Operation Replica Resultt1 read(balance) A $1000 (Mark session: seen timestamp T1)t2 read(balance) B WAIT (B is behind T1, can't serve)t2' read(balance) A $1000 (Routed to A which has T1)t3 read(balance) B $1000 (B now has T1, can serve) User sees: $1000 → $1000 → $1000User thinks: "System is working correctly"Implementation strategies:
Read timestamp tracking: After each read, record the timestamp of what was read. Subsequent reads must come from replicas at or past that timestamp.
Sticky sessions: Always route the session to the same replica. Since replicas never 'forget' data they've received, monotonicity is automatic.
Session version vectors: Carry a vector clock in the session that represents 'the state this session has observed'. Only replicas that have reached that state can serve reads.
Read quorum with timestamp constraints: Read from a quorum that includes at least one replica with state >= the session's last observed timestamp.
Monotonic Reads creates an illusion of 'forward time' for the client. Distributed systems have no true global clock, but from any individual session's perspective, the system appears to progress forward. This is sufficient for intuitive user experiences.
Monotonic Writes ensures that writes from a session are applied in the order the session issued them.
Formal definition:
If a session performs write W1 before write W2, then all replicas will apply W1 before W2. No replica will have W2 without also having W1, and no replica will apply them in the opposite order.
In simpler terms: your writes happen in order. The sequence of your operations is preserved across all replicas.
The problem Monotonic Writes solves:
Consider incrementing a counter:
Without Monotonic Writes, replication might deliver these out of order. Replica A might receive 0, then 2, then 1—ending up with counter = 1 instead of 2. Or worse, different replicas might end up with different final values depending on the order they received the writes.
Monotonic Writes ensures that wherever these writes land, they land in 0 → 1 → 2 order.
Session Write Sequence: W1: Set name = "Alice" (at time t1) W2: Set name = "Alice Smith" (at time t2) W3: Set name = "Alice J. Smith" (at time t3) WITHOUT Monotonic Writes (possible replication orders): Replica A receives: W1 → W3 → W2, final value = "Alice Smith" Replica B receives: W2 → W1 → W3, final value = "Alice J. Smith" Replica C receives: W3 → W2 → W1, final value = "Alice" Different replicas have different values! Last-write-wins based on arrival order, not issue order. WITH Monotonic Writes (guaranteed order): All replicas receive: W1 → W2 → W3 All replicas have final value = "Alice J. Smith" Consistency preserved because session order is preserved.Implementation strategies:
Write serialization: Force all writes from a session to flow through a single coordinator that serializes them before replication.
Causal barriers: Each write carries a dependency on the session's previous write. Replicas refuse to apply a write until its dependency is satisfied.
Session sequence numbers: Assign monotonically increasing sequence numbers to writes within a session. Replicas buffer out-of-order writes and apply them only when all previous sequences have been applied.
Write-acknowledgment chaining: Don't acknowledge write N until write N-1 has been fully replicated. This prevents the client from issuing further writes until ordering is assured.
Monotonic Writes is especially important when writes to the same key logically depend on previous writes. If you're updating a document in stages (draft → review → published), MW ensures every replica sees this progression in order.
Writes Follow Reads (WFR) is the most sophisticated session guarantee, capturing the causal relationship between reading and writing.
Formal definition:
If a session reads a data item x and then writes a data item y, the write to y will be causally ordered after the read from x. Any replica that has the write to y must also have the state of x that was read (or a later state).
In simpler terms: your writes are causally connected to what you've seen. A write 'knows about' all the reads that came before it in your session.
The problem Writes Follow Reads solves:
Consider a comment system:
Without WFR, your comment might replicate to servers that haven't yet received the blog post. Users on those servers see your comment but not the post—they have no idea what topic you're calling 'great'.
WFR ensures that any replica showing your comment will also have the blog post you commented on. The causal chain is preserved: post exists → you read post → you write comment → others see post with comment.
123456789101112131415161718192021222324252627282930
// Example: Social media response system // Step 1: User reads the original postconst originalPost = await read("posts/123");// Session now tracks: "I've seen posts/123 at version V5" // Step 2: User writes a replyawait write("replies/456", { content: "This is so insightful!", replyTo: "posts/123"});// Write carries dependency: "This write requires posts/123 >= V5" // Step 3: At any replica receiving the replyfunction applyWrite(write: Write) { // Check causal dependencies for (const dep of write.causalDependencies) { if (!this.hasVersion(dep.key, dep.minVersion)) { // Can't apply yet - missing a causal dependency this.bufferWrite(write); return; } } // All dependencies satisfied, safe to apply this.apply(write);} // Result: No user ever sees a reply without its parent post// The "replyTo" reference is always resolvable| Scenario | Read Dependency | Write Action | WFR Guarantee |
|---|---|---|---|
| Reply to post | Original post at V3 | Create reply | Reply only visible where post V3+ exists |
| Edit document | Document at revision 42 | Submit revision 43 | Revision 43 only applied after 42 |
| Transfer money | Account balance = $100 | Debit $50 | Debit only applied where balance >= $100 |
| Update derived data | Read source record R1 | Write derived D1 | D1 only visible where R1 exists |
Writes Follow Reads is arguably the most important session guarantee because it captures the fundamental cause-effect relationship in applications: you observe something, make a decision based on that observation, and act. WFR ensures your action stays connected to what you observed.
The four session guarantees are not mutually exclusive—they can be combined to provide stronger overall consistency. Different combinations are useful for different application requirements.
| Combination | Provides | Implementation Cost | Use Case |
|---|---|---|---|
| RYW alone | See your own writes | Low (sticky sessions) | Simple applications, user profiles |
| RYW + MR | Writes visible, time moves forward | Medium | Email, notifications, feeds |
| RYW + MR + MW | Full session coherence for user writes | Medium-High | Document editing, messaging |
| All four (Causal) | Complete causal consistency | High | Collaborative systems, distributed databases |
The relationship between session guarantees and causal consistency:
When all four session guarantees are provided simultaneously, the result is equivalent to causal consistency for single-session scenarios. The guarantees compose as follows:
Together, they ensure that a session's operations form a consistent causal narrative that any replica can reconstruct.
Full Causal Session = RYW + MR + MW + WFR Consider this session: 1. Read(x) → 5 2. Read(y) → 10 3. Write(z, x + y = 15) 4. Read(z) → expect 15 Guarantees in action: - WFR: Write to z 'sees' reads of x=5 and y=10 - MW: If another write followed, it would be ordered after z=15 - RYW: Read of z in step 4 returns 15 (our write) - MR: Any subsequent read of z returns 15 or later values Any replica applying this session's operations will maintainthese relationships. The session forms a coherent causal chainthat replicas process consistently.Providing all four guarantees has costs—primarily in terms of metadata overhead and coordination requirements. Many applications only need RYW (the most commonly violated guarantee in eventually consistent systems). Choose the minimal set of guarantees that meets your requirements.
There are several architectural patterns for implementing session guarantees in distributed systems. Each has different trade-offs in terms of complexity, performance, and resilience.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// Session token pattern for session guaranteesinterface SessionToken { sessionId: string; // For RYW: tracks writes from this session writtenKeys: Map<string, VectorClock>; // For MR: tracks reads from this session readHorizon: VectorClock; // For MW: ensures write ordering lastWriteSequence: number;} class SessionAwareReplica { async read(key: string, token: SessionToken): Promise<ReadResult> { const localVersion = this.getVersion(key); // Check RYW: have we seen this session's writes to this key? const sessionWriteVersion = token.writtenKeys.get(key); if (sessionWriteVersion && localVersion.happensBefore(sessionWriteVersion)) { throw new StaleReplicaError("Missing session's writes"); } // Check MR: is our version at least as recent as what session has seen? if (localVersion.happensBefore(token.readHorizon)) { throw new StaleReplicaError("Would violate monotonic reads"); } const value = this.getValue(key); // Update session's read horizon token.readHorizon = token.readHorizon.merge(localVersion); return { value, version: localVersion }; } async write(key: string, value: any, token: SessionToken): Promise<void> { // WFR: capture causal dependencies from reads const dependencies = token.readHorizon.clone(); // MW: ensure ordering with previous writes const writeSeq = ++token.lastWriteSequence; const writeVersion = this.performWrite(key, value, { dependencies, sessionId: token.sessionId, sequence: writeSeq }); // Update session state for RYW token.writtenKeys.set(key, writeVersion); token.readHorizon = token.readHorizon.merge(writeVersion); }}If session state is lost (client crashes, token expires, session store fails), the system cannot guarantee continuity. This is acceptable in many applications (user refreshes, sees brief stale data, then it catches up). For critical applications, session state must be persisted redundantly.
We've explored the four fundamental session guarantees that emerge from and support causal consistency. Let's consolidate:
What's next:
Understanding session guarantees tells us what properties we want. The next page explores implementation approaches—the mechanisms and data structures (like vector clocks, Lamport timestamps, and hybrid logical clocks) that actually deliver these guarantees in distributed systems.
You now understand the four session guarantees and how they compose to create coherent user experiences in distributed systems. These guarantees are the practical manifestation of causal consistency—ensuring that distributed systems behave intuitively for individual users, even without global coordination.