Chapter 5 of 12 · Part II, Distributed data

Replication

A study guide for Chapter 5 of Designing Data-Intensive Applications by Martin Kleppmann. It explains how to keep copies of the same data on several machines, and what goes wrong when the copies differ. It uses simple English, diagrams, examples, and tools that you can use.

Reading time: about 70 minutesBefore this: Part I (chapters 1 to 4). This is the first chapter of Part II.

What you will learn
  • Why you copy data to several machines, and the three reasons for it.
  • The three main replication methods: single-leader, multi-leader, and leaderless.
  • Why an asynchronous follower can show old data, and three guarantees that prevent the worst surprises.
  • How two writes at the same time create a conflict, and how to detect and merge them.
  • How quorums (w and r) decide how many copies must answer.

The ideas and facts come from the book. The code, examples, and drawings are new. The boxes named "Update since the book" come from public documentation.

Story first · Chapter 5

The courier and the ledger

Before the rules, meet the cast. This is a story about a village message that three couriers must keep the same. It is original art in paper-cut style. The characters are invented for this story and are not from any game or comic.

Ren, the leader. Writes every change first, in order, in the ledger.
Mio, a fast courier with cat ears and a quick route.
Gor, a strong courier whose road is often blocked.
Sora, a reader from the village who asks for the latest news.
v7leader's ledgerRenBridge closed. Version 7.
1. The leader writes the news The bridge is closed. Ren is the leader, so Ren writes the change first, as version 7 in the ledger. The ledger keeps the order of changes.

Your turn: change the rules

Gor's copy is always late in this story. Ren and Mio always hold the new message. Choose how many couriers must confirm a write (W), and how many a reader asks (R). Watch the story.

1. Why replicate?

Replication means that you keep a copy of the same data on several machines connected by a network. Each machine that holds a copy is a replica. There are three reasons to do this:

Latency

Put the data near the users. A user in Mumbai reads from a server in Mumbai, not in Virginia.

Availability

If one machine (or a whole data center) fails, the others keep serving the data.

Read throughput

Many replicas can answer read requests. One machine would be too slow.

In this chapter, each machine can hold the whole data set. Chapter 6 covers partitioning (sharding), which splits data that is too big for one machine. Here is the key point: if the data never changes, replication is easy. You copy it once. All the difficulty comes from changes. Every write must reach every replica, and it must reach them in a sensible order.

Three algorithms do this, and almost all databases use one of them:

ApproachWho accepts writes?Examples
Single-leaderOne leader. Followers copy its changes.PostgreSQL, MySQL, MongoDB, Kafka
Multi-leaderSeveral leaders, often one per data center.Tungsten, BDR for PostgreSQL, CouchDB
LeaderlessAny replica, directly from the client.Dynamo, Riak, Cassandra, Voldemort

2. Leaders and followers

The most common method is leader-based replication. Also called master–slave, or active/passive.

  1. One replica is the leader (or primary). All writes go to the leader. The leader writes the new data to its own storage first.
  2. The other replicas are followers (read replicas, secondaries, hot standbys). The leader sends each change to its followers in a replication log. A follower applies the changes in the same order as the leader.
  3. A client can read from the leader or from any follower. But writes are accepted only by the leader.
Leader-based replication: writes go to the leader, changes flow to followers Client Leaderaccepts writes Follower 1read-only Follower 2read-only write replication log reads can go to any replica Dashed: reads. Solid: writes and changes.
Figure 1. Single-leader replication. My own drawing of the book's Figure 5-1.

Many databases use this: PostgreSQL (since version 9.0), MySQL, Oracle Data Guard, and SQL Server AlwaysOn. Some NoSQL systems use it too, such as MongoDB and RethinkDB. Message brokers such as Kafka and RabbitMQ (in highly available queues) use it. So do network filesystems and DRBD.

Synchronous or asynchronous replication?

Here is a website example. A user changes their profile picture. The client sends the change to the leader. The leader sends it to the followers. Then the leader tells the client that the update worked. The question is: does the leader wait for the followers before it says "OK"?

Try it: what survives a leader crash?

Choose the replication mode. Then press "Leader crashes now". The user has been told "saved" for the write. Does the new leader still have it?

Here is the trade-off:

In practice, one follower is often synchronous, and the others are asynchronous. If the synchronous follower slows down, an asynchronous one takes its place. This gives an up-to-date copy on at least two nodes. People call this semi-synchronous.

