Module 7: Reliability and the Consistency Tradeoff

6. Strong versus eventual consistency, and Enlace's choice

Description

By the end of this lesson you will precisely distinguish strong consistency from eventual, you will meet the spectrum of intermediate guarantees between the two, and you will measure —by running the calculation— why Enlace gets eventual consistency for resolve. Strong consistency (linearizability) means that every read sees the most recent write, as if there were a single copy always up to date; eventual consistency means that reads may be a bit behind, but if you stop writing, all the copies converge to the same value. Between the two there is a useful spectrum —read-your-writes (you see your own writes), monotonic reads (you do not see time go backward)— that connects directly with the replication lag you learned in module 5. You will bring everything down to the case: you will compute how many short_codes are "in flight" at any instant, how improbable it is that a read hits a freshly created one, and why the only uncomfortable case —the creator who resolves their own link before it propagates— is solved by reading from the primary. And you will see where Enlace does need something stronger: the uniqueness of the short_code when generating it, not when resolving it.

This matters because "eventual consistency" is a phrase people use as if it were a synonym for "unreliable data", when in reality it is an engineering decision with a concrete benefit (low latency, high availability) and a bounded cost (a lag window you can compute and control). The mark of a good systems designer is not always choosing strong consistency "to be safe" —that is expensive and often unnecessary—, but knowing when eventual is enough and how much lag it really implies. This lesson teaches you to do that math: not to assert "eventual is fine for redirects" from memory, but to prove it with Enlace's numbers, which is exactly what separates an opinion from a defensible decision.

Connection to the module: this lesson is the landing of the two previous ones. In lesson 4 you decided that Enlace is AP (available under partition); in lesson 5, that it is EL (low latency without a partition); the two branches together —PA/EL— have a name in practice, and it is eventual consistency, which this lesson formalizes and measures. It also closes the loop with module 5: the replication lag that there was "an uncomfortable detail of the replicas" here reveals itself as the exact mechanism of eventual consistency —the lag between the primary and the replicas is the eventuality window—. The border holds: here we decide and measure the model; how an intermediate guarantee like read-your-writes is implemented (sessions pinned to the primary, read versioning) brushes against the resilience guide, and taking the reconciliation of divergent replicas to the extreme (event sourcing, CRDTs) is the events guide. We choose the model and compute its cost.

The shared document versus email

Think of it this way. There are two ways information reaches several people from one person, and they are the two extremes of consistency.

The first is a live shared document —the kind where several people edit at once and all see every keystroke instantly—. When someone types a word, at that moment everyone else sees it; there is no way for two people to see different versions of the document at the same time. It is as if there were a single copy of the document and everyone looked at that single copy. That is strong consistency (linearizability): the system behaves as if there were a single datum always up to date, no matter how many copies there are underneath. The price, which you already saw in lesson 5, is coordination: for everyone to see every keystroke instantly, the system has to synchronize on every change, and that costs latency.

The second is an email you send to a group. When you send it, it does not reach everyone at the same instant: it reaches the first in a second, the second in three, someone with a bad connection in a minute. During that little while, some people have already read the news and others have not yet —the group is temporarily out of date—. But there is a guarantee: if you stop sending emails, sooner or later everyone will have received the same message. The group converges. That is eventual consistency: the copies may be behind for a while, but if the flow of writes stops, they all end up the same. The prize is that sending the email is instant —you do not wait for everyone to confirm the read—, and that is why it is fast and available.

The lesson I want you to take is this: neither is "better" in the abstract; they are two tools for two needs. You want the live document (strong) when a momentary discrepancy would be a disaster —a bank balance, an inventory—. You want the email (eventual) when a lag of seconds does no harm to anyone and what you value is speed and that the "mailbox" never "goes down" —a redirect, a likes counter, a feed—. The task of this lesson is to prove, with numbers, that Enlace clearly lives in the second case.

The precise definitions and the intermediate spectrum

Let us sharpen the two definitions and see that they are not a two-position switch, but the extremes of a spectrum.

Strong consistency (linearizability). There is a single, global order of all operations, and every read returns the result of the most recent write in that order. Operationally: if the write of a value finished (the client received "done"), any subsequent read, on any replica, has to return that value or a newer one —never an old one—. It is the perfect illusion of a single copy.

