1. Courses
  2. System Design
  3. Distributed Systems
  4. Stop 1

35 minAdvanced

CAP, PACELC and consistency

As soon as your data lives on more than one machine, the copies can disagree. This lesson gives you the words and the rules for that: what a system can promise when the network breaks, what it costs on a normal day, and how to choose the right promise for each feature.

By the end you can

  • Explain CAP in one sentence, and why "pick two of three" is the wrong way to say it
  • Use PACELC to talk about the everyday cost of consistency: latency
  • Tell strong, eventual, read-your-writes, monotonic and causal consistency apart with a retail example each
  • Check a quorum setting with R + W > N
  • Build read-your-writes on top of a Prisma replica setup with a short Redis marker

The everyday picture

Imagine a wholesaler with two offices, North and South, in two cities a few hundred kilometres apart. Both keep a copy of the same stock ledger, and every time one office sells something, a clerk phones the other office so both ledgers stay the same. Then a storm cuts the phone line for three hours.

A customer walks into the South office and wants the last carton of cooking oil. The clerk has two choices. He can sell it, and hope North did not sell the same carton in the meantime. Or he can say "Sorry, I can't confirm stock until the line is back." The first choice keeps the shop open and risks selling a carton twice. The second keeps the ledgers correct and sends a customer away. There is no third choice while the line is down. That is the CAP theorem, and the rest of this lesson is about making that choice on purpose.

When one machine becomes many

A is any system where parts run on different machines and talk over a network. If you have followed the earlier courses, you have already met several: a primary with read replicas (Replication and read replicas), a Redis cache in front of Postgres (Caching), workers reading a queue (Queues, background jobs and pub/sub). Each copy of the data can be a little behind the others.

The network between machines fails in ways a single machine never does. A cable is cut, a switch reboots, a cloud zone loses its uplink, a store's internet drops during a power cut. When some machines can still talk to each other but not to the rest, that is a . Each side keeps running and keeps getting requests, but cannot see what the other side is doing. Partitions are not rare events you can design away. In a large system, some link is broken somewhere most days.

CAP: during a split, refuse or guess