Asynchronous replication is still very common

Losing durability sounds bad. But asynchronous replication is used widely, especially with many followers or followers in other regions. The next section explains the price you pay for that.

Setting up a new follower

You cannot copy the data files while the database is changing. The files would show different parts of the data at different times. So you do this, without downtime:

  1. Take a consistent snapshot of the leader. Most databases can do this for backups, without locking everything.
  2. Copy the snapshot to the new follower.
  3. The follower asks the leader for every change since the snapshot. The snapshot must be tied to an exact position in the replication log. PostgreSQL calls this the log sequence number. MySQL calls it the binlog coordinates.
  4. When it has processed the backlog, it has caught up. Then it follows the live stream.

Node outages and failover

Nodes fail, and they also go down for planned work, such as a security patch. The goal is that the system keeps working, and the impact stays small.

Follower failure: catch-up recovery

Each follower keeps a log of the changes it received. After a crash or network break, it knows the last change that it applied. It asks the leader for everything after that point. Then it is caught up again.

Leader failure: failover

If the leader fails, one follower must become the new leader. Clients must send their writes to the new leader. The other followers must follow the new leader. This is failover. It can be manual, or automatic. An automatic failover does these three steps:

  1. Detect that the leader failed. There is no perfect way. Most systems use a timeout. If a node does not answer for, say, 30 seconds, it is assumed dead.
  2. Choose a new leader. By an election, or by a controller node. The best choice is the replica with the most recent data, to lose as little as possible. Agreement is a consensus problem (Chapter 9).
  3. Reconfigure the system. Clients use the new leader. The old leader, if it comes back, must accept that it is now a follower.

Try it: the timeout dilemma

A short timeout finds a dead leader fast. But it also causes false alarms when the leader is only slow. Move the slider.

Failover can go wrong in four ways, and each one is a real risk:

Real example

Example: promoting a follower safely

Three replicas: A (leader), B, and C. B has received everything from A. C is 2 minutes behind. A crashes. The best new leader is B, because it has the most recent data. If C is promoted, the 2 minutes of writes stay on A. When A comes back, those writes are either discarded or must be merged by hand.

How replication logs work

How does the leader send its changes? There are several methods.

what the leader sends (example)
-- The leader logs the SQL text of each write:
INSERT INTO orders (id, created) VALUES (42, NOW());
UPDATE accounts SET balance = balance - 10 WHERE id = 7;

-- Problems:
--   NOW() and RAND() give different values on each replica.
--   "UPDATE ... WHERE" depends on the data already there.
--   Triggers and stored procedures can have side effects.
-- Fix: the leader replaces NOW() with a fixed value before it logs.

Each method has a trade-off. Statement-based replication is compact, but breaks easily. WAL shipping is precise, but it ties the replicas to one storage format. Logical logs are the most flexible, because the format is separate from the storage. Trigger-based replication gives the most control, and it is the most work.

Update since the book

PostgreSQL now has logical replication built in. It publishes changes per table, and a subscriber applies them. This is the logical approach from the book. Quorum-based synchronous replication is also in PostgreSQL. You write ANY 2 (s1, s2, s3) in synchronous_standby_names, and each commit waits until any 2 of the 3 standbys confirm it. MySQL uses row-based replication by default, when a statement is not deterministic. Check your version's documentation before you choose a setting.

In simple words
  • One leader takes writes. Followers copy its log in order.
  • Synchronous: safe, but slow or blocked when a follower is down. Asynchronous: fast, but the latest writes can be lost.
  • Failover is hard: lost writes, split brain, and wrong timeouts.

3. Problems with replication lag

Asynchronous followers are behind the leader by a small time. This is the replication lag. Normally it is under a second. Under heavy load or network trouble it can grow to minutes.

A follower shows old data while it is behind. If you run the same query on the leader and on a follower, you can get different answers. This is only temporary: when writes stop, the followers catch up. Databases call this eventual consistency. The word "eventually" is vague on purpose. There is no upper limit on the lag.

Many developers did not expect this. They built apps as if the database were one machine. The next three guarantees describe the most common surprises.

Reading your own writes

A user submits a comment, then reloads the page. If the reload reads from a lagging follower, the comment is missing. The user thinks the data was lost. This is a read-after-write problem. You need read-your-writes consistency: a user always sees their own updates. Other users may still see old data for a while.

Ways to get it:

Monotonic reads

A user refreshes a page. The first request goes to a fresh follower and shows a new comment. The second goes to a lagging follower and the comment has gone. Time seems to go backward. Monotonic reads is the guarantee that this does not happen: once you have read a value, later reads do not return an older value. It is weaker than strong consistency, but stronger than plain eventual consistency.

A simple way: send each user to the same replica every time, for example by hashing the user ID. If that replica fails, the user moves to another replica.

Consistent prefix reads

Mr. Poons asks: "How far into the future can you see, Mrs. Cake?" Mrs. Cake answers: "About ten seconds usually." These sentences have a cause and effect. Now imagine an observer who reads Mrs. Cake's answer from a fast partition, and Mr. Poons's question from a slow one. The observer sees the answer before the question. This is a causality violation.

Consistent prefix reads guarantees that if a sequence of writes happened in one order, readers see them in that order. A single partition does this automatically. Problems appear in partitioned databases, where different partitions lag by different amounts. Fixes: put causally related writes in the same partition, or track dependencies explicitly.

Try it: which guarantee prevents each anomaly?

Choose a guarantee for each anomaly. The explanation shows if it was right.

Try it: lag simulator

Move the lag. A user posts a comment at time 0, then reloads at time 1 second. Turn on the fixes and see what the user sees.

src/comments/read-comments.ts (example file)
// Read-your-writes: the author always reads their own recent posts from the leader.
async function readComments(postId: string, viewer: User) {
  const fromLeader = viewer.lastWriteAt && Date.now() - viewer.lastWriteAt < 60_000;
  const db = fromLeader ? leader : pickReplica(viewer.id);   // pinned replica: monotonic reads
  return db.query("SELECT * FROM comments WHERE post_id = $1 ORDER BY created_at", [postId]);
}
In simple words
  • Read your writes: reload shows what you just wrote.
  • Monotonic reads: a later read is never older than an earlier one.
  • Consistent prefix: answers never appear before their questions.
  • Pinning a user to one replica, and reading own data from the leader, are cheap fixes.

4. Multi-leader replication

One leader is a bottleneck. All writes must go through it. If you cannot reach the leader, you cannot write. The next idea: let more than one node accept writes. Each leader sends its changes to the others. Each leader is also a follower of the others. This is multi-leader replication, also called master–master or active/active.

Use cases

Inside one data center, multi-leader rarely pays off, because it adds complexity. It makes sense in three cases:

Try it: single-leader against multi-leader across data centers

Two data centers, Mumbai and Virginia. The network between them takes the time you choose. Compare the time a user waits for a write.

Write from Mumbai
ms wait for the user
If the link goes down

The downside is serious. The same data can be changed in two places at the same time. Those changes conflict. Multi-leader replication is often called dangerous territory. Auto-increment keys, triggers, and constraints can behave badly when several leaders are involved. Most teams avoid it unless they need it.

Handling write conflicts

Two users edit a wiki page title at the same time. User 1 changes it from "A" to "B" on leader 1. User 2 changes it from "A" to "C" on leader 2. Both writes succeed locally. The conflict appears only when the changes are exchanged. With one leader, the second writer would wait or be rejected. With several leaders, both succeed. You learn about the conflict later, when it may be too late to ask the user.

Two leaders accept concurrent writes to the same title Leader 1title: A → B → C Leader 2title: A → C → B writes replicated Two different final valuesB on leader 2 and C on leader 1 (without a rule) Neither order is "more correct". Every system must still reach one value on all replicas. Convergence: all replicas must end with the same value when changes are done.
Figure 2. The conflict from the book's wiki example (Figure 5-7). My own drawing.

Avoid conflicts first

The simplest strategy: make sure all writes for one record go to the same leader. For example, route each user's requests to their "home" data center. Then, for that user, the system acts as single-leader. This breaks when the leader must change, for example after a data-center failure.

Converge to one value

There are four common ways to reach one final value:

MethodHow it worksTrade-off
Last write wins (LWW)Give each write a timestamp or unique ID. The highest one wins.Popular, but loses data. Several writes that were all reported as successful are thrown away.
Highest replica ID winsWrites from the higher-numbered replica always win.Also loses data.
Merge the valuesCombine them, for example sorted and joined: "B/C".No loss, but the merge must make sense for your data.
Keep all, resolve laterStore all conflicting values. Show them to the user or to the app to resolve.No loss. More work for the application.

