Skip to content

Vector Clocks: Detecting Concurrency

A vector clock is an array of counters, one entry per process, attached to every event. Each process increments its own entry on every event it stamps, and on every receive it takes the entry-wise maximum of its own vector and the vector carried by the message before incrementing. The result has a property no single integer can have: for any two events, comparing their vectors entry by entry tells you whether the first happened before the second, the second happened before the first, or neither, in which case the events are concurrent. The vector does not approximate the happened-before relation; it represents it exactly.

The problem it solves is conflict detection. A Lamport timestamp never inverts a causal pair, but it also orders concurrent events, so a replicated store that resolves writes by Lamport or wall-clock timestamp cannot distinguish “this write superseded that one” from “these two writers never saw each other”. The first case is safe to collapse; the second is a conflict that must be kept, merged, or deliberately discarded. A store that cannot tell them apart either loses concurrent writes silently or keeps every version forever. Vector clocks, and their data-oriented form, version vectors, are how multi-leader and leaderless stores, causal broadcast protocols, and dynamic race detectors make that distinction.

The core trade-off is exactness against metadata size. Exact concurrency detection costs one entry per actor that can generate events independently, and Charron-Bost’s 1991 lower bound, discussed on the happened-before page, shows that no entry-by-entry comparable timestamp can do it with fewer than n entries for n processes. Systems therefore spend their design effort on choosing who counts as an actor (processes, servers, clients), on keeping that set bounded, and on what to do when it grows anyway. Every production failure of vector clocks below is a consequence of one of those choices.

Each process Pi in a system of n processes holds a vector Vi of n integers, all zero initially. Fidge and Mattern published the mechanism independently in 1988; the rules are a direct generalization of Lamport’s. Before a local event or a send, Pi increments Vi[i] and stamps the event with a copy of Vi. A message carries the sender’s vector Vm. On receive, Pi sets every entry Vi[k] to max(Vi[k], Vm[k]) and then increments Vi[i], and the receive event takes the result.

The meaning of each entry is what makes the vector exact. After those rules run, V(e)[j] is the number of events on process Pj that are in the causal past of e, counting e itself when e is on Pj. Own-entry increments count local events; the entry-wise maximum on receive imports everything the sender knew about every other process. A vector clock is therefore a compressed description of an event’s entire causal past: because each process’s history is a sequence, “the first k events of Pj” is fully described by the single number k.

P3P2P1P3P2P1a: local, V=[1,0,0]c: receive m1, V=[2,1,0]e: local, V=[0,0,1]g: receive m2, V=[2,2,2]h: local, V=[3,0,0]j: receive m3, V=[4,2,3]m1, b: send, V=[2,0,0]1m2, f: send, V=[2,2,0]2m3, i: send, V=[2,2,3]3
P3P2P1P3P2P1a: local, V=[1,0,0]c: receive m1, V=[2,1,0]e: local, V=[0,0,1]g: receive m2, V=[2,2,2]h: local, V=[3,0,0]j: receive m3, V=[4,2,3]m1, b: send, V=[2,0,0]1m2, f: send, V=[2,2,0]2m3, i: send, V=[2,2,3]3

Three processes running the vector clock rules. Each send increments the sender’s own entry; each receive takes the entry-wise maximum with the carried vector and then increments the receiver’s own entry.

The receive of m2 is where P3 learns of P1 without ever exchanging a message with it: m2 carries [2,2,0], and P3’s vector jumps from [0,0,1] to [2,2,2], recording that two of P1’s events and two of P2’s are now in its causal past. Event h on P1 has vector [3,0,0]: it knows its own three events and nothing else, because P1 has heard nothing back since m1. Compare that with g = [2,2,2]. The first entry of h is larger, the second and third are smaller, so neither vector is below the other, and the events are concurrent. A Lamport clock on the same execution would give h and g two different integers and an order; the vector refuses to.

Two vectors compare entry by entry. V(a) <= V(b) means every entry of V(a) is at most the corresponding entry of V(b), and V(a) < V(b) means V(a) <= V(b) with at least one entry strictly smaller. Exactly one of four outcomes holds for any pair: the vectors are equal, which for event stamps means the same event; V(a) < V(b), so a -> b; V(b) < V(a), so b -> a; or neither dominates, so a || b. That last outcome is the one no scalar encoding can produce.

yes

no

yes

no

yes

no

Compare V(a) and V(b) entry by entry

Every entry equal?

Same causal past: identical versions

Every entry of V(a) at most V(b)?

a happened before b: b supersedes a

