You change a customer's name to “Alice Smith” through a server in Mumbai. A colleague changes the same record to “Alice Johnson” through a server in Singapore. Both requests succeed. A moment later, you refresh the page. Which name should you see?

“The latest one” sounds reasonable. But what does latest mean when neither server had seen the other's write?

That question is what makes vector clocks interesting. They help a database distinguish a copy that's behind from a version that developed independently. Understanding that distinction also explains why a system can get every replica to agree and still lose an update somebody cared about.

We'll follow one record through ordinary replication, a branching history, and reconciliation. Then we'll compare the original Amazon Dynamo design with DynamoDB Global Tables.

First, a replica that's simply behind

Suppose user:42 contains "Alice" in Mumbai, Delhi, and Singapore. These are names for replicas in our example; they aren't a description of an AWS deployment. The store lets different replicas accept writes and sends those changes to the others asynchronously.

Mumbai accepts "Alice Smith". For a while, Delhi and Singapore still return "Alice". Once the update reaches them, all three return "Alice Smith".

That's replication lag. There is one update to propagate, and the old value has an identifiable successor.

A counter can represent this history: v1 for "Alice", v2 for "Alice Smith". That works if writes to the record are serialized, for example by a single authority assigning versions. The number is useful because the system has already established an order. Merely adding a counter to each independent replica doesn't establish one.

Now let Mumbai and Singapore lose contact. Both start at v1. Mumbai writes "Alice Smith" as v2; Singapore writes "Alice Johnson" as v2. They have both followed the counter rule, but the counter has lost the distinction we need.

A linear history goes from Alice to Alice Smith. A branching history goes from Alice to two independent versions, Alice Smith and Alice Johnson, both numbered v2.
One successor can replace its parent. Two independent successors require a decision.

A partition makes the example obvious, but it isn't required. Normal replication delay is enough for two writers to read the same old version and produce different updates.

Causality: what had this write seen?

Imagine a client reads "Alice Smith" and then writes "Alice Marie Smith", carrying the version information from that read. The second update is based on the first. In the record's history, the first write happens before the second.

Compare that with the two clients who both read "Alice". Neither update incorporates the other. Those writes are concurrent, even if one request arrives several seconds later in physical time.

For events in a distributed system, happens-before follows local execution order, messages from sender to receiver, and chains of those relationships. Events with no such ordering are concurrent. Lamport's Time, Clocks, and the Ordering of Events in a Distributed System formalizes this distinction.

For a stored record, we need to preserve the relevant dependency: which version or versions the writer is replacing. A server receiving some newer data in the background doesn't prove that a client editing an older form has seen it.

This is why causal context travels with the data. A read returns the value and its version information; an update returns that context with the proposed new value. We'll come back to what happens when the client reads more than one version.

A clock with more than one counter

Instead of storing one number, attach a map of actor IDs to counters:

{ "M": 2, "S": 1 }

Here M means Mumbai and S means Singapore. For this example, the vector summarizes this record's history through Mumbai's second update and Singapore's first. It doesn't count requests across the whole database, and it contains no time of day.

There is a terminology wrinkle. General vector clocks can timestamp process events, including message receipts. What we're building here is an object version vector, tracking updates to a record. Dynamo calls its object metadata “vector clocks,” so that is the name you'll often encounter in this context. Copying an existing object version to another replica does not create a new application version.

We'll use two stable actors and a simple history in which each actor extends its own previous updates. Real stores must also handle retries, stale client contexts, actor restarts, and changing membership; this example isn't a complete storage protocol.

Start from a shared version with clock {M:1, S:1}. That starting point represents an earlier history containing one update from each actor. For this walk-through, we don't need its contents beyond "Alice".

StepValueClock
Both replicas have the starting versionAlice{M:1, S:1}
Mumbai updates that versionAlice Smith{M:2, S:1}
Singapore receives a copyAlice Smith{M:2, S:1}
A client reads it and updates through SingaporeAlice Marie Smith{M:2, S:2}

The actor creating an update advances its own component. The other component retains the history already known. Notice that Singapore's replication step didn't advance the stored version's clock.

Compare the last two clocks. Mumbai's component stays at 2; Singapore's increases from 1 to 2. The final clock is at least as large in every component and strictly larger in one. We say it dominates the previous clock.

Under this versioning model, the new version supersedes the old one. The store can remove the old version from its set of current candidates. That doesn't mean the old value has been preserved for audit or undo; keeping history is a separate feature.

Comparing two versions

During the partition, both replicas start from {M:1, S:1}. Mumbai creates {M:2, S:1} and Singapore creates {M:1, S:2}.

Mumbai's version is ahead in M and behind in S. Singapore's is the reverse. Neither contains the full history represented by the other.