Most multi-leader tools let you write the rule in code. It can run on write (in the background, fast, cannot ask the user) or on read (all versions go to the app, which decides, then writes the result back). Conflicts are resolved for one row or document at a time. A transaction that changes several rows is not one unit for conflict resolution.

Example: the shopping cart that lost deletions

Amazon once had a cart rule that kept added items, but not removed ones. Customers saw items come back into their carts after deleting them. Custom merge rules are powerful, and they are easy to get wrong.

Try it: pick a conflict rule for the wiki title

Choose a rule. Press the button to replicate both leaders. See whether they agree, and what is lost.

Leader 1 ends with
Leader 2 ends with
Synchronous conflict detection gives up the point

You could wait for every leader to confirm before you say "OK". But then the write is only as fast as the slowest link. You might as well use single-leader replication.

Replication topologies

A topology is the path that writes take from one node to another. With two leaders, there is only one: each sends to the other. With more leaders, there are several choices.

Three multi-leader topologies: circular, star, and all-to-all Circular Each node passes writes to one next node. Star One root forwards to all others. All-to-all Every node sends to every other node.
Figure 3. Three topologies from the book (Figure 5-8). My own drawing.

Overtaking causes a real problem. Client A inserts a row on leader 1. Client B updates that row on leader 3. Leader 2 may receive the update first. From its view, the update is for a row that does not exist. Timestamps do not solve this, because clocks are not perfectly in sync (Chapter 8). The book's answer is version vectors, shown below.

5. Leaderless replication

The last approach drops the leader completely. The client sends a write to several replicas at once. Or a coordinator node does it for the client. No node decides the order of the writes. This style is called Dynamo-style, after Amazon's Dynamo system. Riak, Cassandra, and Voldemort use it.

Writing when a node is down

Imagine three replicas, and one is rebooting. There is no leader, so there is no failover. The client writes to all three in parallel. Two accept. The third misses the write. If the client needs only two acknowledgments, the write succeeds. The client ignores the missed one.

When the third node comes back, it has stale data. Reads may return it. So a read also goes to several nodes in parallel, and the client uses version numbers to pick the newest value. Two mechanisms bring the stale node up to date:

A quorum write and a quorum read with one node down Client Replica 1v7 (ok) Replica 2v7 (ok) Replica 3down, missed write w = 2 two acks is enough write succeeds Replica 3 gets the value later (repair)
Figure 4. A write with w = 2 of n = 3, while replica 3 is down. My own drawing of Figure 5-10.

Gossip: how a rumor spreads

Twelve nodes. One node starts with a new value. Each round, every node that has the value tells a few random neighbors. Change how many each node tells, or split the network in two. Watch how many nodes know the value after each round.

Play · the courier and the ledger

Play the replication story on a map

The same cast as the story above. Ren writes version 7, and the write counts after 2 couriers hold it. Sora asks couriers for the latest version. Block Gor's road to see what a lagging copy does to a read.

Everyone holds version 6. Ren is the leader.

Quorums for reading and writing

How many replicas must agree? With n replicas:

If w + r > n, then the set of nodes that got the write and the set you read must overlap in at least one node. That node has the latest value. This is the quorum condition. A common choice is n = 3 with w = r = 2. Another is n = 5 with w = r = 3.

The quorum condition also tells you how many failures you survive:

Try it: quorums with n replicas

Choose n, w, and r, then choose which replicas are down. The write is acknowledged by the first w available replicas (green). A read asks r replicas (outlined). Try w = 1 and r = 1 to see a stale read.

Limits of quorums and sloppy quorums

Quorums look like a guarantee. They are not one. Even with w + r > n, you can read stale values:

Most quorum systems therefore do not give you read-your-writes, monotonic reads, or consistent prefix reads by default. Stronger guarantees need transactions or consensus (Chapters 7 and 9).

Now the sloppy quorum. Suppose the home nodes for a key are unreachable. You can (a) return an error, or (b) accept the write on any reachable nodes, even ones that are not home nodes. Option (b) is a sloppy quorum. When the home nodes come back, the temporary copies are sent to them. This is hinted handoff.

Analogy from the book

You lock yourself out of your house. You wait on the neighbor's couch. Later you go home, and the neighbor asks you to leave. The couch was a temporary home.