Every entry of V(b) at most V(a)?

b happened before a: a supersedes b

Concurrent: neither writer saw the other

Discard a safely

Discard b safely

Keep both, merge, or apply an explicit policy

yes

no

yes

no

yes

no

Compare V(a) and V(b) entry by entry

Every entry equal?

Same causal past: identical versions

Every entry of V(a) at most V(b)?

a happened before b: b supersedes a

Every entry of V(b) at most V(a)?

b happened before a: a supersedes b

Concurrent: neither writer saw the other

Discard a safely

Discard b safely

Keep both, merge, or apply an explicit policy

The comparison as a decision procedure for two versions of one data item. Only the concurrent branch requires a policy decision; the other three are mechanical.

For replicated data the diagram is the entire conflict-handling contract. The two dominance branches are what make vector-based replication safe to automate: an older version can be dropped without losing anything, because the newer one was written by someone who had seen it. The concurrent branch is where the application, a CRDT merge function, or a deliberate last-writer-wins rule takes over. Vector clocks do not resolve conflicts; they guarantee that the only conflicts left to resolve are real ones.

When event identities are known, a cheaper test exists. If a occurred on Pi, then a -> b holds if and only if V(a)[i] <= V(b)[i] and a is not b: b’s causal past contains the first V(b)[i] events of Pi, and a is the V(a)[i]-th of them. Causal broadcast and race detectors exploit this to compare in constant time against the one entry that matters.

The guarantee is the strong clock condition: a -> b if and only if V(a) < V(b). Both directions follow from the meaning of the entries.

Forward: every direct link of happened-before preserves dominance. Along program order, the process’s own entry increases and no other entry decreases. Along a message, the receiver takes the entry-wise maximum, so its vector is at least the sender’s in every entry, and then increments its own entry, which the sender’s vector had at a strictly smaller value because the sender had not yet seen the receive. Each link yields <= everywhere and < somewhere, and a causal chain is a sequence of links, so V(a) < V(b).

Converse: suppose V(a) <= V(b), with a on Pi and a different from b. Then V(b)[i] >= V(a)[i], so b’s causal past contains at least the first V(a)[i] events of Pi, and a is one of them. Either a is in b’s causal past, which is a -> b, or a is b. The converse is exactly the step that fails for a Lamport clock: a single integer does not say which process’s events it counts, so a larger value proves nothing about b’s past.

Like the Lamport proof, this uses nothing about real time or message delay, so it holds in the fully asynchronous model. It depends on two things implementations can break: entries never decrease, including across crashes, and every actor’s identity is unique. Every failure mode later on this page violates one of them.

Version Vectors: From Events to Replicated Data

Section titled “Version Vectors: From Events to Replicated Data”

A vector clock stamps every event, including every message receive. A replicated store does not care about most events; it cares about versions of data items and whether one version supersedes another. Parker and co-authors introduced version vectors in 1983 for exactly this, detecting mutual inconsistency between copies of a file in a partitioned system, five years before the Fidge-Mattern clocks. The mechanism is the same lattice with different increment rules. A version vector is attached to a data item, not to a process. It has one entry per replica that has coordinated an update to the item. A replica that accepts a write increments its own entry; replicas that synchronize take the entry-wise maximum; reads and message exchanges change nothing.

Because a version vector counts updates rather than events, two versions compare exactly as the decision procedure above describes, and anti-entropy between replicas becomes mechanical: dominated versions are dropped, concurrent ones are kept as siblings. The read path carries the other half of the protocol. A client that reads receives every current sibling plus a causal context, the merge of their version vectors, and returns that context with its next write. The context is how the store learns which versions the client saw and therefore which ones the write supersedes.

Client YReplica BReplica AClient XClient YReplica BReplica AClient Xv1 stored with VV A1v2 with A2 dominates A1, v1 droppedv3 with A1 B1 dominates A1, v1 droppedpar[Concurrent updates from the same context]keeps siblings v2 and v3VV A2 B2 dominates both siblings, conflict resolvedput(cart, v1), no context1replicate v1 with A12put(cart, v2), context A13put(cart, v3), context A14anti-entropy, A2 vs A1 B1, neither dominates5get(cart)6siblings v2 and v3, context A2 B17put(cart, merge of v2 and v3), context A2 B18
Client YReplica BReplica AClient XClient YReplica BReplica AClient Xv1 stored with VV A1v2 with A2 dominates A1, v1 droppedv3 with A1 B1 dominates A1, v1 droppedpar[Concurrent updates from the same context]keeps siblings v2 and v3VV A2 B2 dominates both siblings, conflict resolvedput(cart, v1), no context1replicate v1 with A12put(cart, v2), context A13put(cart, v3), context A14anti-entropy, A2 vs A1 B1, neither dominates5get(cart)6siblings v2 and v3, context A2 B17put(cart, merge of v2 and v3), context A2 B18