Eventual consistency. If you stop writing a datum, all the replicas end up returning the same value (they converge), but in the meantime different replicas may return different values —some up to date, others behind—. There is no guarantee of when they converge (hence "eventual"), although in practice it is a matter of milliseconds to seconds: it is exactly the replication lag of module 5.

Between those two extremes there are intermediate guarantees that solve the uncomfortable cases of eventual without paying the full cost of strong. The two most useful:

  • Read-your-writes. Guarantees that whoever just wrote will see their own write in subsequent reads —even if others still see the old value—. It solves the most annoying case of eventual: creating something and having it "not appear" on reload. It is implemented, for example, by sending the reads of a user who just wrote to the primary (which is always up to date) for a little while.
  • Monotonic reads. Guarantees that, once you have seen a new value, you will not see an older one again —time does not go backward for you—. Without this guarantee, reloading the page could show you first the new datum (from an up-to-date replica) and then the old one (from a replica that is behind), which is disconcerting. It is implemented by pinning each user to the same replica.
   STRONG ◄──────────────────── spectrum ────────────────────► EVENTUAL
      │              │                    │                        │
 linearizable   read-your-writes    monotonic reads         pure eventual
 (everyone sees  (you see your        (never go backward)    (converges someday)
  the last one)   own writes)
      │                                                            │
 more coordination, more latency         ◄────►      less coordination, less latency
 less available under partition                      more available under partition

The point of the spectrum: you do not have to choose between "super expensive strong" and "wild eventual". You can take eventual (fast, available) and add only the guarantee you need for the concrete uncomfortable case —usually read-your-writes—, without paying the full linearizability. Enlace will do exactly that, as we will see.

Worked example: why eventual is enough for resolve

Here is the numeric proof, which is the heart of the lesson. The question is concrete: if Enlace uses eventual consistency —the replicas are a bit behind the primary—, how much harm does it really do? We are going to model it with the anchor numbers.

The risk scenario is this: someone creates a new short_code (a write to the primary), and in the seconds it takes to propagate to the replicas, a resolve of that same code reaches a replica that does not yet have it → it returns 404, as if the link did not exist. How probable is that?

# Enlace's inconsistency window, with the anchor numbers
qps_write = 40          # ~40 writes/s (module 2)
qps_read  = 4000        # ~4,000 reads/s
lag_s     = 2.0         # worst-case replication lag, in seconds

# short_codes "in flight": created but not yet propagated to all replicas
in_flight = qps_write * lag_s
print(f"short_codes in flight at any instant = {qps_write} w/s x {lag_s} s = {in_flight:.0f}")

# The code space is base62^7 (from module 3). A RANDOM read
# has a minuscule probability of hitting one of the ~80 in flight:
code_space = 62 ** 7
p_hit = in_flight / code_space
print(f"code space base62^7 = {code_space:,}")
print(f"P(a random read hits a code in flight) = {p_hit:.2e}")

reads_in_window = qps_read * lag_s
print(f"total reads in a window of {lag_s}s = {reads_in_window:.0f}")

What to expect. When you run it:

short_codes in flight at any instant = 80
code space base62^7 = 3,521,614,606,208
P(a random read hits a code in flight) = 2.27e-11
total reads in a window of 2.0s = 8000

Read those numbers slowly, because they tell the whole story. At any instant there are only ~80 short_codes "in flight" —created but not yet on all the replicas—. The code space is 3.5 trillion. The probability that a random read hits exactly one of those 80 is 2 in a hundred billion —negligible—.

But the decisive argument is even stronger, and it is not probabilistic: Enlace's reads are not random. A freshly created short_code nobody knows yet, except the person who just created it. For someone else to resolve it, the creator has to have shared it —by chat, by email, by publishing it—, and that takes considerably more than the 2 seconds of the propagation window. So, in practice, the only read that can see the 404 is the creator's own, if they resolve their link in the first seconds of its life. Everyone else arrives when the code has already propagated a while ago.

And that single uncomfortable case has a cheap, well-known cure: read-your-writes. After a user does shorten, their next reads —for a little while— are served from the primary (which is always up to date) instead of a replica. The creator always sees their own link instantly; the rest of the traffic keeps reading from the nearby replicas, fast and available. You do not pay strong consistency for anyone; you put the exact guarantee on the only one who needed it. That is the design: eventual consistency for resolve, with read-your-writes for the creator.

