Vector Clocks vs Logical Clocks

In distributed systems, understanding causality between events is fundamental for correctness. While Lamport's logical clocks provide partial ordering, they cannot detect concurrency. Vector clocks ex

Introduction#

In distributed systems, understanding causality between events is fundamental for correctness. While Lamport’s logical clocks provide partial ordering, they cannot detect concurrency. Vector clocks extend logical clocks to capture causal relationships completely, enabling detection of concurrent events and conflict resolution in distributed systems.

The Limitations of Lamport Clocks#

Lamport clocks guarantee that if event a happened-before event b, then C(a) < C(b). However, the converse is not true: if C(a) < C(b), we cannot conclude whether a → b or if they are concurrent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Python: Lamport clock limitation demonstration
class LamportClockDemo:
    """Shows why Lamport clocks cannot detect concurrency"""
    
    def __init__(self):
        self.p1_clock = 0
        self.p2_clock = 0
        self.events = []
    
    def p1_event(self, action):
        self.p1_clock += 1
        event = {'process': 'P1', 'clock': self.p1_clock, 'action': action}
        self.events.append(event)
        return event
    
    def p2_event(self, action):
        self.p2_clock += 1
        event = {'process': 'P2', 'clock': self.p2_clock, 'action': action}
        self.events.append(event)
        return event
    
    def send_p1_to_p2(self):
        self.p1_clock += 1
        send_clock = self.p1_clock
        self.p2_clock = max(self.p2_clock, send_clock) + 1
        return {'from': 'P1', 'to': 'P2', 'send_clock': send_clock, 'recv_clock': self.p2_clock}

# Demonstrate the problem
demo = LamportClockDemo()

# Two concurrent events on different processes
e1 = demo.p1_event("Write X")    # P1: clock=1
e2 = demo.p2_event("Write X")    # P2: clock=1

# Both have same timestamp but are concurrent!
# Lamport clocks cannot tell us they're concurrent
print(f"Event 1: {e1['process']} at {e1['clock']}")
print(f"Event 2: {e2['process']} at {e2['clock']}")
print(f"Same timestamp but CONCURRENT - Lamport clocks cannot detect this!")

# Later events
e3 = demo.p1_event("Read Y")     # P1: clock=2
e4 = demo.p2_event("Read Y")     # P2: clock=2

# Problem: We can't distinguish between:
# 1. e1 → e3 (causal)
# 2. e1 || e3 (concurrent)

Vector Clocks: Complete Causality Tracking#

Vector clocks solve this by maintaining a vector of logical timestamps, one per process in the system. Each process tracks its own logical time and the last known time of every other process.

Vector Clock Structure#

For N processes, each vector clock is an array of N integers:

  • VC[i]: Process i’s current logical time
  • VC[j]: Process i’s last known logical time of process j

Vector Clock Rules#

Local Event:

  • Increment own counter: VC[i]++

Send Message:

  • Increment own counter: VC[i]++
  • Send entire vector with message