Version vectors in a leaderless store. Two clients write from the same context through different replicas; the store detects the concurrency, keeps both versions, and collapses them only when a write arrives whose context covers both.

Steps 3 and 4 are concurrent: both writes are based on the same context A1, and neither replica knows about the other’s write. The anti-entropy exchange in step 5 compares A2 with A1 B1 and finds that each has an entry the other lacks, so neither version is dropped. The resolution in step 8 is the important part: Client X’s merged write carries context A2 B1, which covers both siblings, so the new version A2 B2 dominates both and they are removed everywhere as replication spreads it. Had Client X written without the context, as a blind write, the store would have had to treat its write as concurrent with everything and add a third sibling. The quorum mechanics that decide which replicas take each read and write are covered in Leaderless Replication, and how Dynamo used this scheme for its shopping cart, including what happened in production, in Vector Clocks in the Dynamo Paper.

The entries of a version vector belong to actors, and the choice of actor decides both the metadata size and the precision. If clients are actors, each client increments its own entry, so the vector precisely records which writes each client saw, but its size grows with the number of distinct clients that have ever written the item, which in a web-facing store is unbounded. If replica servers are actors, the vector has at most one entry per replica, typically three, but a server now coordinates writes on behalf of many clients and must decide, for each incoming write, which existing siblings the write supersedes. With only one vector per item, the server cannot tell which sibling a client’s context refers to once several siblings share its entry.

Riak’s engineering write-up describes the consequence with vnode actors. Client Y writes “Bob” and client X concurrently writes “Sue”; both become siblings under one vector. Client Y then writes “Rita” with its old context, which should replace “Bob”. The vnode compares Y’s context with the item’s single vector, cannot attribute “Bob” to the older entry, and keeps “Rita” as a new sibling. Every subsequent write from a stale context adds another sibling, and the set grows without bound: sibling explosion, examined as a failure mode below.

Dotted version vectors, introduced by Preguiça, Baquero, Almeida, Fonte, and Gonçalves around 2010, fix the precision problem while keeping server actors. Each sibling is stored with a dot, the single (replica, counter) event that created it, alongside the item’s causal context. When a write arrives with a context, the server discards exactly those siblings whose dots the context covers, meaning the context’s entry for the dot’s replica is at least the dot’s counter, and keeps the rest. In the Riak example, “Bob” carries dot (a, 1), Y’s context covers it, and “Rita” replaces “Bob” instead of joining it. The metadata cost is one dot per sibling, a small constant. Riak adopted dotted version vectors in 2.0, where they are the default logical clock for bucket types; buckets outside bucket types keep classic vector clocks unless dvv_enabled is set.

Version vectors also summarize replica state, not only item versions. A replica that has applied a contiguous prefix of every other replica’s updates can describe everything it holds with one vector, and two replicas exchange vectors to compute exactly which updates each is missing. This is how many operation-based CRDT systems and some anti-entropy protocols find the delta to send without comparing whole data sets.

The second major use of vectors is enforcing causal delivery in group communication, the rule from the happened-before page that a message is delivered only after every message in its causal past. Birman, Schiper, and Stephenson gave the standard vector-based protocol in 1991. For broadcast within a fixed group, each process keeps a vector Vi counting the broadcasts it has delivered from each sender, its own included; the sender increments its own entry and attaches the vector. The receiver then holds a message back until two conditions are met.

on broadcast by Pi:
Vi[i] <- Vi[i] + 1
send (m, Vi) to every other process
on receive (m, Vm) from Pj at Pi:
wait until Vm[j] = Vi[j] + 1 # next message from Pj, none skipped
and Vm[k] <= Vi[k] for k != j # sender's dependencies already delivered
deliver m
Vi[j] <- Vm[j]
re-check the hold-back queue

The first condition enforces first-in-first-out (FIFO) order per sender; the second enforces that everything the sender had delivered from other processes before sending has also been delivered here. Together they are the constant-time dot test applied at delivery time. The protocol needs no acknowledgments and no extra messages, only the vector on each message, which is why it is attractive for small groups: replicated in-memory state, collaborative sessions, or a cluster-membership channel. Its limits are that the group must be known and fixed (membership changes need a view-change protocol), a lost message blocks every message that causally follows it until retransmission, and the hold-back queue grows during that block. Causal delivery as a consistency guarantee for clients, and session-level alternatives that avoid per-message vectors, are covered in Causal Consistency and Session Guarantees.

