Workspace IndexAlgorithms › Day 53

Logical Clocks, Vector Clocks, and Hybrid Logical Clocks (HLC) TODO

Algorithms · Day 53 / 100 · D. Distributed Systems & Consensus (Day 52-68)

Concept

In a distributed setting you can't assume a fully synchronized global physical clock, so event order is defined through causality instead. A Lamport clock has each node keep a counter, incrementing it on every local event and updating it to max(mine, received)+1 on message receipt; if a causes b, then C(a) < C(b) is guaranteed, but the converse doesn't hold, so it can't distinguish concurrency. A vector clock carries an array of counters, one per node, and can precisely tell whether two events are causally ordered or concurrent — but its metadata size scales with the number of nodes, hurting scalability. A hybrid logical clock (HLC) combines physical time and a logical counter into one value, producing a monotonically increasing timestamp that stays close to physical time without violating causality. Because causal order survives clock skew and the value stays close to a human-readable time, HLCs are used as version timestamps in distributed databases.

Sorting logs from multiple nodes by physical time can make cause and effect look reversed, and whether you can even detect concurrent-update conflicts ultimately comes down to which clock you use.

Code & Formula

# 논리 시계·벡터 시계 — 벡터 시계로 두 이벤트가 인과적으로 순서가 있는지, 동시(concurrent)인지 판별한다.
# Lamport 시계는 동시성을 구분 못하지만, 벡터 시계는 노드별 카운터 배열로 정확히 판별한다.

class VectorClock:
    def __init__(self, node_id, n_nodes):
        self.node_id = node_id
        self.clock = [0] * n_nodes

    def local_event(self):
        self.clock[self.node_id] += 1
        return tuple(self.clock)

    def send(self):
        self.clock[self.node_id] += 1
        return tuple(self.clock)

    def receive(self, remote_clock):
        self.clock = [max(a, b) for a, b in zip(self.clock, remote_clock)]
        self.clock[self.node_id] += 1
        return tuple(self.clock)

def compare(vc_a, vc_b):
    """a <= b 성분별 비교로 인과 순서 또는 동시성을 판별"""
    le = all(a <= b for a, b in zip(vc_a, vc_b))
    ge = all(a >= b for a, b in zip(vc_a, vc_b))
    if vc_a == vc_b:
        return "동일 이벤트"
    if le:
        return "a -> b (a가 b의 원인)"
    if ge:
        return "b -> a (b가 a의 원인)"
    return "concurrent (동시, 인과관계 없음)"

n = 3
node0, node1, node2 = VectorClock(0, n), VectorClock(1, n), VectorClock(2, n)

e1 = node0.local_event()              # node0: [1,0,0]
msg = node0.send()                    # node0: [2,0,0]
e2 = node1.receive(msg)                # node1: [2,1,0] <- node0 인과적으로 앞섬
e3 = node2.local_event()              # node2: [0,0,1] <- node0/node1과 무관하게 독립 발생

print(f"e1 (node0 local)      = {e1}")
print(f"e2 (node1, e1 이후 수신) = {e2}")
print(f"e3 (node2 독립 이벤트)   = {e3}")

print(f"\ncompare(e1, e2) = {compare(e1, e2)}")  # e1이 e2의 원인
print(f"compare(e1, e3) = {compare(e1, e3)}")  # concurrent
print(f"compare(e2, e3) = {compare(e2, e3)}")  # concurrent

Exercise

Write a simulation of 3 nodes exchanging messages, attach both a Lamport clock and a vector clock to the same run, and find event pairs that only the vector clock identifies as concurrent.

Practical Connection

In a matching service where multiple instances accept orders, determining which order came first by each server's physical clock gets the order reversed by skew, which is exactly why you need a single sequencer or a causal timestamp like HLC.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

논리 시계·벡터 시계·하이브리드 논리 시계(HLC) TODO

Algorithms · Day 53 / 100 · D. 분산시스템·합의 (Day 52–68)

개념