The CAP theorem (Eric Brewer's idea from 2000, proved by Seth Gilbert and Nancy Lynch in 2002) talks about three properties:

  • Consistency (C). Every read sees the latest write, as if there were only one copy of the data. This strict meaning is called .
  • Availability (A). Every request to a working machine gets a real answer, not an error and not an endless wait.
  • Partition tolerance (P). The system keeps working when the network between machines breaks.

People often say "pick two of the three". That is misleading. You cannot choose to have no partitions, because the network decides that for you. The useful version is: when a partition happens, each side must either refuse requests it cannot confirm (choose C) or answer with what it knows and accept that the sides may disagree (choose A). A system that refuses is called CP. A system that answers anyway is called AP. When the network is healthy, a well-built system gives you both.

Quick check

Why is "pick any two of C, A and P" a misleading way to say CAP?

buy last carton buy last carton North leader, stock: 1 South stock: 1 link cut CP: refuse when unsure Sell: OK stock now 0 Sell: refused can't reach North AP: always answer Sell: OK stock now 0 Sell: OK stock now 0, too After heal: 2 sold but only 1 existed
The same split, two policies. A CP system keeps the data correct by refusing at the side that cannot reach the leader, so South loses a sale. An AP system keeps both sides selling, and when the link comes back the ledgers show two sales of one carton.

Which side should a feature take?

Neither choice is "better". It depends on what a wrong answer costs compared with no answer. Selling the last carton twice means a phone call to an angry partner shop and a refund, so ordering against scarce stock should usually be CP: if the order service cannot confirm with the stock database, it says "try again in a minute". Showing a product photo that is ten minutes old costs nothing, so the catalogue should be AP: answer from the cache or a replica even if the primary is unreachable.

A point-of-sale counter during an internet outage is the most famous AP case in retail. The store keeps selling from its local copy, and the sales are merged with head office later. That only works because the business accepts that the stock count may be wrong for a while. Offline-first apps and data sync builds exactly that.

Quick check

During a network split, which of these features should refuse rather than answer from a local copy?

Try both policies in the simulator. It has two nodes: North, which holds the leader, and South. Start in CP mode, press "Cut the network", and press "Sell 1 here" at North and then at South. Then switch to AP, sell a few units on both sides until the total passes the 5 in stock, and press "Heal the network".

Simulator

Loading the simulator…

  • In CP mode, South (the side without the leader) gives errors for both sales and reads, while North keeps selling. Nothing goes wrong with the data, but customers on that side are turned away.
  • In AP mode, both sides keep working and their numbers drift apart. If the total sold is more than the stock, healing the network cannot fix it. A person has to.

PACELC: the price you pay on a normal day

Partitions are only part of the story. Daniel Abadi pointed out in 2010 that CAP says nothing about the 99% of the time when the network works. His extension is called : if there is a Partition, choose A or C; Else, choose Latency or Consistency.

The "else" part is the one you feel every day. To keep copies in step, a write has to wait until other machines confirm it. Each confirmation is a network round trip: about half a millisecond inside one data centre, and tens of milliseconds or more to another city or region. If you don't wait, writes are fast, but a read from another copy may be old.

Postgres shows this clearly. With the default , a commit returns as soon as the primary has saved it, and replicas catch up a few milliseconds or seconds later. That is "else latency". If you set synchronous_standby_names and synchronous_commit = remote_apply, every commit waits until a standby has applied it, so a read on that standby always sees it. That is "else consistency", paid for with a slower commit on every write. If that standby is unreachable, commits wait until it comes back or until you change the setting, which is CP behaviour during a partition.

So a system gets a two-part label. Cassandra and Riak are PA/EL by default: they stay up and answer fast, and you ask for stronger reads per query when you need them. etcd, which stores the configuration of Kubernetes clusters, is PC/EC: a majority must agree on every write and, by default, on every read, and the minority side refuses. A single Postgres primary on its own counts as PC/EC too: there is only one copy to read and write, and when it is unreachable, nobody can write.

Consistency is a ladder, not a switch

"Consistent" and "eventually consistent" are the two words you hear most, but there are useful steps between them. Each step is a promise about what a reader may see.

ModelThe promiseRetail example
Strong (linearizable)Once a write is confirmed, every reader everywhere sees itReserving the last cartons for a partner shop order
CausalIf B happened because of A, nobody sees B without AA "GRN corrected" note is never shown before the it corrects
Read-your-writesYou always see your own changes; others may see them laterA manager saves a GRN and sees it in the list on the next screen
Monotonic readsOnce you have seen a value, you never see an older oneRefreshing the order list never makes a new order disappear
EventualIf writes stop, all copies agree after a while; no promise about whenProduct descriptions and images on the Partner app

The middle three are called , because they are about one user's experience. They are cheap compared with strong consistency, and they remove most of the bugs users actually notice.

Monotonic reads break in a way that surprises people. Say you have two replicas, one 1 second behind and one 5 seconds behind, and each request goes to a random one. A partner shop owner refreshes the order list and sees three orders, refreshes again and sees two, refreshes again and sees three. Nothing is lost, but it looks broken. The fix is to send each user to the same replica every time, for example by hashing the user id. One replica only ever moves forward, so that user's view only moves forward too.

Quick check

A customer refreshes the delivery screen and the order goes from "Delivered" back to "On the way". Which guarantee is missing?

Think first: a replica is 2 seconds behind. Is that a problem for a sales dashboard? For the screen right after "Save GRN"?

For the dashboard, no. Nobody can tell whether a chart is 2 seconds old, and the replica keeps heavy queries off the primary. For the screen after saving, yes. The manager just pressed Save, and if the list does not show the new GRN, he assumes it failed and saves it again. That screen needs read-your-writes. The data and the lag are the same; the need is different, so the routing should be different.

Quorums: how many copies must agree?

Some databases (Cassandra, DynamoDB internally, Riak) have no single primary. Every key is stored on N machines, and you choose two numbers. W is how many must confirm a write before it counts as done. R is how many you ask on a read; you take the newest version among their answers. A group of machines that is large enough to decide is called a .

The rule to remember is R + W > N. If it holds, the machines you read from and the machines that took the last write must share at least one machine, so at least one answer contains the newest value. The common choice is N = 3, W = 2, R = 2. It survives one machine being down for both reads and writes, and every read overlaps every write.

You can also read the fault tolerance straight from the numbers. A write needs W machines, so it still succeeds with N − W machines down. A read needs R, so it still succeeds with N − R down. With N = 3 and W = 2, one machine can be down for writes.

Quick check

A key is stored on N = 5 replicas and writes wait for W = 3. What is the smallest R that makes every read overlap the latest write?

Write qty = 4 wait for W = 2 acks ack ack slow, not yet Replica A qty 4 (v2) Replica B qty 4 (v2) Replica C qty 5 (v1) not asked v2 v1 Read asks R = 2 newest (v2) wins R + W = 2 + 2 > N = 3 so they always overlap
Quorum with N = 3, W = 2, R = 2. Replica C has not received the write yet, but the read also asks replica B, which has it. Because 2 + 2 is more than 3, any read group and any write group overlap in at least one replica.

Turning the dials

Change W and R and you move along the PACELC line. W = 1, R = 1 is the fastest and works with two machines down, but a read can easily miss a write. W = 3, R = 1 makes reads fast and always fresh, but a write fails as soon as one machine is down. W = 2, R = 2 is the balanced middle. Cassandra exposes this per query as consistency levels such as ONE, QUORUM and ALL. DynamoDB reads are eventually consistent by default; a read with ConsistentRead: true is strongly consistent and uses twice the read capacity.

R + W > N is a strong rule of thumb, not a complete guarantee. A write that failed after reaching only one replica, two writes to the same key at the same moment, or a "sloppy" quorum that borrows other machines during a failure can still produce surprising reads. When you need a hard guarantee, such as "never sell more than we have", use one place that decides: a single Postgres primary with an atomic conditional update (Transactions, ACID, isolation and locks), or a system built on a consensus protocol.

For most teams on Postgres, the practical picture is simpler. Writes go to one primary, so writes are strongly consistent. If the primary is unreachable, nobody can write (C over A). Replicas keep answering reads with slightly old data (A over C for reads). Your job is to decide, read by read, which ones may be a little old.

Build it

See replica lag break read-your-writes on your laptop, then fix it with a Redis marker that sends a user's reads to the primary for a few seconds after they write. The steps need a local primary and replica; the quorum arithmetic from this lesson is an exercise you can run in the browser, in the Practice section below.

  1. Make lag visible

    Use the primary and replica you set up in Replication and read replicas. Pausing WAL replay on the replica freezes it in the past, which is a big, easy-to-see version of normal lag.

    SQL
    -- On the REPLICA, as a superuser: stop applying changes
    SELECT pg_wal_replay_pause();
    
    -- On the PRIMARY: save a GRN
    INSERT INTO grn (site_id, supplier_id) VALUES (12, 305) RETURNING id;
    
    -- On the REPLICA: the new row is not there yet
    SELECT id, site_id, received_at FROM grn ORDER BY id DESC LIMIT 3;
    
    -- On the REPLICA: catch up again
    SELECT pg_wal_replay_resume();

    Before you pick a window, look at real lag: SELECT application_name, replay_lag FROM pg_stat_replication; on the primary. Choose a marker time comfortably above the worst lag you see on a normal busy day.

  2. Write a read router

    The service sets a short-lived key after a user writes, and picks the primary for that user while the key exists. If Redis is down, it reads from the primary: when in doubt, choose correct over fast. Db is the extended Prisma client from Replication and read replicas; REDIS is an ioredis provider.

    TypeScript
    // read-router.service.ts
    import { Inject, Injectable, Logger } from '@nestjs/common';
    import Redis from 'ioredis';
    import { DB, Db } from './db.module';
    import { REDIS } from './redis.module';
    
    export type Reader = Omit<Db, '$primary' | '$replica'>;
    const WINDOW_SECONDS = 10; // well above normal replay_lag
    
    @Injectable()
    export class ReadRouter {
      private readonly log = new Logger(ReadRouter.name);
      constructor(@Inject(DB) private readonly db: Db, @Inject(REDIS) private readonly redis: Redis) {}
    
      /** Call right after a write has committed. */
      async markWrote(scope: string): Promise<void> {
        try {
          await this.redis.set(`ryw:${scope}`, '1', 'EX', WINDOW_SECONDS);
        } catch (e) {
          this.log.warn(`read-your-writes marker not set for ${scope}: ${e}`);
        }
      }
    
      /** Primary for anyone who wrote in the last few seconds, replica for everyone else. */
      async reader(scope: string): Promise<Reader> {
        try {
          const wroteRecently = await this.redis.exists(`ryw:${scope}`);
          return wroteRecently ? this.db.$primary() : this.db;
        } catch {
          return this.db.$primary(); // Redis is down: correct beats fast
        }
      }
    }
  3. Use it in a service

    Choose the scope with the product question "whose writes must this person see?". Here the whole store's team should see a goods received note (GRN) that any of them saved, so the marker is per site (in the data model, a site is a store or a distribution centre).

    TypeScript
    // grn.service.ts
    @Injectable()
    export class GrnService {
      constructor(@Inject(DB) private readonly db: Db, private readonly reads: ReadRouter) {}
    
      async create(siteId: bigint, supplierId: bigint) {
        // Writes always go to the primary
        const grn = await this.db.grn.create({ data: { siteId, supplierId } });
        await this.reads.markWrote(`site:${siteId}`);
        return grn;
      }
    
      async recent(siteId: bigint) {
        const db = await this.reads.reader(`site:${siteId}`);
        return db.grn.findMany({ where: { siteId }, orderBy: { receivedAt: 'desc' }, take: 20 });
      }
    }
  4. Test it with replay paused

    Pause replay again, then call the API. The fresh GRN shows up because the list comes from the primary. After the window ends, the list comes from the frozen replica and the GRN disappears. That shows the fix works, and also its limit: the window must be longer than your real lag.

    Terminal
    psql "$REPLICA_ADMIN_URL" -c "SELECT pg_wal_replay_pause();"
    
    curl -s -X POST localhost:3000/sites/12/grns -H 'content-type: application/json' -d '{"supplierId":305}'
    curl -s localhost:3000/sites/12/grns | jq '.[0].id'    # new id: read from the primary
    
    sleep 11
    curl -s localhost:3000/sites/12/grns | jq '.[0].id'    # older id: replica is still paused
    
    psql "$REPLICA_ADMIN_URL" -c "SELECT pg_wal_replay_resume();"
  5. Write the consistency table into the design doc

    The code is the easy part. The decision behind it belongs in the design doc, one row per feature, so nobody has to guess later.

    Markdown
    ## Consistency decisions
    
    | Feature                       | Needs                  | Read from                   | If primary unreachable          |
    |-------------------------------|------------------------|-----------------------------|---------------------------------|
    | Reserve stock for an order    | Strong                 | Primary, conditional UPDATE | Refuse: "try again in a minute" |
    | "My orders" after placing one | Read-your-writes       | Primary for 10 s, then replica | Show cached list + banner    |
    | Order status for the customer | Monotonic reads        | Same replica per user       | Show last known status          |
    | Product catalogue and photos  | Eventual (minutes)     | Cache, then replica         | Serve from cache                |
    | Zone sales dashboard          | Eventual (a few min)   | Replica or warehouse        | Show "data as of 10:40"         |

Bring it to your team

Use these when a design has replicas, caches, or more than one data centre.

  • If this service cannot reach the database, what does the user see: an error, or an answer that may be old?
  • Which reads in this feature must show the user's own change right away, and which can be a few seconds old?
  • What is our replica lag on a busy day, at p99? Is our read-your-writes window longer than that?
  • If two places can accept a change to the same thing during a split, how do we merge them, and who is told when the merge goes wrong?
  • Are we paying for strong consistency (synchronous replication, quorum reads) on data where nobody would notice a few seconds of delay?
  • Is the consistency choice for each feature written in the design doc?

Red flags

  • "We picked C and A" or "the network is reliable inside our cloud".
  • "It's eventually consistent", with no idea whether "eventually" means 50 ms or 50 minutes.
  • A stock or payment decision made from a replica or a cache read.
  • Every read forced to the primary "to be safe", so the replicas sit idle and the primary is overloaded.

Practice

Check a quorum setting

Write checkQuorum(n, w, r) for a store that keeps every key on n replicas, waits for w acknowledgements on a write and asks r replicas on a read. Return an object with three fields: overlap (true when every read group shares at least one replica with every write group), writeCanLose (how many replicas can be down while writes still succeed) and readCanLose (the same for reads). If w or r is less than 1 or more than n, throw an error, because that setting can never work.

JavaScript exerciseRuns in your browser with tests
function checkQuorum(n, w, r) {
  // 1. Throw if w or r is outside 1..n
  // 2. overlap: do read and write groups always share a replica?
  // 3. How many replicas can be down for writes? For reads?
  return { overlap: false, writeCanLose: 0, readCanLose: 0 };
}
Ctrl + Enter runs
Show a solution
JavaScript
function checkQuorum(n, w, r) {
  if (w < 1 || w > n || r < 1 || r > n) {
    throw new Error('w and r must be between 1 and n');
  }
  return {
    overlap: r + w > n,      // the two groups must share a replica
    writeCanLose: n - w,     // a write still finds w replicas
    readCanLose: n - r,      // a read still finds r replicas
  };
}
Pass the exercise to finish this task.

Write the consistency table for the Rider app

The Rider app has four features. For each one, write a row with the consistency it needs, where it reads from, and what it does if the primary database is unreachable. (1) A rider taps "Accept" on a new order; only one rider may get each order. (2) A rider opens "Today's earnings" right after finishing a delivery. (3) A customer watches the rider's dot move on a map. (4) The list of delivery zones, which changes a few times a year.

  • Accepting an order is strong: one place decides, and the app refuses when it cannot confirm
  • Today's earnings uses read-your-writes, so the delivery the rider just finished shows up
  • The map can be eventual and a few seconds old, and shows the last known position when the data is unreachable
  • The zone list is served from a cache or replica and keeps working without the primary
  • Every row says what the user sees on a bad day, not only on a good one
Show a model answer
Markdown
| Feature              | Needs             | Read from                     | If primary unreachable              |
|----------------------|-------------------|-------------------------------|-------------------------------------|
| Accept an order      | Strong            | Primary, conditional UPDATE   | Refuse: "Could not confirm, retry"  |
| Today's earnings     | Read-your-writes  | Primary for 10 s, then replica | Show cached total + "as of 18:05"  |
| Rider dot on the map | Eventual (secs)   | Latest position cache         | Keep last known dot, grey it out    |
| Delivery zones       | Eventual (days)   | Cache, then replica           | Serve from cache                    |

The accept step is the only one where a wrong answer (two riders on one order) is worse than no answer. It uses UPDATE "order" SET rider_id = $1 WHERE id = $2 AND rider_id IS NULL on the primary and checks that one row changed.

Check yourself

A network cut separates the South node from the North leader. The system is CP. What does a customer at South get when buying the last carton?

You store each key on N = 3 replicas. You want every read to see the latest confirmed write, and you want reads and writes to keep working when one replica is down. Which setting fits?

A store manager saves a GRN, and the list on the next screen does not show it. Replica lag is about 2 seconds. What is the most targeted fix?

Flashcards

Open my review

Key takeaways

  • Partitions will happen. CAP says that during one, each side must refuse (CP) or answer with what it knows (AP).
  • PACELC adds the everyday cost: when the network is fine, stronger consistency means waiting for other copies, which adds latency to writes.
  • Between strong and eventual sit cheap session guarantees, such as read-your-writes and monotonic reads, which fix most bugs users notice.
  • With quorums, R + W > N makes reads and writes overlap; N = 3, W = 2, R = 2 also survives one machine down.
  • Choose consistency per feature, write it in the design doc, and keep money and stock decisions on one place that decides.

Sources

Episode transcriptSell the last carton, or refuse? · about 14 minutes · tap any line to play from there
  1. NadiaA storm, Imran. A wholesaler has two offices in two cities, North and South. Both keep a copy of the same stock ledger, and every time one office sells something, a clerk phones the other, so both ledgers stay the same. Then the storm cuts the phone line for three hours.
  2. ImranAnd I'm guessing a customer walks in.
  3. NadiaInto the South office, wanting the last carton of cooking oil. The clerk has two choices. Sell it, and hope North didn't sell the same carton in the meantime. Or say, sorry, I can't confirm stock until the line is back.
  4. ImranThe first keeps the shop open and risks selling one carton twice. The second keeps the ledger right and sends a customer away.
  5. NadiaAnd while the line is down, there's no third choice. That's the CAP theorem.
  6. NadiaThis episode opens the Distributed Systems course. Once your data lives on more than one machine, the copies can disagree. Today: what a system can promise when the network breaks, what that costs on a normal day, and how to choose per feature.
  7. ImranBut Nadia, Mangrove isn't really a distributed system. We have one main database.
  8. NadiaYou already have several. A primary with read replicas. A Redis cache in front of Postgres. Workers reading a queue. Each copy of the data can be a little behind the others.
  9. ImranFair. What actually breaks?
  10. NadiaThe network between machines, in ways a single machine never does. A cable is cut, a switch reboots, a cloud zone loses its uplink, a store's internet drops during a power cut.
  11. NadiaWhen some machines can still talk to each other but not to the rest, that's a network partition. Each side keeps running, but can't see the other. And in a large system, some link is broken somewhere most days.
  12. ImranOkay. CAP stands for three things.
  13. NadiaConsistency: every read sees the latest write, as if there were only one copy of the data. Availability: every request to a working machine gets a real answer, not an error and not an endless wait. And partition tolerance: the system keeps working when the network between machines breaks.
  14. ImranAnd the famous rule is, pick two of the three. So we pick consistency and availability, and we're done.
  15. NadiaThat's the misleading version, and a common trap. You can't choose to have no partitions. The network decides that for you.
  16. NadiaThe useful version is: when a partition happens, each side must either refuse requests it can't confirm, which is choosing C, or answer with what it knows and accept that the sides may disagree, which is choosing A.
  17. ImranSo CP refuses, and AP answers anyway.
  18. NadiaYes. And when the network is healthy, a well-built system gives you both. The lesson has a warning about teams that say our system is CA. It means nobody decided what happens when the network splits, so the code decides for them, usually with long timeouts that make the app hang.
  19. NadiaThe first diagram shows the storm with both policies. If you can't see it, picture North and South, each with one carton, the link cut. North holds the leader. In the CP version, North sells, and South says refused, can't reach North.
  20. ImranAnd in the AP version, both sell. When the network heals, two sales of a carton that only existed once. So which is better?
  21. NadiaNeither. It depends on what a wrong answer costs compared with no answer. Selling the last carton twice means a phone call to an angry partner shop and a refund. So ordering against scarce stock should usually be CP. If the order service can't confirm, it says try again in a minute.
  22. NadiaShowing a product photo that's ten minutes old costs nothing. So the catalogue should be AP. Answer from the cache or a replica even if the primary is unreachable.
  23. ImranAnd the POS counter during an internet outage keeps selling. That's AP.
  24. NadiaThe most famous AP case in retail. The store sells from its local copy, and sales are merged with head office later. That only works because the business accepts that stock counts may be wrong for a while. The offline sync lesson at the end of this course builds exactly that.
  25. NadiaTry both in the simulator later. It has two nodes, North, the leader, and South. Start in CP mode, cut the network, and try to sell at each node. Then switch to AP, sell a few units on both sides, and heal the network.
  26. ImranAnd in AP mode, if the total sold is more than the stock, healing the network can't fix it.
  27. NadiaA person has to. Now, CAP only covers the bad days. Daniel Abadi pointed out in twenty ten that it says nothing about the ninety-nine percent of the time when the network works. His extension is called PACELC.
  28. ImranThat's a mouthful.
  29. NadiaRead it as a sentence. If there's a partition, P, choose availability or consistency. Else, E, when the network is fine, choose latency or consistency. That else part is the one you feel every day.
  30. ImranWhy does consistency cost latency on a good day?
  31. NadiaTo keep copies in step, a write has to wait until other machines confirm it. Each confirmation is a network round trip. About half a millisecond inside one data centre, and tens of milliseconds or more to another city. If you don't wait, writes are fast, but a read from another copy may be old.
  32. ImranThat's our replica setup from the replication lesson.
  33. NadiaExactly. With default async replication, a commit returns as soon as the primary has saved it. That's else latency. If you make every commit wait until a standby has applied it, a read there always sees it. That's else consistency, paid for with a slower commit on every write.
  34. ImranAnd real databases get labels?
  35. NadiaTwo-part labels. Cassandra is PA, EL by default. It stays up and answers fast. etcd, which stores Kubernetes configuration, is PC, EC. A majority must agree, and the minority side refuses. A single Postgres primary counts as PC, EC too: when it's unreachable, nobody can write.
  36. ImranI hear consistent and eventually consistent all the time. Is that the whole choice?
  37. NadiaThere are useful steps in between. The lesson calls it a ladder. At the top, strong: once a write is confirmed, everyone everywhere sees it. Like reserving the last cartons for an order. At the bottom, eventual: copies agree after a while, with no promise about when. Like product descriptions.
  38. ImranAnd in the middle?
  39. NadiaRead-your-writes: you always see your own changes, others may see them later. A manager saves a GRN and sees it on the next screen.
  40. NadiaMonotonic reads: once you've seen a value, you never see an older one. And causal: if B happened because of A, nobody sees B without A. Like a correction note never shown before the GRN it corrects.
  41. NadiaRead-your-writes and monotonic reads are session guarantees, about one user's experience. They're cheap compared with strong consistency, and they remove most of the bugs users actually notice.
  42. ImranHow would monotonic reads break?
  43. NadiaSay you have two replicas, one a second behind, one five seconds behind, and each request goes to a random one. A partner shop owner refreshes the order list and sees three orders. Refreshes again, and sees two. Again, three.
  44. ImranNothing is lost, but it looks broken. So we'd send each user to the same replica every time.
  45. NadiaYes, for example by hashing the user ID. One replica only moves forward, so that user's view only moves forward too.
  46. ImranSome databases have no single primary at all, right?
  47. NadiaLike Cassandra, or Riak. Every key is stored on N machines, and you pick two numbers. W is how many must confirm a write. R is how many you ask on a read, taking the newest answer. The rule to remember is: R plus W must be greater than N.
  48. NadiaThe second diagram shows why. Picture three replicas, A, B and C. A write of quantity four waits for two acknowledgements, from A and B. C is slow and still has the old value, five. Then a read asks two replicas, B and C. B has the new value, so the newest one wins.
  49. ImranBecause two plus two is more than three, any read group and any write group share at least one machine.
  50. NadiaExactly. N three, W two, R two is the common choice. It survives one machine being down for both reads and writes. But it's a strong rule of thumb, not a complete guarantee. For something like never sell more than we have, use one place that decides: a single Postgres primary with an atomic conditional update.
  51. ImranLet me apply it to the Partner app. It reads from a replica to keep the primary free for the store tills.
  52. NadiaThe lesson's Mangrove example uses exactly that. Three features, three choices. The catalogue can be minutes old, so replica and cache are fine. The My orders screen right after placing an order must show that order, so that user reads from the primary for a few seconds.
  53. NadiaAnd the stock reservation itself is a conditional update on the primary, inside the order transaction. If the primary can't be reached, the app says, we could not confirm stock, please try again, instead of accepting the order.
  54. ImranAnd the Build it section builds that My orders fix?
  55. NadiaYes, for the GRN list. First you make lag visible. On the replica you pause WAL replay, which freezes it in the past. You save a GRN on the primary, and the replica doesn't show it. Then you check real lag on a busy day, and pick a window comfortably above it.
  56. NadiaThen a small read router. Right after a write commits, it sets a Redis key for that scope, expiring in ten seconds. While the key exists, that scope reads from the primary. If Redis is down, it reads from the primary too. When in doubt, correct beats fast.
  57. ImranAnd the scope is per user?
  58. NadiaYou choose it with a product question: whose writes must this person see? For GRNs, the goods received notes, the whole store team should see a GRN any of them saved, so the marker is per store.
  59. NadiaThen you test with replay paused. The new GRN appears. After eleven seconds it vanishes again, because the replica is still frozen. So the window must be longer than your real lag.
  60. NadiaAnd the last step is a consistency table in the design doc. One row per feature: what it needs, where it reads from, and what it does if the primary is unreachable.
  61. ImranMy review questions. If this service can't reach the database, does the user see an error, or an answer that may be old? Which reads must show the user's own change right away? What's our replica lag at p ninety-nine on a busy day, and is our window longer than that? And is each choice written in the design doc?
  62. NadiaAdd one: are we paying for strong consistency on data where nobody would notice a few seconds of delay?
  63. NadiaNow a puzzle. A replica is two seconds behind. Is that a problem for a sales dashboard? And for the screen right after save GRN? Take a second.
  64. ImranFor the dashboard, no. Nobody can tell if a chart is two seconds old. For the screen after saving, yes. The manager thinks his save failed and saves again.
  65. NadiaExactly. Same data, same lag, different need. So the routing should be different.
  66. ImranAnd the quorum rule is something I can practise right in the page?
  67. NadiaYes. The practice section has a small function to write: give it N, W and R, and it says whether reads and writes overlap, and how many replicas can be down. Then a second task asks you to fill a consistency table for the Rider app.
  68. ImranMy takeaways. One. Partitions will happen, and during one, each side must refuse or answer with what it knows. Two. On a normal day, stronger consistency means waiting for other copies, so slower writes. Three. Between strong and eventual are cheap guarantees like read-your-writes, which fix most bugs users notice.
  69. NadiaAnd the rule for your team: choose consistency per feature, write it down, and keep money and stock decisions in one place that decides.
  70. ImranNo more saying we're CA.
  71. NadiaPlease, never. Next episode: what to do when a call to another service just doesn't answer. Timeouts, retries, and why waiting a random amount of time helps.
Finished reading?Mark it complete to fill in its stop on your course line.

Use it at work

Multi-region

Lose a whole region and keep serving.

Open in the System Design Studio
Next stopFailure is normal: timeouts, retries, backoff and circuit breakers
Was this lesson helpful?
Questions and discussion

Loading the discussion…