All numbers in this example are illustrative.

Causal broadcast first. Assume a group of 40 processes with 8-byte entries, an average payload of 200 bytes, and 50,000 messages per second across the group. The vector adds 40 * 8 = 320 bytes to every message, 160% of the payload, and 320 * 50,000 = 16 MB/s of metadata. Singhal and Kshemkalyani’s 1992 differential technique sends, to each destination, only the entries that changed since the last message to that destination, as (index, value) pairs. If a process typically learns of changes from three other processes between sends, each message carries 3 * (4 + 8) = 36 bytes and the overhead falls to about 1.8 MB/s. The technique requires FIFO channels and per-destination state, and it costs nothing in exactness.

Storage next. Assume 200 million items on three replicas. With clients as actors, an entry is a 16-byte client identifier plus an 8-byte counter, 24 bytes. If an item is written over its lifetime by 30 distinct clients on average, its version vector holds 720 bytes, and the store carries 200,000,000 * 720 = 144 GB of vectors per copy, 432 GB across the three replicas, much of it for clients that will never write again. With replica actors and dotted version vectors, three 16-byte entries plus one 16-byte dot per sibling is 64 bytes for an item with one sibling: 12.8 GB per copy. The ratio is not fixed, and it is the curve, not the point, that matters.

Version vector bytes per item vs. distinct writers (illustrative)15102050100200Distinct writers over item lifetime5000450040003500300025002000150010005000Bytes per item
Version vector bytes per item vs. distinct writers (illustrative)15102050100200Distinct writers over item lifetime5000450040003500300025002000150010005000Bytes per item

Upper line: clients as actors, 24 bytes per writer. Flat line: three replica actors plus one dot, 64 bytes, independent of the number of clients. Entry sizes are illustrative.

The client-actor line crosses the flat line at about three writers and keeps climbing linearly, so an item that is popular, such as a shared document, a counter, or a configuration key, pays the most. The flat line hides a slower growth of its own: when replicas are replaced, new replica identifiers join the actor set and the old ones never leave on their own, so an item that has lived through ten node replacements carries thirteen server entries unless retired actors are compacted. Every comparison is also linear in the number of entries, and a read coordinator compares replica responses on every read.

The family of causality trackers is larger than vector clocks, and each member discards a different part of the relation to save space.

TrackerDetects concurrencySizeActor churnWhen to prefer
Lamport timestampNoOne integerIrrelevantA deterministic total order is enough; conflicts need not be detected
Vector clock, one entry per processYes, for eventsOne entry per processNeeds a fixed or managed membershipCausal broadcast in small fixed groups; debugging and race detection
Version vector, client actorsYes, for versionsGrows with distinct writersUnbounded growthFew, long-lived writers per item, such as device sync
Version vector, server actorsPartly: false siblings under concurrencyOne entry per replicaGrows with node replacementsStores where clients rarely write concurrently, with read-before-write discipline
Dotted version vectorYes, for versionsOne entry per replica plus one dot per siblingGrows with node replacementsLeaderless or multi-leader stores that keep siblings
Interval tree clockYesAdapts to live actorsFork and join without global identifiersHighly dynamic actor sets with no identifier service
Hybrid logical clockNoOne integer plus a counterIrrelevantCausal timestamps that must also approximate wall time

Interval tree clocks (Almeida, Baquero, and Fonte, 2008) attack the actor-identity problem directly: actors split (“fork”) and recombine (“join”) a shared identifier space, so a new replica needs no registry and a retired one returns its share. They live mostly in research systems and standalone libraries. Hybrid logical clocks belong in the table as the contrast: they are what systems choose when they decide they do not need to detect concurrency.

Actor identity. Identifiers must be unique for the lifetime of any data that carries them, and an identifier must never be reused by a process that does not hold that identifier’s full history. A replica that loses its disk and rejoins under its old identifier with its counter reset is the most dangerous configuration a vector-based system can reach, as the danger aside below explains. Randomly generated identifiers per incarnation avoid the problem at the cost of a new actor per restart-with-data-loss.

Counter persistence. An actor’s own entry must never decrease. For a replica, the entry lives inside each item’s version vector, which is durable with the item; the risk lies in snapshots, restores from backup, and copy-based provisioning, which can bring back an old counter under a live identifier.