분산 환경에서는 완전히 동기화된 전역 물리 시계를 가정할 수 없으므로 사건의 순서를 인과관계로 정의한다. Lamport 논리 시계는 각 노드가 카운터를 유지해 로컬 이벤트마다 증가시키고 메시지 수신 시 max(내 값, 받은 값)+1로 갱신하며, a가 b의 원인이면 C(a) < C(b)를 보장하지만 역은 성립하지 않아 동시성을 구분하지 못한다. 벡터 시계는 노드 수만큼의 카운터 배열을 들고 다녀 두 사건이 인과적으로 앞뒤인지 아니면 동시(concurrent)인지를 정확히 판별하지만, 메타데이터 크기가 노드 수에 비례해 확장성이 떨어진다. 하이브리드 논리 시계(HLC)는 물리 시각과 논리 카운터를 한 값으로 결합해, 인과성을 어기지 않으면서도 물리 시각에 가깝게 유지되는 단조 증가 타임스탬프를 만든다. HLC는 클럭 스큐가 있어도 인과 순서가 깨지지 않고 사람이 읽을 수 있는 시각에 근접한다는 점 때문에 분산 데이터베이스의 버전 타임스탬프로 쓰인다.

여러 노드의 로그를 물리 시각으로 정렬하면 원인과 결과가 뒤집혀 보이고, 동시 갱신 충돌을 탐지할지 말지도 결국 어떤 시계를 쓰느냐에서 갈리기 때문이다.

코드 · 수식

# 논리 시계·벡터 시계 — 벡터 시계로 두 이벤트가 인과적으로 순서가 있는지, 동시(concurrent)인지 판별한다.
# Lamport 시계는 동시성을 구분 못하지만, 벡터 시계는 노드별 카운터 배열로 정확히 판별한다.

class VectorClock:
    def __init__(self, node_id, n_nodes):
        self.node_id = node_id
        self.clock = [0] * n_nodes

    def local_event(self):
        self.clock[self.node_id] += 1
        return tuple(self.clock)

    def send(self):
        self.clock[self.node_id] += 1
        return tuple(self.clock)

    def receive(self, remote_clock):
        self.clock = [max(a, b) for a, b in zip(self.clock, remote_clock)]
        self.clock[self.node_id] += 1
        return tuple(self.clock)

def compare(vc_a, vc_b):
    """a <= b 성분별 비교로 인과 순서 또는 동시성을 판별"""
    le = all(a <= b for a, b in zip(vc_a, vc_b))
    ge = all(a >= b for a, b in zip(vc_a, vc_b))
    if vc_a == vc_b:
        return "동일 이벤트"
    if le:
        return "a -> b (a가 b의 원인)"
    if ge:
        return "b -> a (b가 a의 원인)"
    return "concurrent (동시, 인과관계 없음)"

n = 3
node0, node1, node2 = VectorClock(0, n), VectorClock(1, n), VectorClock(2, n)

e1 = node0.local_event()              # node0: [1,0,0]
msg = node0.send()                    # node0: [2,0,0]
e2 = node1.receive(msg)                # node1: [2,1,0] <- node0 인과적으로 앞섬
e3 = node2.local_event()              # node2: [0,0,1] <- node0/node1과 무관하게 독립 발생

print(f"e1 (node0 local)      = {e1}")
print(f"e2 (node1, e1 이후 수신) = {e2}")
print(f"e3 (node2 독립 이벤트)   = {e3}")

print(f"\ncompare(e1, e2) = {compare(e1, e2)}")  # e1이 e2의 원인
print(f"compare(e1, e3) = {compare(e1, e3)}")  # concurrent
print(f"compare(e2, e3) = {compare(e2, e3)}")  # concurrent

연습

노드 3개가 메시지를 주고받는 시뮬레이션을 짜서 같은 실행에 Lamport 시계와 벡터 시계를 동시에 붙이고, 벡터 시계로만 동시성이라고 판별되는 사건 쌍을 찾아보기.

실무 · Verex 연결

여러 인스턴스가 주문을 받는 매칭 서비스에서 어느 주문이 먼저인지를 각 서버의 물리 시각으로 정하면 스큐 때문에 순서가 뒤집히므로, 단일 시퀀서나 HLC 같은 인과 타임스탬프가 필요해진다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 52. 일관성 모델 지도54. Raft 심화 →