The vector 2,2 dominates 2,1 because neither component is smaller. The vectors 2,1 and 1,2 are concurrent because each is ahead in a different component.
Compare every component. A larger total or one larger counter is not enough.

There are four outcomes when comparing X and Y:

RelationshipRuleWhat the store knows
X precedes YEvery X component is at most Y's; at least one is smallerY supersedes X
Y precedes XEvery Y component is at most X's; at least one is smallerX supersedes Y
EqualEvery component matchesThe same causal summary; neither is a strict successor
ConcurrentX is ahead somewhere and Y is ahead elsewhereNeither supersedes the other

Treat a missing component as zero, and compare by actor ID. {M:2} and {M:2, S:0} are equivalent summaries. Don't compare the vectors as strings or add their counters together.

Equality matters: “neither dominates” alone is not a sufficient test for concurrency if domination means strictly newer. Also, equal metadata is not a comparison of payload bytes. In our simple model, copies of a version share a clock; systems that summarize multiple siblings under one context need more information to identify each value.

With the comparison rule in place, the database can retain both independent values as siblings. But it still cannot decide which surname Alice intended. A clock can describe the history without understanding the value.

Completing the merge, including its clock

The missing step in many explanations is what happens after the application receives siblings.

Suppose it reads both names and their contexts:

  • "Alice Smith" at {M:2, S:1}
  • "Alice Johnson" at {M:1, S:2}

For a name, the application might ask a person to confirm the right value. Assume they choose "Alice Smith". Even though that text matches one sibling, this is a new decision made with knowledge of both.

First, combine the contexts by taking the maximum of each component: {M:2, S:2}. Then, if Mumbai handles the new write, it advances M, producing {M:3, S:2}.

Two sibling values, Alice Smith at 2,1 and Alice Johnson at 1,2, feed into a combined context 2,2. The application chooses Alice Smith and writes through Mumbai, creating 3,2, which supersedes both siblings.
Merging the clocks records what was considered. The application still has to choose or compute the value.

The new clock dominates both siblings. Once replicas receive it, they can replace both current candidates with the resolved version. Taking the maximum was a metadata operation; it did not merge the names.

This is the read–reconcile–write pattern described in section 4.4 of the original Dynamo paper. The client sends back the context of the versions it read, allowing the new write to supersede them.

If another update was created without seeing this resolution, there may be another sibling to handle. Reconciliation covers the histories it actually considered, not every write that might exist elsewhere.

Why a shopping cart is only partly easier

Start with {Laptop}. One client adds a mouse; another adds a keyboard. Taking the union of {Laptop, Mouse} and {Laptop, Keyboard} preserves both additions.

Now change the example: one client removes the laptop while another adds the keyboard to an old cart. Union brings the laptop back. Quantity changes make the rule harder still: setting a quantity to two differs from adding two units.

The Dynamo paper explicitly describes removed cart items resurfacing under its reconciliation approach. “Merge the carts” therefore needs a definition of what additions, removals, and quantities mean; it isn't a general solution by itself.

CRDTs—conflict-free replicated data types—offer data structures with defined merge behavior. For example, an observed-remove set tracks individual additions so a removal can refer to the additions it observed. The application still has to accept the chosen semantics. Riak's data type documentation is a useful starting point.

What a timestamp buys you

Another policy is last writer wins (LWW): attach an ordering key to each version and keep the greatest one. With a timestamp-based policy, 10:00:05 wins over 10:00:01.

That can produce one deterministic result even for concurrent versions. It doesn't establish that the winning writer saw the losing update.

The ordering must handle ties consistently across replicas. A system you design might compare (timestamp, unique write ID), using the ID as a deterministic tie-breaker. That is an illustrative policy, not a claim about DynamoDB's internals.

Physical clocks add another concern: skew. A write made earlier in real time can carry a larger timestamp if its clock is ahead. Synchronization reduces that risk; it doesn't make physical timestamps a record of causal dependencies. Even with perfect physical clocks, two independent edits still need a policy.

Scalar logical clocks deserve a mention here too. Lamport clocks can respect happens-before: if A happens before B, A gets a smaller logical timestamp. The reverse implication does not hold. A smaller Lamport timestamp alone cannot tell you whether A and B were causally related or concurrent.

The cost of picking one whole item

Consider a record containing both a phone number and an email address. Two clients read it. Mumbai updates the phone; Singapore updates the email. Each submits a complete item based on its old read.

Mumbai changes the phone while Singapore changes the email in separate copies of a contact record. Whole-item LWW selects Singapore's copy, retaining the new email but reverting the phone to its old number.
The replicas agree on the winning item, but the phone update has disappeared.

If Singapore's complete item wins, the phone update is gone. Nobody asked to undo it; the winning copy still contained the old number.