Pruning. Bounding vector size by dropping entries trades exactness for space. The Dynamo paper describes truncation: when the number of (node, counter) pairs reaches a threshold, which it puts at about ten, the oldest pair by stored timestamp is removed. Riak exposes four bucket properties for the same purpose, small_vclock, big_vclock, young_vclock, and old_vclock, which bound pruning by entry count and by entry age; its documentation lists 50 as the default for both count thresholds and one day for old_vclock. Pruning is only safe if a dropped entry can never be needed to decide a comparison, which no store can know.

Compaction of retired actors. An entry can be removed without loss once it is equal across every version on every replica and no longer distinguishes anything; establishing that needs a global agreement step, which is why most systems prune by heuristic instead.

Every failure below either breaks one of the two assumptions of the proof, monotonic entries and unique actors, or exhausts the space budget the mechanism needs.

Trigger: server-actor version vectors without dots, combined with clients writing from stale contexts, or clients writing without contexts at all. Symptoms: individual items grow from one version to dozens or hundreds of siblings; read latency and object size climb on hot keys; in extreme cases nodes run out of memory reading a single object.

Client Y"Replica a (server actor)"Client XClient Y"Replica a (server actor)"Client XBob, VV a1siblings Bob, Sue, VV a2a1 does not cover a2, Rita kept as new siblingBob not removed, siblings Bob, Sue, Rita, VV a3a3 covers the whole vector, all replaced by Annput Bob, no context1put Sue, no context2put Rita, context a1 from first write3get4Bob, Sue, Rita, context a35put Ann, context a36
Client Y"Replica a (server actor)"Client XClient Y"Replica a (server actor)"Client XBob, VV a1siblings Bob, Sue, VV a2a1 does not cover a2, Rita kept as new siblingBob not removed, siblings Bob, Sue, Rita, VV a3a3 covers the whole vector, all replaced by Annput Bob, no context1put Sue, no context2put Rita, context a1 from first write3get4Bob, Sue, Rita, context a35put Ann, context a36

Sibling explosion with one version vector per item and the server as actor. Step 3 should replace Bob, but the server has lost the association between Bob and the version a1.

Step 3 is where precision is lost. Client Y’s context a1 describes exactly the version it saw, Bob, but the item’s single vector is now a2, and the server has no record of which sibling corresponds to a1. It must conservatively keep everything, so Rita becomes a third sibling and Bob survives. Two writers interleaving like this add a sibling on almost every write. Steps 4 to 6 show why the condition is intermittent: a client that reads first and writes back with a context covering the whole vector clears everything at once, so the sibling count oscillates with traffic patterns, which makes it hard to reproduce. Detection: a histogram of sibling count per read and object size per read, with alerts on the high percentiles rather than the mean; logs of reads that return more than a small number of siblings, by key. Mitigation: dotted version vectors; client libraries that always read before writing and return the context; per-key rate limits on hot keys. Recovery: read the affected objects, resolve the siblings in the application, and write back with the full context, which collapses them in one step.

Trigger: a replica loses its state (disk failure, rebuilt from an old snapshot, reprovisioned from a template) and rejoins under its previous actor identifier with its counter at zero or at an older value. It then accepts writes and stamps them with entries other replicas have already seen. Symptoms: newly acknowledged writes through that replica disappear after anti-entropy or read repair, with no conflict reported, because peers judge them to be dominated by versions they already hold.

Detection: a replica whose own entry in a newly written version is lower than its entry in versions held by peers is proof of reuse; check it during anti-entropy and alert. Mitigation: incarnation-based identifiers (a fresh random identifier per data directory), a startup check that refuses writes until the replica has synchronized with peers, and operational runbooks that treat “replace node” and “rejoin node” as different procedures. Recovery is hard: the discarded writes may survive only in client logs or in hinted-handoff buffers.

Trigger: vector size exceeds the pruning threshold, typically on items written through many coordinators, for example during a failure that moves coordination to unusual nodes, or with client actors. Symptoms come in two kinds. Dropping an entry from the newer version breaks its dominance, so a version that should supersede becomes concurrent and siblings appear for writes that were causally ordered. Dropping an entry from one of two concurrent versions, as in the caution aside, can make the other appear to dominate it, and an acknowledged write is silently discarded. Detection: count pruning events per bucket or table; track the distribution of vector lengths and alert well before the threshold. Mitigation: bound the actor set (server actors, compaction of retired actors) so pruning never triggers in normal operation, and set pruning thresholds above the largest vector expected after a failover, not at the average.

