Skip to content

← Writing

engineering

Consistency Models

· Jerwin Arnado · 6 min read ·

Part four of the series. CAP framed consistency as a single binary knob — you keep it or you trade it away. Reality has a dial with several stops in between, and the middle stops are where most real systems actually run. Knowing their names turns a class of baffling “the data was there a second ago” bugs into predictable, chooseable behavior.

The two ends of the dial

  • Strong consistency (linearizability): every read returns the most recent write, as if there were one copy of the data and one clock. The moment a write is acknowledged, everyone everywhere sees it. Simple to reason about, and the most expensive — it costs the latency PACELC warned about, because nodes must coordinate before confirming.
  • Eventual consistency: if writes stop, all replicas eventually converge to the same value. That’s the only promise. It says nothing about when, and nothing about what you see in the meantime — you might read older data than you just read a second ago. Cheap, available, and full of sharp edges if you assume more than it guarantees.

Between them sits a menu, and the useful insight is that “eventual” is a floor, not a description. You can — and usually should — ask for specific stronger guarantees that are still far cheaper than full linearizability.

The middle: the guarantees you actually want

These are the stops that matter for everyday apps, because they kill specific, common bugs:

  • Read-your-own-writes: after you write something, you will see it in subsequent reads (others might not yet). Kills the single most jarring bug in eventually-consistent systems: you post a comment, the page reloads from a lagging replica, and your own comment is gone. You refresh in a panic; it reappears. Maddening, and entirely avoidable.
  • Monotonic reads: once you’ve seen a value, you’ll never see an older one on a later read. Kills “time travel” — a list that shows 12 items, then 11, then 12 again as your reads bounce between replicas at different lag.
  • Consistent prefix: you see writes in the order they happened, never out of sequence. A reply never appears before the message it answers.

None of these require full strong consistency. They’re targeted promises you can often get cheaply, and each one maps to a real user-facing glitch you’re choosing to prevent or allow.

Where this bites: replication lag

For most of us, consistency models aren’t abstract — they show up as replication lag the day we add a read replica to take load off the primary. The classic pattern: write to the primary, read from a replica that’s a few hundred milliseconds behind.

// The read-your-writes bug, written innocently
$appointment = Appointment::create($validated);   // → primary
return redirect()->route('appointments.index');    // → reads from a LAGGING replica
// ...and the appointment the user just created isn't in the list yet.

The user booked an appointment and it’s “not there.” They book again. Now you have a double-booking created by your own consistency model. The fixes are exactly the middle-menu guarantees, made concrete:

// Option 1: read-your-writes by pinning THIS user's reads to the primary briefly
DB::connection()->sticky = true;   // Laravel: after a write, read from primary
                                   // for the rest of the request cycle

// Option 2: read the just-written record from the primary explicitly
$appointment = Appointment::on('primary')->find($id);

Laravel’s sticky option is read-your-writes in a config flag: for a connection that just wrote, subsequent reads in the same cycle go to the primary. That one line removes an entire category of “it vanished!” reports.

Choosing a model per feature

Like CAP, this is a per-feature decision driven by what a glitch costs:

Feature Model that fits Why
Account balance, booking a slot Strong Being wrong is unacceptable
Your own profile edits Read-your-writes You must see your own changes instantly
A paginated list, a counter Monotonic reads Never show fewer/older on refresh
A like count, “trending” Eventual Off-by-a-little for a moment harms nobody

The senior move is to notice that within one screen different data wants different models: the appointment you just booked needs read-your-writes; the “clinic activity” sidebar next to it is fine being eventually consistent. Dialing the whole app to “strong” to be safe buys latency you don’t need; dialing it all to “eventual” buys bugs you’ll spend weeks chasing.

Caveats and best practices

  • “Eventually consistent” is a floor, not a spec. It permits stale and out-of-order reads. If your code assumes read-your-writes and the store only promises eventual, you have a bug that appears only under lag — the worst kind to reproduce.
  • Reach for read-your-writes first. It’s the cheapest guarantee that removes the most user-visible weirdness, and in Laravel it’s often just sticky or an explicit primary read. Use it anywhere a user acts and then immediately looks at the result.
  • Replication lag is invisible until it isn’t. It’s zero on your laptop and 300ms under production load. Test the “create then immediately view” path against an actually-lagging replica, not a local single database.
  • Name the model in code review. “This reads from a replica — is read-your-writes required here?” is a one-line question that prevents a class of ghosts. Make it a habit.
  • Don’t confuse this C with ACID’s C. ACID consistency is about invariants within a transaction; this is about agreement across replicas. Same word, different chapter.

Conclusion

Ends → strong (always current, costly) ↔ eventual (converges someday, cheap)
Middle → read-your-writes, monotonic reads, consistent prefix (cheap, targeted)
Bug → write to primary, read from lagging replica = "it vanished"
Fix → pin post-write reads to primary (Laravel `sticky`); choose per feature

“Eventually consistent” hides a menu, and the middle options — especially read-your-writes — remove most of the glitches people blame on “weird caching” for the cost of a config flag. Pick the weakest model that keeps each feature’s glitches acceptable. Next: replication and quorums — the machinery underneath all of this, and how copying data is what creates the lag in the first place.