Where Enlace DOES need something stronger

It would be dishonest to say "Enlace is eventual and that is it"; there is a point where eventuality is not enough, and recognizing it is part of mature design. That point is the uniqueness of the short_code when generating it.

Remember from module 3 that Enlace generates each short_code so that it is unique —two different URLs must never receive the same code, because that corrupts the system (a resolve would return the wrong URL)—. If that generation depended on eventual coordination between nodes, two shorten requests on different nodes, almost at the same time, could assign the same code before "finding out" about each other. That is a corrupt collision, and here eventual consistency is not acceptable: you cannot "converge later" when you already handed the same code to two different URLs.

But —and this is the elegant part— the solution is not to make all of Enlace strongly consistent. It is to avoid the need for coordination at the moment of writing, distributing the work in advance. The id_generator of module 3 does exactly that: it gives each node a disjoint range of numbers (node A uses 1–1,000,000, node B uses 1,000,001–2,000,000, etc.), so that two nodes can never generate the same code, without needing to consult each other on every write. Uniqueness is guaranteed by construction, not by hot coordination. That way, Enlace gets a strong guarantee where it needs it (uniqueness) without paying the cost of synchronous coordination on the hot path.

The design lesson, which holds for any system: do not classify the entire system with a single consistency model. Enlace is eventual on the read (resolve), read-your-writes for the creator, and strong-by-construction on the uniqueness of the write. Each operation gets the guarantee its cost-benefit justifies, no more and no less. Choosing "all strong to be safe" would have made Enlace slow and fragile under partition with no need whatsoever; choosing "all eventual" would have opened the door to collisions. The correct design is granular.

Common mistakes

Believing that "eventual" means "unreliable" (conceptual). What happens: someone dismisses eventual consistency out of fear, assuming that "the data can be wrong", and chooses strong consistency by default, paying latency and availability needlessly. Why it happens: "eventual" sounds like "maybe never". How to spot it: if you choose strong "to be safe" without having computed the real lag window (here: ~80 codes in flight, probability 2e-11), you are deciding out of fear, not analysis. How to fix it: eventual guarantees convergence; its only concession is a temporary lag that is bounded and computable. Do the math —like this lesson's— and decide with the number.

Forgetting the "read your own write" case (of design). What happens: eventual consistency is chosen for the whole system and the case of the creator who resolves their freshly created link and sees a 404 is forgotten, generating reports of "the system lost my link". Why it happens: the case is rare in volume but very visible to whoever suffers it (exactly the user who just acted). How to spot it: if after a write the author themselves can read an old value and that confuses, you are missing read-your-writes. How to fix it: add the exact intermediate guarantee —serve the author's reads from the primary for a little while— without raising the whole system to strong consistency.

Applying a single consistency model to the whole system (of granularity). What happens: someone declares "Enlace is eventual" and applies eventual also to the generation of the short_code, opening the door to collisions; or the reverse, declares "Enlace is strong" and makes resolve slow needlessly. Why it happens: a single, simple label is sought for the whole system. How to spot it: if different operations have different cost of incoherence (an old redirect is harmless; a duplicated code is corruption) but you apply the same model to them, you are miscalibrated. How to fix it: choose the model per operation: eventual for resolve, read-your-writes for the creator, strong-by-construction for uniqueness.

Exercises

Exercise 1 — Strong, eventual or intermediate. For each Enlace requirement, say which consistency model corresponds to it and why. (a) Any user resolves a link created a month ago. (b) The creator of a link resolves it 1 second after creating it. (c) Two nodes generate short_codes for two different URLs in the same millisecond.

See solution
  • (a) Eventual consistency (is enough). A month-old link propagated to all the replicas ages ago; any replica has it. The read is fast, available and correct. This is 99.99% of the resolve traffic, and eventual is more than enough.
  • (b) Read-your-writes. The creator could hit a replica that has not yet received their code (within the ~2 s lag window) and see a 404. The exact guarantee is to serve their reads from the primary for a little while, so that they always see their own link. There is no need for strong consistency for everyone, just this guarantee for the author.
  • (c) Strong / unique by construction. Here eventual does NOT suffice: if the two nodes assign the same code, it is irreversible corruption. It is solved by guaranteeing uniqueness by design —disjoint ranges per node (the id_generator of module 3)— so that two nodes can never collide, without coordinating on the hot path.