A sloppy quorum increases write availability: as long as any w nodes are up, you can write. But it breaks the overlap. The latest value may sit outside the home nodes, so a read of r home nodes can miss it. A sloppy quorum is only a promise that the data is stored on w nodes somewhere. Riak enables it by default. Cassandra and Voldemort disable it.

Multi-datacenter operation with leaderless replication

Cassandra and Voldemort count the replicas in all data centers in n. The client waits only for a quorum in its local data center. Writes to the other data centers happen in the background. Riak keeps client traffic in one data center, and copies between clusters asynchronously.

Detecting concurrent writes

Two clients write the same key at the same time, to a three-node store. Client A's write reaches node 1 first, and node 2 gets A then B, while node 3 gets B then A. Each node sees a different order, and nobody has a global clock. If each node just keeps the last value it received, the nodes stay different forever.

The common fix is last write wins: attach a timestamp and keep the biggest. This is simple, and Cassandra uses it. But the writes are concurrent*, so neither one happened "first". LWW silently drops one of two successful writes. It can also drop writes that are not concurrent, when clocks are skewed. LWW is fine when losing data is acceptable, for example in a cache. Another safe trick: write each key only once, and never update it. Cassandra users often use a UUID as the key.

The happens-before relationship

What is concurrency? Operation A happens before B if B knows about A, or depends on A, or builds on A. Two operations are concurrent if neither happens before the other. Exact clock time does not matter. Two events a few milliseconds apart can still be concurrent if the network hid one from the other.

Try it: happens before, or concurrent?

Read each case. Decide the relation between the two operations.

Version vectors: capturing the dependencies

The book's example uses one shopping cart on one replica. Two clients add items. The server must decide which writes overwrite others and which are concurrent. The trick: every write carries the version number that the client read before it wrote. Here are the five steps from the book. Press "Next step" to follow them.

Try it: a shopping cart with versions

The server keeps all values that are not yet overwritten, called siblings. A write that includes the version it read overwrites only the values it had seen.

The rules the server follows:

  • The server gives each key a version number that goes up with every write. A read returns all values not yet overwritten, plus the latest version.
  • A client must read before it writes, and it must merge the values it got.
  • When a write with version v arrives, the server removes all values with version v or lower, because the client has merged them. It keeps the values with a higher version, because they are concurrent.
  • A write without a version is concurrent with everything. It overwrites nothing, and it comes back as one more sibling.

Siblings are merged by the application. For a cart, the simple merge is the union. But union has a flaw: if you remove an item in one sibling, it returns in the union. The fix is a tombstone: a marker that says "removed at version v". Riak's CRDT data types merge siblings automatically, and keep deletions.

Version vectors for many replicas

With several replicas and no leader, one version number per key is not enough. Each replica keeps a counter for itself and remembers the counters of the others. The set of counters is a version vector. Riak's dotted version vector is a refinement. Riak calls the encoded vector the causal context. A client that read from one replica and wrote to another is still safe. The worst result is siblings, and no data is lost if you merge them.

Try it: compare two version vectors

Each vector has one counter per replica (A, B, C). Vector X happened before Y if every counter in X is less than or equal to Y's, and at least one is less. If neither is smaller in all places, the writes are concurrent.

src/versions/compare.ts (example file)
type VV = Record<string, number>;   // replica ID to counter

// Returns "before", "after", "equal", or "concurrent".
function compare(x: VV, y: VV): string {
  const ids = new Set([...Object.keys(x), ...Object.keys(y)]);
  let xLess = false, yLess = false;
  for (const id of ids) {
    const a = x[id] ?? 0, b = y[id] ?? 0;
    if (a < b) xLess = true;
    if (b < a) yLess = true;
  }
  if (xLess && yLess) return "concurrent";   // neither knows the other
  if (xLess) return "before";
  if (yLess) return "after";
  return "equal";
}
Update since the book

Amazon's DynamoDB has moved away from the leaderless model in its public product. Its global tables use last-writer-wins conflict resolution by default, and the timestamp is the key rule. Many databases now offer CRDTs (for example Redis Enterprise and Riak). Using a conflict rule means you must test it with concurrent writes, because a timestamp rule can silently drop data.

In simple words
  • Multi-leader: writes are fast and local, but concurrent writes conflict. Pick a rule that loses no data.
  • Leaderless: the client writes to many replicas. With w + r > n, you usually see the latest value.
  • Concurrent writes: keep siblings and merge them. Version vectors tell you which writes overwrote which.