Receive Message:

  • Update vector: VC[i] = max(VC[i], message.VC[i]) for all i
  • Increment own counter: VC[self]++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Java/Spring Boot: Complete Vector Clock implementation
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class VectorClock implements Cloneable {
    private final int[] vector;
    private final int processId;
    private final int numProcesses;
    private final ReentrantLock lock = new ReentrantLock();
    
    public VectorClock(int processId, int numProcesses) {
        this.processId = processId;
        this.numProcesses = numProcesses;
        this.vector = new int[numProcesses];
        Arrays.fill(vector, 0);
    }
    
    /**
     * Increment clock for local event
     */
    public VectorClock tick() {
        lock.lock();
        try {
            vector[processId]++;
            return this.clone();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * Update clock when receiving message
     */
    public VectorClock update(VectorClock messageVC) {
        lock.lock();
        try {
            for (int i = 0; i < numProcesses; i++) {
                vector[i] = Math.max(vector[i], messageVC.vector[i]);
            }
            vector[processId]++;
            return this.clone();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * Check if this clock happened before another
     * Returns true if this ≤ other and this ≠ other
     */
    public boolean happenedBefore(VectorClock other) {
        boolean atLeastOneLess = false;
        
        for (int i = 0; i < numProcesses; i++) {
            if (this.vector[i] > other.vector[i]) {
                return false;  // Not happened before
            }
            if (this.vector[i] < other.vector[i]) {
                atLeastOneLess = true;
            }
        }
        
        return atLeastOneLess;
    }
    
    /**
     * Check if events are concurrent
     * Returns true if neither happened before the other
     */
    public boolean isConcurrentWith(VectorClock other) {
        return !this.happenedBefore(other) && 
               !other.happenedBefore(this) &&
               !this.equals(other);
    }
    
    /**
     * Check if clocks are equal
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof VectorClock)) return false;
        VectorClock other = (VectorClock) obj;
        return Arrays.equals(this.vector, other.vector);
    }
    
    @Override
    public VectorClock clone() {
        VectorClock cloned = new VectorClock(this.processId, this.numProcesses);
        System.arraycopy(this.vector, 0, cloned.vector, 0, this.numProcesses);
        return cloned;
    }
    
    @Override
    public String toString() {
        return Arrays.toString(vector);
    }
    
    public int[] getVector() {
        return vector.clone();
    }
}

@Service
public class DistributedEventTracker {
    private final VectorClock clock;
    private final int processId;
    private final Map<String, Event> eventLog = new ConcurrentHashMap<>();
    
    @Autowired
    public DistributedEventTracker(
        @Value("${node.id}") int processId,
        @Value("${cluster.size}") int clusterSize
    ) {
        this.processId = processId;
        this.clock = new VectorClock(processId, clusterSize);
    }
    
    public Event logLocalEvent(String eventType, String data) {
        VectorClock timestamp = clock.tick();
        Event event = new Event(processId, timestamp, eventType, data);
        eventLog.put(event.getId(), event);
        
        System.out.printf("[Node %d] Local event: %s at %s%n", 
            processId, eventType, timestamp);
        
        return event;
    }
    
    public Message sendMessage(int recipient, String data) {
        VectorClock timestamp = clock.tick();
        Message msg = new Message(processId, recipient, timestamp.clone(), data);
        
        System.out.printf("[Node %d] Sending to Node %d at %s%n", 
            processId, recipient, timestamp);
        
        return msg;
    }
    
    public Event receiveMessage(Message msg) {
        VectorClock timestamp = clock.update(msg.getVectorClock());
        Event event = new Event(
            processId,
            timestamp,
            "MESSAGE_RECEIVED",
            String.format("From Node %d: %s", msg.getSender(), msg.getData())
        );
        eventLog.put(event.getId(), event);
        
        System.out.printf("[Node %d] Received from Node %d at %s%n",
            processId, msg.getSender(), timestamp);
        
        return event;
    }
    
    /**
     * Detect if two events are concurrent
     */
    public boolean areConcurrent(String eventId1, String eventId2) {
        Event e1 = eventLog.get(eventId1);
        Event e2 = eventLog.get(eventId2);
        
        if (e1 == null || e2 == null) {
            throw new IllegalArgumentException("Event not found");
        }
        
        return e1.getVectorClock().isConcurrentWith(e2.getVectorClock());
    }
    
    /**
     * Determine causal relationship between events
     */
    public CausalRelation getCausalRelation(String eventId1, String eventId2) {
        Event e1 = eventLog.get(eventId1);
        Event e2 = eventLog.get(eventId2);
        
        if (e1.getVectorClock().happenedBefore(e2.getVectorClock())) {
            return CausalRelation.HAPPENED_BEFORE;
        } else if (e2.getVectorClock().happenedBefore(e1.getVectorClock())) {
            return CausalRelation.HAPPENED_AFTER;
        } else if (e1.getVectorClock().equals(e2.getVectorClock())) {
            return CausalRelation.IDENTICAL;
        } else {
            return CausalRelation.CONCURRENT;
        }
    }
}

enum CausalRelation {
    HAPPENED_BEFORE,
    HAPPENED_AFTER,
    CONCURRENT,
    IDENTICAL
}

Comparing Vector Clocks and Lamport Clocks#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Node.js: Side-by-side comparison
class LamportClock {
    constructor(processId) {
        this.processId = processId;
        this.counter = 0;
    }
    
    tick() {
        this.counter++;
        return this.counter;
    }
    
    update(messageTime) {
        this.counter = Math.max(this.counter, messageTime) + 1;
        return this.counter;
    }
    
    // Lamport clocks CANNOT compare for concurrency
    isConcurrent(time1, time2) {
        return "UNKNOWN - Lamport clocks cannot determine concurrency";
    }
}

class VectorClockNode {
    constructor(processId, numProcesses) {
        this.processId = processId;
        this.vector = new Array(numProcesses).fill(0);
    }
    
    tick() {
        this.vector[this.processId]++;
        return [...this.vector];
    }
    
    update(messageVector) {
        for (let i = 0; i < this.vector.length; i++) {
            this.vector[i] = Math.max(this.vector[i], messageVector[i]);
        }
        this.vector[this.processId]++;
        return [...this.vector];
    }
    
    // Vector clocks CAN determine concurrency
    happenedBefore(vc1, vc2) {
        let atLeastOneLess = false;
        for (let i = 0; i < vc1.length; i++) {
            if (vc1[i] > vc2[i]) return false;
            if (vc1[i] < vc2[i]) atLeastOneLess = true;
        }
        return atLeastOneLess;
    }
    
    isConcurrent(vc1, vc2) {
        return !this.happenedBefore(vc1, vc2) && 
               !this.happenedBefore(vc2, vc1) &&
               !this.areEqual(vc1, vc2);
    }
    
    areEqual(vc1, vc2) {
        return vc1.every((val, idx) => val === vc2[idx]);
    }
}

// Demonstration
console.log("=== Comparison Demo ===\n");

// Lamport clocks
const lp1 = new LamportClock('P1');
const lp2 = new LamportClock('P2');

const lt1 = lp1.tick();  // P1: 1
const lt2 = lp2.tick();  // P2: 1

console.log("Lamport Clocks:");
console.log(`Event on P1: timestamp=${lt1}`);
console.log(`Event on P2: timestamp=${lt2}`);
console.log(`Are they concurrent? ${lp1.isConcurrent(lt1, lt2)}\n`);

// Vector clocks
const vp1 = new VectorClockNode(0, 2);
const vp2 = new VectorClockNode(1, 2);

const vt1 = vp1.tick();  // P1: [1, 0]
const vt2 = vp2.tick();  // P2: [0, 1]

console.log("Vector Clocks:");
console.log(`Event on P1: vector=${JSON.stringify(vt1)}`);
console.log(`Event on P2: vector=${JSON.stringify(vt2)}`);
console.log(`Are they concurrent? ${vp1.isConcurrent(vt1, vt2)}`);
console.log(`Did P1 happen before P2? ${vp1.happenedBefore(vt1, vt2)}`);
console.log(`Did P2 happen before P1? ${vp1.happenedBefore(vt2, vt1)}`);

Complete Example Scenario#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import copy
from typing import List, Dict, Optional

class VectorClock:
    """Complete vector clock implementation"""
    
    def __init__(self, process_id: int, num_processes: int):
        self.process_id = process_id
        self.vector = [0] * num_processes
    
    def tick(self) -> List[int]:
        """Increment own counter for local event"""
        self.vector[self.process_id] += 1
        return copy.deepcopy(self.vector)
    
    def update(self, message_vector: List[int]) -> List[int]:
        """Update clock upon receiving message"""
        for i in range(len(self.vector)):
            self.vector[i] = max(self.vector[i], message_vector[i])
        self.vector[self.process_id] += 1
        return copy.deepcopy(self.vector)
    
    @staticmethod
    def happened_before(vc1: List[int], vc2: List[int]) -> bool:
        """Check if vc1 happened before vc2"""
        at_least_one_less = False
        for i in range(len(vc1)):
            if vc1[i] > vc2[i]:
                return False
            if vc1[i] < vc2[i]:
                at_least_one_less = True
        return at_least_one_less
    
    @staticmethod
    def is_concurrent(vc1: List[int], vc2: List[int]) -> bool:
        """Check if events are concurrent"""
        return (not VectorClock.happened_before(vc1, vc2) and 
                not VectorClock.happened_before(vc2, vc1) and
                vc1 != vc2)
    
    def get_vector(self) -> List[int]:
        return copy.deepcopy(self.vector)

class DistributedProcess:
    """Process in distributed system with vector clock"""
    
    def __init__(self, process_id: int, num_processes: int):
        self.process_id = process_id
        self.clock = VectorClock(process_id, num_processes)
        self.events = []
        self.messages_sent = []
        self.messages_received = []
    
    def local_event(self, event_type: str, data: str) -> Dict:
        """Execute local event"""
        timestamp = self.clock.tick()
        event = {
            'process': self.process_id,
            'type': event_type,
            'data': data,
            'vector_clock': timestamp
        }
        self.events.append(event)
        print(f"[P{self.process_id}] {event_type}: {data} | VC={timestamp}")
        return event
    
    def send_message(self, recipient: 'DistributedProcess', data: str) -> Dict:
        """Send message to another process"""
        timestamp = self.clock.tick()
        message = {
            'sender': self.process_id,
            'recipient': recipient.process_id,
            'data': data,
            'vector_clock': timestamp
        }
        self.messages_sent.append(message)
        print(f"[P{self.process_id}] SEND to P{recipient.process_id}: {data} | VC={timestamp}")
        
        # Simulate message delivery
        recipient.receive_message(message)
        return message
    
    def receive_message(self, message: Dict) -> Dict:
        """Receive message and update clock"""
        timestamp = self.clock.update(message['vector_clock'])
        received_event = {
            'process': self.process_id,
            'type': 'RECEIVE',
            'from': message['sender'],
            'data': message['data'],
            'vector_clock': timestamp
        }
        self.messages_received.append(received_event)
        self.events.append(received_event)
        print(f"[P{self.process_id}] RECEIVE from P{message['sender']}: {message['data']} | VC={timestamp}")
        return received_event

# Simulate distributed system with 3 processes
print("=== Vector Clock Distributed System Simulation ===\n")

p0 = DistributedProcess(0, 3)
p1 = DistributedProcess(1, 3)
p2 = DistributedProcess(2, 3)

# Execute distributed operations
e1 = p0.local_event("START", "Initialize P0")       # P0: [1, 0, 0]
e2 = p1.local_event("START", "Initialize P1")       # P1: [0, 1, 0]
e3 = p0.send_message(p1, "Request data")            # P0: [2, 0, 0] -> P1: [2, 2, 0]
e4 = p2.local_event("START", "Initialize P2")       # P2: [0, 0, 1]
e5 = p1.local_event("PROCESS", "Handle request")    # P1: [2, 3, 0]
e6 = p1.send_message(p2, "Query database")          # P1: [2, 4, 0] -> P2: [2, 4, 2]
e7 = p2.send_message(p0, "Database result")         # P2: [2, 4, 3] -> P0: [3, 4, 3]
e8 = p1.send_message(p0, "Response ready")          # P1: [2, 5, 0] -> P0: [3, 5, 3]

# Analyze causality
print("\n=== Causality Analysis ===\n")

def analyze_events(event1, event2, name1, name2):
    vc1 = event1['vector_clock']
    vc2 = event2['vector_clock']
    
    print(f"{name1}: VC={vc1}")
    print(f"{name2}: VC={vc2}")
    
    if VectorClock.happened_before(vc1, vc2):
        print(f"Result: {name1}{name2} (happened-before)")
    elif VectorClock.happened_before(vc2, vc1):
        print(f"Result: {name2}{name1} (happened-before)")
    elif vc1 == vc2:
        print(f"Result: {name1}{name2} (identical)")
    else:
        print(f"Result: {name1} || {name2} (concurrent)")
    print()

# Check various relationships
analyze_events(e1, e2, "P0 START", "P1 START")
analyze_events(e1, e3, "P0 START", "P0 send to P1")
analyze_events(e2, e5, "P1 START", "P1 PROCESS")
analyze_events(e4, e1, "P2 START", "P0 START")
analyze_events(e3, e7, "P0 send to P1", "P2 send to P0")

Storage Overhead Comparison#

Clock Type Storage per Node Message Overhead Total System
Lamport O(1) 1 integer O(N)
Vector O(N) N integers O(N²)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// C#: Storage overhead analysis
public class ClockStorageAnalysis
{
    public static void AnalyzeOverhead(int numProcesses)
    {
        // Lamport clock storage
        int lamportBytes = sizeof(long);  // 8 bytes per process
        int lamportTotal = lamportBytes * numProcesses;
        
        // Vector clock storage
        int vectorBytes = sizeof(int) * numProcesses;  // N integers per process
        int vectorTotal = vectorBytes * numProcesses;
        
        Console.WriteLine($"=== Storage Overhead for {numProcesses} Processes ===");
        Console.WriteLine($"Lamport Clock:");
        Console.WriteLine($"  Per process: {lamportBytes} bytes");
        Console.WriteLine($"  Total system: {lamportTotal} bytes");
        Console.WriteLine();
        
        Console.WriteLine($"Vector Clock:");
        Console.WriteLine($"  Per process: {vectorBytes} bytes");
        Console.WriteLine($"  Total system: {vectorTotal} bytes");
        Console.WriteLine();
        
        Console.WriteLine($"Overhead ratio: {vectorTotal / (double)lamportTotal:F2}x");
        Console.WriteLine();
        
        // Message overhead
        Console.WriteLine("Message Overhead:");
        Console.WriteLine($"  Lamport: {lamportBytes} bytes per message");
        Console.WriteLine($"  Vector: {vectorBytes} bytes per message");
        Console.WriteLine($"  Additional: {vectorBytes - lamportBytes} bytes ({((vectorBytes - lamportBytes) / (double)lamportBytes * 100):F1}% increase)");
    }
}

// Example usage
ClockStorageAnalysis.AnalyzeOverhead(10);    // 10 processes
ClockStorageAnalysis.AnalyzeOverhead(100);   // 100 processes
ClockStorageAnalysis.AnalyzeOverhead(1000);  // 1000 processes

/*
Output:
=== Storage Overhead for 10 Processes ===
Lamport Clock:
  Per process: 8 bytes
  Total system: 80 bytes

Vector Clock:
  Per process: 40 bytes
  Total system: 400 bytes

Overhead ratio: 5.00x

=== Storage Overhead for 1000 Processes ===
Lamport Clock:
  Per process: 8 bytes
  Total system: 8000 bytes

Vector Clock:
  Per process: 4000 bytes
  Total system: 4000000 bytes

Overhead ratio: 500.00x
*/

Real-World Applications#

Distributed Version Control (Git-like)#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Service
public class DistributedVersionControl {
    private final VectorClock clock;
    private final Map<String, Commit> commits = new ConcurrentHashMap<>();
    private final int nodeId;
    
    public DistributedVersionControl(int nodeId, int numNodes) {
        this.nodeId = nodeId;
        this.clock = new VectorClock(nodeId, numNodes);
    }
    
    public Commit createCommit(String message, String parentId) {
        VectorClock timestamp = clock.tick();
        Commit parent = commits.get(parentId);
        
        Commit commit = new Commit(
            generateId(),
            message,
            parentId,
            timestamp.clone(),
            nodeId
        );
        
        commits.put(commit.getId(), commit);
        return commit;
    }
    
    public void mergeCommits(Commit remote) {
        clock.update(remote.getVectorClock());
        
        // Check for conflicts
        Commit local = getHEAD();
        
        if (local.getVectorClock().happenedBefore(remote.getVectorClock())) {
            // Fast-forward merge
            System.out.println("Fast-forward merge possible");
            updateHEAD(remote);
        } else if (remote.getVectorClock().happenedBefore(local.getVectorClock())) {
            // Already up to date
            System.out.println("Already up to date");
        } else {
            // Concurrent commits - need merge commit
            System.out.println("Creating merge commit for concurrent changes");
            createMergeCommit(local, remote);
        }
    }
    
    private void createMergeCommit(Commit local, Commit remote) {
        VectorClock mergeTimestamp = clock.tick();
        
        Commit mergeCommit = new Commit(
            generateId(),
            String.format("Merge %s and %s", local.getId(), remote.getId()),
            local.getId(),
            mergeTimestamp.clone(),
            nodeId
        );
        mergeCommit.addParent(remote.getId());
        
        commits.put(mergeCommit.getId(), mergeCommit);
    }
    
    public boolean canFastForward(Commit from, Commit to) {
        return from.getVectorClock().happenedBefore(to.getVectorClock());
    }
}

Eventually Consistent Database#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Node.js: Conflict detection and resolution with vector clocks
class EventuallyConsistentDatabase {
    constructor(nodeId, numNodes) {
        this.nodeId = nodeId;
        this.clock = new VectorClockNode(nodeId, numNodes);
        this.data = new Map();  // key -> [value, vectorClock]
        this.conflictLog = [];
    }
    
    write(key, value) {
        const timestamp = this.clock.tick();
        const existing = this.data.get(key);
        
        if (existing) {
            const [oldValue, oldVC] = existing;
            
            // Check if this write conflicts with existing
            if (this.clock.isConcurrent(timestamp, oldVC)) {
                this.conflictLog.push({
                    key,
                    values: [
                        { value: oldValue, vc: oldVC },
                        { value: value, vc: timestamp }
                    ],
                    timestamp: Date.now()
                });
                
                // Keep both versions for manual resolution
                this.data.set(key, [
                    { value: oldValue, vc: oldVC },
                    { value: value, vc: [...timestamp] }
                ]);
                
                return { conflict: true, key, timestamp };
            }
        }
        
        this.data.set(key, [value, [...timestamp]]);
        return { success: true, key, timestamp };
    }
    
    read(key) {
        const entry = this.data.get(key);
        if (!entry) return null;
        
        // Check if we have conflict (array of values)
        if (Array.isArray(entry) && entry.length > 1 && entry[0].vc) {
            return {
                conflict: true,
                versions: entry
            };
        }
        
        const [value, vc] = entry;
        return { value, vectorClock: vc };
    }
    
    mergeFromReplica(key, value, vectorClock) {
        this.clock.update(vectorClock);
        const local = this.data.get(key);
        
        if (!local) {
            // No local value, accept remote
            this.data.set(key, [value, [...vectorClock]]);
            return { merged: true, action: 'accept_remote' };
        }
        
        const [localValue, localVC] = local;
        
        if (this.clock.happenedBefore(vectorClock, localVC)) {
            // Remote is older, keep local
            return { merged: true, action: 'keep_local' };
        } else if (this.clock.happenedBefore(localVC, vectorClock)) {
            // Local is older, accept remote
            this.data.set(key, [value, [...vectorClock]]);
            return { merged: true, action: 'accept_remote' };
        } else {
            // Concurrent writes - conflict!
            this.conflictLog.push({
                key,
                localValue,
                localVC,
                remoteValue: value,
                remoteVC: vectorClock,
                timestamp: Date.now()
            });
            
            // Store both versions
            this.data.set(key, [
                { value: localValue, vc: localVC },
                { value: value, vc: vectorClock }
            ]);
            
            return { 
                merged: false, 
                action: 'conflict',
                conflict: true 
            };
        }
    }
    
    resolveConflict(key, chosenValue) {
        const timestamp = this.clock.tick();
        this.data.set(key, [chosenValue, [...timestamp]]);
        
        // Remove from conflict log
        this.conflictLog = this.conflictLog.filter(c => c.key !== key);
        
        return { resolved: true, key, timestamp };
    }
    
    getConflicts() {
        return [...this.conflictLog];
    }
}

// Usage example
const db1 = new EventuallyConsistentDatabase(0, 3);
const db2 = new EventuallyConsistentDatabase(1, 3);

// Concurrent writes on different nodes
db1.write('user:123:name', 'Alice');   // Node 0: [1, 0, 0]
db2.write('user:123:name', 'Alicia');  // Node 1: [0, 1, 0]

// Replicate - detects conflict!
const result = db1.mergeFromReplica(
    'user:123:name',
    'Alicia',
    [0, 1, 0]
);

console.log('Merge result:', result);  // { merged: false, action: 'conflict', conflict: true }
console.log('Conflicts:', db1.getConflicts());

// Manual resolution
db1.resolveConflict('user:123:name', 'Alice');

When to Use Each#

Use Lamport Clocks When:#

  1. Total ordering needed: Mutex, leader election
  2. Storage is limited: IoT devices, embedded systems
  3. System has many processes: O(1) overhead preferable
  4. Concurrency detection not required: Simple event logging

Use Vector Clocks When:#

  1. Conflict detection needed: Distributed databases, CRDTs
  2. Causality tracking critical: Distributed debugging, version control
  3. Process count is manageable: < 100 nodes typically
  4. Concurrent operation detection: Replicated state machines

Performance Optimization: Version Vectors#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class OptimizedVectorClock:
    """
    Optimized vector clock with pruning and compression
    Used in systems like Riak and Voldemort
    """
    
    def __init__(self, node_id: str):
        self.node_id = node_id
        self.vector = {}  # Sparse representation: node_id -> counter
        self.max_entries = 10  # Limit vector size
    
    def increment(self):
        """Increment own counter"""
        self.vector[self.node_id] = self.vector.get(self.node_id, 0) + 1
        self._prune_if_needed()
    
    def merge(self, other_vector: Dict[str, int]):
        """Merge with another vector clock"""
        for node, count in other_vector.items():
            self.vector[node] = max(self.vector.get(node, 0), count)
        self.vector[self.node_id] = self.vector.get(self.node_id, 0) + 1
        self._prune_if_needed()
    
    def _prune_if_needed(self):
        """Prune old entries to limit size"""
        if len(self.vector) > self.max_entries:
            # Keep only most recent entries
            sorted_entries = sorted(
                self.vector.items(),
                key=lambda x: x[1],
                reverse=True
            )
            self.vector = dict(sorted_entries[:self.max_entries])
    
    def descends(self, other_vector: Dict[str, int]) -> bool:
        """Check if this vector descends from (happened after) other"""
        for node, count in other_vector.items():
            if self.vector.get(node, 0) < count:
                return False
        return True
    
    def concurrent_with(self, other_vector: Dict[str, int]) -> bool:
        """Check if vectors are concurrent"""
        return not self.descends(other_vector) and not self._other_descends(other_vector)
    
    def _other_descends(self, other_vector: Dict[str, int]) -> bool:
        """Check if other vector descends from this one"""
        for node, count in self.vector.items():
            if other_vector.get(node, 0) < count:
                return False
        return True

Summary#

Vector clocks extend Lamport clocks to provide complete causality tracking in distributed systems. While they require O(N) storage per process compared to Lamport’s O(1), they enable critical capabilities like conflict detection, concurrent event identification, and causal consistency. The choice between Lamport and vector clocks depends on system requirements, scale, and the importance of detecting concurrent operations.

Key takeaways:

  • Vector clocks detect concurrency, Lamport clocks cannot
  • Vector clocks require O(N) storage vs O(1) for Lamport
  • Essential for distributed databases and version control
  • Modern optimizations reduce overhead in practice
  • Choose based on scale, requirements, and storage constraints
Contents