Offline-first and sync

What is a vector clock?

A vector clock is a list of counters, one for each process or replica, attached to every event or version. Comparing two vector clocks shows whether one change happened before the other or whether they were made concurrently, which a single timestamp cannot tell you.

Learning objectives

After reading this article you will be able to:

  • Explain how a vector clock detects concurrency that a Lamport timestamp hides
  • Compare two vector clocks to tell ordered changes from concurrent ones
  • Describe how Dynamo and Riak cope with the growing size of vector clocks

The problem it solves

A Lamport timestamp gives every event a number, so that if one event could have caused another, the cause has the smaller number. The reverse is not true. A smaller number does not prove that one event came before the other; the two may have happened independently.

Friedemann Mattern pointed out that this loses information. Squeezing a partial order of events onto a line of integers makes events that were concurrent look as if they happened in a definite order. That does not matter for some uses, but it matters a great deal for sync. An offline app needs to know whether an incoming change replaces the local one, which is safe to apply, or was made concurrently with it, which is a conflict.

Vector clocks answer that question. Mattern and, independently, Fidge proposed them in 1988.

How it works

Each process keeps a vector with one entry per process. Mattern’s rules are short:

  1. Each process increments its own entry by one whenever an event happens.
  2. When it sends a message, it attaches its whole vector.
  3. When it receives a message, it takes the componentwise maximum of its own vector and the one it received, entry by entry.

The result is that each process carries its best knowledge of how far every other process has got. Take two phones, Ana’s and Ben’s. Ana makes an edit, so her entry rises. If she then syncs with Ben, his vector absorbs her entry and he knows about her edit. If instead Ben edits while they are apart, each vector has moved ahead in a different entry, and neither knows of the other’s change.

Comparing two clocks

Two vectors compare entry by entry:

  • If every entry in A is less than or equal to the matching entry in B, and they are not identical, then A happened before B.
  • If the reverse holds, B happened before A.
  • If each has at least one entry larger than the other’s, the two are concurrent.

Mattern showed that this comparison captures causality exactly: one event happened before another if and only if its vector is smaller. That is the property a single counter lacks, and it is why vector clocks can detect conflicts rather than hide them.

Version vectors in replicated data

Databases use the same structure to track versions of data rather than events. Mattern credits Parker and colleagues with version vectors, in which each copy of a file counts the updates made at each site. If two copies’ vectors are concurrent, they were modified independently and a conflict is flagged. If not, the copies can be reconciled without further action, and the result takes the maximum of both vectors.

Amazon’s Dynamo paper used vector clocks this way. Each version of an object carried a list of (node, counter) pairs. If one version’s counters were all less than or equal to another’s, it was an ancestor and could be forgotten. Otherwise the versions were in conflict, and both were returned to the application to reconcile.

Riak’s documentation describes the same behaviour. When vector clocks cannot decide which value is current, Riak can keep both as siblings for the application to resolve.

What vector clocks cost

The weakness is size. The vector needs an entry for every participant that has written, so it grows with the number of nodes. The authors of the hybrid logical clock paper call that space requirement prohibitive.

Real systems cut corners, with consequences:

  • Truncation. Dynamo dropped the oldest entry once a clock reached a threshold, which its authors gave as, say, ten pairs. They noted this could make reconciliation less accurate, because ancestry could no longer be worked out exactly.
  • Sibling growth. Riak found that with plain vector clocks, sibling values could be duplicated depending on delivery order, leading to what it calls sibling explosion. From version 2.0 it recommends dotted version vectors, which tag each value with the update that created it.

For mobile apps the size question is practical. Every device that writes becomes an entry, and devices come and go. Some libraries use a lighter structure for the part they need.

Yjs is one example. It keeps a state vector holding the next expected clock from each client, and sends it to another client so that only the missing changes come back. The Yjs README says this is similar to a version vector, but is used only to describe the state of the local document, not to track causality.

When you need one

Use a vector clock, or a version vector, when you must tell a replacement apart from a conflict. That includes keeping both versions for a person to choose, merging concurrent changes, and working out which changes a peer is missing.

If the rule is last writer wins, a single totally ordered timestamp is enough, because concurrent writes are resolved by discarding one anyway. Conflict-free replicated data types sit in between: Shapiro and colleagues build their multi-value register on version vectors precisely because a single timestamp cannot detect concurrency.

Frequently asked questions

What is the difference between a vector clock and a version vector?

They use the same structure. A vector clock usually timestamps events in a computation, while a version vector, as Parker and colleagues used it, is attached to each copy of a piece of data and counts the updates made at each site, so conflicting copies can be detected.

Do vector clocks need synchronised wall clocks?

No. They count events, not seconds. Riak's documentation puts it this way, saying vector clocks do not care whether something happened at 6 pm today or back in 1972, only about the sequence of events.

Sources

Build it with Offline Protocol

Offline Protocol's replicated documents merge concurrent edits when replicas communicate, with a fixed rule per collection type. The shared state guide includes a test for it. Edit the same map key on two devices independently, then confirm the documented conflict rule.

Read the shared state guide