6. Summary and glossary

Replication has four goals: high availability, disconnected operation, low latency, and read scalability. It sounds simple, but it needs careful thinking about concurrency and about everything that can go wrong.

Methods compared

Single-leaderMulti-leaderLeaderless
Who accepts writesOne leaderSeveral leadersAny replica (or a coordinator)
Failover needed?Yes, when the leader failsNo, other leaders continueNo failover
Concurrent writesBlocked or rejected by the leaderConflict, resolve laterSiblings, merge later
Read consistencyLeader reads are current. Follower reads lag.Depends on the ruleQuorum (w + r > n) gives usually current reads
Typical useMost relational databasesMulti-datacenter, offline appsHigh availability, large clusters

Glossary

TermSimple meaning
ReplicaA node that holds a copy of the data.
Leader, followerThe node that takes writes, and the nodes that copy its changes.
Replication logThe ordered list of changes that the leader sends.
Synchronous, asynchronousThe leader waits for the follower's confirmation, or does not wait.
Semi-synchronousOne synchronous follower, the others asynchronous.
FailoverPromoting a follower to be the new leader.
Split brainTwo nodes both believe they are the leader.
Replication lagHow far a follower is behind the leader, in time.
Eventual consistencyReplicas agree after writes stop, with no set time limit.
Read-your-writesA user always sees their own updates.
Monotonic readsA user never reads an older value after a newer one.
Consistent prefix readsWrites appear in the order that they happened.
Multi-leaderMore than one node accepts writes.
ConflictTwo concurrent writes to the same data.
ConvergenceAll replicas reach the same final value.
Last write wins (LWW)Keep the write with the biggest timestamp. Can lose data.
TopologyThe paths along which writes flow between leaders.
Leaderless, Dynamo-styleAny replica accepts writes. No leader.
Quorum (w, r, n)Number of acks for a write, number of replies for a read, and number of replicas. Overlap if w + r > n.
Read repairA read writes the newest value back to stale replicas.
Anti-entropyA background process that copies missing data between replicas.
Sloppy quorum, hinted handoffWrites go to any reachable nodes. Later they are moved to the home nodes.
Happens before, concurrentA knows about B, or neither knows about the other.
SiblingsConcurrent values kept by the server until the client merges them.
Version vectorA counter for each replica, used to detect concurrent writes.
TombstoneA marker that says "this was deleted", kept so that merges do not bring it back.

Part II continues with Chapter 6, Partitioning. It explains how to split data that is too large for one machine, and how to keep the load even across the parts.

7. Check yourself

Eight questions

Choose an answer. You see the explanation at once.

Exercises

Try each exercise before you open the hint. The answers are hidden until you ask for them.

Calculation · exercise 1

Quorum overlap

n = 5 replicas. Which pairs (w, r) guarantee that a read sees the latest acknowledged write? Check (2,2), (3,3), (3,2), and (1,5).

Show hint

The guarantee needs w + r greater than n.

Show answer

(2,2): 4 is not greater than 5, no guarantee. (3,3): 6 > 5, yes. (3,2): 5 is not greater than 5, no guarantee. (1,5): 6 > 5, yes, but every write must reach all five replicas to be acknowledged by one, so writes are slow.

Pen and paper · exercise 2

Which anomaly?

A user posts a comment, reloads, and it is missing because the reload hit a lagging follower. Then a second reload shows it, and a third does not. Name the two guarantees that are missing, and one fix for each.

Show hint

Look at each read on its own, then compare reads with each other.

Show answer

Read-your-writes is missing: fix by reading the user's own recent writes from the leader. Monotonic reads is missing (the comment disappeared between reads): fix by pinning each user to one replica.

Design · exercise 3

Multi-leader conflict rule

Two data centers both accept edits to a product price. Choose a conflict rule for the price, and explain what it loses or keeps.

Show hint

Last-write-wins is simple. Think about whether a discarded price matters.

Show answer

Keep both as siblings and ask a person or a business rule (for example, the lower price). Last-write-wins silently drops one price, which is data loss a shop cannot accept. A deterministic rule that is documented is the minimum.

Study guide made from Chapter 5 of Designing Data-Intensive Applications (O'Reilly) by Martin Kleppmann. All explanations, examples, code, and drawings on this page are new. The ideas and facts come from the book. The box named "Update since the book" comes from public PostgreSQL and DynamoDB documentation.