The unit of conflict resolution matters. A policy applied per field can preserve these particular disjoint edits, though concurrent edits to the same field still need a rule. It may also allow combinations that violate an invariant spanning several fields.

LWW is useful when replacing one state with another is acceptable and concurrent losses fit the application's requirements. Replicas converging on one value is a narrower guarantee than preserving every user's action.

Dynamo and DynamoDB make different choices

Amazon's 2007 Dynamo used object version clocks and could return divergent versions for application reconciliation. Today's managed DynamoDB service has its own architecture and APIs. Sharing a name is not enough to transfer the original paper's mechanisms to it.

For DynamoDB Global Tables, the consistency mode matters.

Multi-Region eventual consistency: MREC

MREC accepts regional writes and replicates them asynchronously. Conflicts on the same item are resolved using LWW and internal per-item timestamps. AWS describes the timestamp as a private system property; an application's updatedAt attribute isn't the replication clock. See AWS's global-table design guidance.

This is item-level resolution, so updates to different attributes of the same item can still conflict. You don't receive Dynamo-style siblings to reconcile through the normal item-read API.

The exact-timestamp-tie question is interesting, but the cited documentation doesn't specify a secondary tie-breaking key. Assuming it compares Region IDs would be speculation. The useful contract here is the documented LWW behavior, including its potential to discard concurrent changes.

There is another trap: a conditional write in MREC checks the regional copy. Two Regions can independently satisfy a condition on an old version before replication catches up. A local optimistic-locking check doesn't become a global lock. AWS documents this scope in How DynamoDB global tables work.

Multi-Region strong consistency: MRSC

MRSC synchronously replicates a change to at least one other Region before acknowledging it. Strongly consistent reads return the latest item across participating replicas. Concurrent modifications in different Regions can produce ReplicatedWriteConflictException; applications must handle retries. This mode has different latency and deployment constraints, described in the same AWS documentation.

So “DynamoDB resolves conflicts with LWW” needs the MREC qualifier. MRSC coordinates writes before acknowledging them. It does not expose the same asynchronously diverging histories for later LWW resolution.

Coordination belongs before the branch

A system can serialize writes through a leader or a consensus protocol. Concurrent requests then have an agreed order. It may accept both sequentially, or reject one if a condition such as “update only if the version is still 7” fails.

Strong consistency alone doesn't protect a read–modify–write operation split across requests. Two clients can read the same value and later submit unconditional replacements in sequence. To reject a stale edit, the application needs an appropriate atomic conditional update or transaction.

Coordination also needs communication. During a partition, some replicas may be unable to accept writes under the required guarantee. Cross-region coordination adds latency even when the network is healthy.

That is a decision made before acknowledging writes. Once two independent writes have succeeded, adding coordination doesn't retroactively merge their contents. You still have to choose, reconcile, or retain the existing versions.

What the small examples leave out

Vector metadata has a cost. More actors mean more components, and retaining unresolved siblings uses storage and makes reads more complicated. Actor IDs and counters must also survive restarts safely; reusing an old identity with a reset counter can invalidate the history.

Pruning metadata is not free either. Dynamo bounded its clocks by dropping old entries, with a corresponding loss of precision in ancestry detection. Later designs address other problems: Riak's dotted version vectors associate individual updates with values, helping distinguish real siblings from false concurrency when servers act on behalf of multiple clients.

A clock also doesn't transport data. Replication and repair mechanisms must deliver versions to the replicas that missed them. Eventual convergence depends on that delivery and a resolution policy that reaches compatible results. A vector attached to a permanently isolated copy cannot make it catch up.

Those details matter when building a database. For reading its guarantees, the useful questions are more immediate: what history does it track, what is the unit of conflict, and who decides what survives?

Try the comparison yourself

Use the same actor order, (M, S). For each pair, decide whether one version supersedes the other or whether they are concurrent before reading the answers.

  1. A = (3, 2), B = (3, 4)
  2. A = (4, 2), B = (3, 3)
  3. A = (2, 1), B = (2, 1)
  4. You reconcile (4, 2) and (3, 3) through Singapore. What is the new clock, assuming no other updates?

Answers: In 1, B dominates A. In 2, the versions are concurrent: a larger Mumbai counter doesn't cancel out a smaller Singapore counter. In 3, the summaries are equal, not concurrent. In 4, the combined context is (4, 3) and the new write is (4, 4) after Singapore advances its component.

Return to Alice's two names. The useful first question is whether one edit was based on the other. If it was, the history gives us a successor. If it wasn't, choosing a name is a policy decision. Vector clocks make that distinction visible; the application still has to decide what it is willing to lose.

Sources and further reading

AWS documentation checked on 13 September 2026. The diagrams are original illustrations of the examples in this article.