Trigger: client actors with high client churn, or server actors over years of node replacement without compaction. Symptoms: object size and read latency grow slowly over months without any change in payload size; storage cost per item rises; serialization dominates CPU profiles on the read path. Detection: average and tail vector length per item over time, and the share of bytes spent on metadata. Mitigation: move from client to server actors with dots, compact retired actors during maintenance windows, or, if concurrency detection is not actually needed, stop tracking it and use a single sequencer per key.

Trigger: a system detects concurrency correctly and then resolves every conflict with last-writer-wins by wall-clock timestamp, often because the application never implemented sibling resolution. Symptoms are those of Eventual Consistency and LWW: acknowledged concurrent writes vanish. The system pays for vectors and gets the guarantees of timestamps. Detection: count conflicts resolved by the fallback rule. Mitigation: implement a merge function, adopt a CRDT, or admit the policy and drop the vectors.

FailureSymptom from outsideDetection signalMitigation
Sibling explosionHot keys grow huge, read latency and memory spikeSiblings per read, object size percentilesDotted version vectors, read-before-write, per-key rate limits
Actor reuse after data lossAcknowledged writes through one replica vanishOwn entry in new versions lower than peers holdFresh identifier per incarnation, sync before accepting writes
PruningFalse siblings, or silently lost concurrent writesPruning event count, vector length distributionBounded actor sets, thresholds above failover peaks
Actor churnSlow growth of object size and read CPUVector length and metadata share over timeServer actors, compaction of retired actors
Detected then discardedConcurrent writes lost despite vectorsConflicts resolved by fallback ruleMerge functions, CRDTs, or simpler design

Use vector clocks or version vectors when the system accepts writes to the same data at more than one place without coordination, as in multi-leader and leaderless replication or offline-capable clients that sync later, and when losing a concurrent write is unacceptable, so conflicts must be detected and kept or merged. The actor set must be bounded or made bounded: replica servers rather than clients, with dots to keep precision. Use per-process vector clocks for causal broadcast in small, fixed groups, and in tooling: dynamic data-race detectors such as FastTrack (Flanagan and Freund, 2009) track happens-before between threads with vector clocks optimized to scalar epochs for the common case, and log-visualization tools reconstruct space-time diagrams from vector stamps in ordinary logs.

Do not use them when one sequencer already orders the scope: a single-leader database, a Raft log, or one Kafka partition gives every write a position, and concurrency within that scope does not exist. Do not use client-actor vectors in a store with many writers per item; the metadata grows with the client population. Do not use them when the application will resolve every conflict with last-writer-wins anyway; a Lamport timestamp or hybrid logical clock gives the same outcome in eight bytes. Do not use them where the requirement is real-time order between clients that do not communicate; that is linearizability, and no causal encoding provides it. And do not use them when concurrency is detectable but not resolvable: if no merge function exists and no user can be asked, detecting the conflict only moves the data loss to a later, less visible point.

Three neighbors are routinely confused with vector clocks. Version vectors are not a different idea but the same lattice applied to data items, and much writing, including the Dynamo paper, calls them vector clocks. CRDTs are data types whose merge makes concurrency harmless; many use version vectors internally to track what has been seen, but the vectors detect concurrency and the data type resolves it. And causal consistency is a guarantee about what clients may observe; vectors are one way to implement it, and explicit dependency tracking, as discussed on the happened-before page, is another that scales differently.

  • Colin J. Fidge, Timestamps in Message-Passing Systems That Preserve the Partial Ordering, Australian Computer Science Conference, 1988.
  • Friedemann Mattern, Virtual Time and Global States of Distributed Systems, Parallel and Distributed Algorithms (workshop proceedings), North-Holland, 1989.
  • D. Stott Parker Jr. et al., Detection of Mutual Inconsistency in Distributed Systems, IEEE Transactions on Software Engineering, 1983.
  • Kenneth Birman, André Schiper, and Pat Stephenson, Lightweight Causal and Atomic Group Multicast, ACM Transactions on Computer Systems, 1991.
  • Nuno Preguiça, Carlos Baquero, Paulo Sérgio Almeida, Victor Fonte, and Ricardo Gonçalves, Dotted Version Vectors: Logical Clocks for Optimistic Replication, arXiv, 2010.
  • Paulo Sérgio Almeida, Carlos Baquero, and Victor Fonte, Interval Tree Clocks: A Logical Clock for Dynamic Systems, OPODIS, 2008.