Skip to content

CONCEPT Cited by 1 source

Session-cookie read-your-writes window

Definition

A session-cookie read-your-writes window is the mechanism of using a short-lived per-user cookie (or equivalent session-state marker) to pin that user's reads to the primary database for Δ seconds after a write — ensuring the user sees their own just- committed data even though the global read pool is eventually consistent and would otherwise return pre-write state.

It is the concrete mechanism behind read-your-writes consistency when the underlying architecture is read-write split with replication lag between primary and replicas.

Canonical invocation

From Rails' ActiveRecord::Middleware::DatabaseSelector:

config.active_record.database_selector     = { delay: 2.seconds }
config.active_record.database_resolver     = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session

"This tells Rails to send all reads to our read-only region and writes to our primary. After each write, it will set a cookie that will send all reads to the primary for 2 seconds, allowing users to read their own writes." (Source: sources/2026-04-21-planetscale-introducing-planetscale-portals-read-only-regions.)

Mechanics

  1. On every write for session S, the middleware sets a cookie (or updates a session attribute) containing a timestamp t_written = now().
  2. On every read for session S, the middleware checks: if now() - t_written < Δ, route the read to primary; else route to the read replica pool.
  3. The cookie is per-user: other users continue to hit the replica pool normally; only the user who just wrote pays the in-region (if the replica was near them) or cross-region (if primary is far) RTT for the next Δ seconds.

Correctness condition

Δ > sup { lag(R) : R in replica pool }   (with high probability)

For the mechanism to provide RYW, the window Δ must exceed the longest replication lag the user might encounter. In practice:

  • Same-region replicas: Δ = 1–2 s is usually ample; healthy MySQL / Postgres replication lag is sub-second.
  • Cross-region replicas ( regional read replicas): Δ should cover cross-region p99 lag, which can be multiple seconds under load. The Portals post's 2 s default is optimistic for a globally-deployed app; 5–10 s is safer.

If Δ is too small, the user occasionally experiences ghost- reads: the write commits, the cookie expires before the replica catches up, the next read hits a still-stale replica, the write "disappears". If Δ is too large, the user reads from primary unnecessarily and loses the read-latency benefit of replicas for a small extra window.

A session cookie is the coarsest-possible RYW mechanism, and that's its selling point:

  • Zero plumbing cost. No LSN / GTID / $clusterTime token threaded through every ORM call, no per-query opaque handle. The middleware is ~20 lines of code in Rails; the cookie is set automatically; the routing decision is local.
  • Works with any replica topology. Doesn't care whether the replica pool is 2 same-region replicas or 5 regional replicas or a hybrid. The decision is "read from primary for Δ seconds" — independent of which replica would otherwise have served the read.
  • Correct in steady state. If replication is healthy and Δ is well-chosen, RYW holds.

Tradeoff vs. token-based schemes (MongoDB $clusterTime, anything using LSNs): the cookie is coarser — it pins all reads, not just reads that need to see the write. A user who writes then navigates to a read path that doesn't depend on the write still pays the primary-read tax for Δ seconds. For most interactive web apps, this is negligible.

Failure modes

  • Δ too small for p99 lag: classic "my edit disappeared" bug. Hard to reproduce because it only fires during replication stalls. Diagnostic: graph Δ against replica-lag p99; if they overlap, users will hit ghost-reads.
  • Cookie missing on some request paths: e.g. API requests from clients that don't send cookies, server-to-server internal traffic, agents. These paths always read from replica and bypass RYW — usually fine (the agent doesn't care about its own writes), sometimes a silent bug.
  • Multiple tabs / devices: the cookie is per-device-session. User writes on laptop, reads on phone — phone's session has no cookie, reads from replica, may see stale data. Usually acceptable: cross-device staleness is a UX tolerance most apps have.
  • Write rolled back, cookie still set: the middleware sets the cookie on write request, not write commit. A rolled-back write still pins reads to primary for Δ. Harmless (primary reads are always correct) but wastes replica capacity.

Framework analogs

  • Rails: ActiveRecord::Middleware::DatabaseSelector (above).
  • Django: DATABASE_ROUTERS — route decision is a method call; session cookie check is in the router.
  • Laravel: DB::connection('read') with a sticky-option that reads from write-connection for the rest of the request; Laravel's built-in sticky reads per-request rather than per-session, which is weaker (page reloads after a write go to replica; Rails' session cookie covers subsequent page reloads too).
  • Roll-your-own (plain mysql2 / pg): set a cookie on write, check the cookie in a query-router function, pick primary vs. replica pool accordingly.

Seen in

Last updated · 378 distilled / 1,213 read