Exercise 2 — Run the window. Suppose you improve the infrastructure and the worst-case replication lag drops from 2 s to 0.5 s, while writes rise to 60/s at a peak. (a) How many short_codes remain "in flight"? (b) Does the probability that a random read hits one rise or fall relative to the base case (80 in flight)? (c) Does the decisive argument for why eventual is enough change?

See solution
  • (a) in_flight = 60 w/s × 0.5 s = 30 codes in flight. Fewer than the 80 of the base case, because the shorter lag weighs more than the write peak.
  • (b) It falls: 30 / 62^7 ≈ 8.5e-12, even lower than the 2.27e-11 of the base case. Fewer codes in flight → lower probability of hitting one.
  • (c) No, the decisive argument does not change and does not even depend on these numbers: it is still that a freshly created code nobody else knows yet, so the only read that can see it stale is the creator's, solved with read-your-writes. The random probability only confirms how marginal the rest is; the real argument is about who knows the code, not about chance.

Exercise 3 — Design the consistency of a new feature. Enlace adds click analytics: each resolve increments a clicks counter in the Link record. A user sees the total number of clicks of their link on a dashboard. Decide the consistency model for (a) the increment of the counter on each click and (b) the number the user sees on their dashboard, and justify why eventual is enough (or not) for each one.

See solution
  • (a) The counter increment: eventual, and it is actually preferable that it be so. At 4,000 clicks/s, coordinating each increment in a strongly consistent way would be super expensive and a bottleneck. Counters perfectly tolerate eventual: they can be accumulated per node and summed later, or incremented approximately. That the counter is a few seconds behind does no harm to anyone —it is the archetypal case of data tolerant to lag, like the "like" of lesson 4—.
  • (b) The dashboard number: eventual, with explicit tolerance. The user does not need to see the count exact to the millisecond; seeing "1,024 clicks" when the real number is 1,027 is perfectly acceptable for an analytics dashboard. In fact, promising an exact number in real time would be expensive and fragile with no benefit. Eventual is more than enough; if anything, show "updated a few seconds ago" to be honest about the lag.

Moral: click analytics is, in both parts, one more case for eventual consistency —just like resolve—. Enlace is, almost entirely, an eventual system, and only the uniqueness of generation asks for a stronger guarantee.

Summary and next step

In this lesson you landed the consistency tradeoff into a measured decision. With the live document (strong) versus the group email (eventual) you understood the two extremes: strong gives the illusion of a single copy always up to date, at the cost of coordination and latency; eventual lets the copies be behind for a while but guarantees that they converge, in exchange for speed and availability. You saw the intermediate spectrum —read-your-writes, monotonic reads— that solves the uncomfortable cases without paying the full strong. And you measured Enlace's decision: only ~80 short_codes in flight at any instant, probability 2e-11 that a random read hits one, and the decisive argument that a freshly created code nobody else knows —so the only stale case is the creator's, cured with read-your-writes—. You also saw where eventual is not enough (the uniqueness of the short_code when generating it) and how it is solved by construction, without raising the whole system to strong.

Before moving on you should be able to: define strong and eventual consistency precisely; locate read-your-writes and monotonic reads on the spectrum; reproduce the calculation of Enlace's inconsistency window and explain why eventual is enough for resolve; and justify why consistency is chosen per operation, not for the whole system.

What comes next is the module's third block: the yardstick that measures all of this in a number. You already know what can take Enlace down (reliability) and what lags it tolerates (consistency); what is missing is putting a price on the promise. In lesson 7 you will run the table of the nines —99% = 87.6 h/year, 99.9% = 8.76 h/year, 99.99% = 52.6 min/year— and you will distinguish the three terms everyone confuses (SLI, SLO, SLA), compute the error budget (99.9% monthly = 43.2 min/month of allowed downtime) and the dependency ceiling (you cannot promise more than the product of your providers). It is where "I want Enlace to be reliable" becomes "Enlace promises 99.9% availability on resolve, with this downtime budget".

Resources