ЁЯПл The SchoolтА║ЁЯМР Distributed SystemsтА║ЁЯХ░я╕П рдзрдбрд╛ 02 тАФ Clocks: рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦреЗрдЪреЗ рдШрдбреНрдпрд╛рд│ рдереЛрдбреЗрд╕реЗ рдЪреБрдХреАрдЪреЗ рдЕрд╕рддреЗ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯХ░я╕П рдзрдбрд╛ 02 тАФ Clocks: рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦреЗрдЪреЗ рдШрдбреНрдпрд╛рд│ рдереЛрдбреЗрд╕реЗ рдЪреБрдХреАрдЪреЗ рдЕрд╕рддреЗ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 02 ┬╖ рдорд╛рдЧреЗ: lesson-01-partial-failure ┬╖ рдкреБрдвреЗ: lesson-03-ordering


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбрд╛ 01, рдЕрдзрд┐рдХ рд╡реЗрд│. рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦреЗрдЪреЗ рд╕реНрд╡рддрдГрдЪреЗ wall clock рдЕрд╕рддреЗ, рдЖрдгрд┐ рддреА рд╣рд│реВрд╣рд│реВ рд╡реЗрдЧрд│реА рд╣реЛрдд рдЬрд╛рддрд╛рдд. рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ machines рд╡рд░рдЪреНрдпрд╛ рдШрдЯрдирд╛рдВрдЪрд╛ рдХреНрд░рдо wall time рд╕реБрд░рдХреНрд╖рд┐рддрдкрдгреЗ рдХрд╛ рдард░рд╡реВ рд╢рдХрдд рдирд╛рд╣реА, NTP рдХрд╛рдп рджреБрд░реБрд╕реНрдд рдХрд░реВ рд╢рдХрддреЛ рдЖрдгрд┐ рдХрд╛рдп рдирд╛рд╣реА, monotonic рд╡рд┐рд░реБрджреНрдз wall clocks, рдЖрдгрд┐ Lamport clocks тАФ рдХреЛрдгрддреНрдпрд╛рд╣реА рдЦрд▒реНрдпрд╛ рдШрдбреНрдпрд╛рд│рд╛рд╢рд┐рд╡рд╛рдп рдХрд╛рд░рдгрд╛рд▓рд╛ рдкрд░рд┐рдгрд╛рдорд╛рдЪреНрдпрд╛ рдЖрдзреА рдареЗрд╡рдгрд╛рд░рд╛ рдПрдХ counter. dist/demo.py рдордзрд▓реЗ clocks() рдЖрдгрд┐ dist/sim.py рдордзрд▓реЗ LamportClock.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦреЗрдЪреНрдпрд╛ рднрд┐рдВрддреАрд╡рд░ рдПрдХ рдШрдбреНрдпрд╛рд│ рдЖрд╣реЗ. ЁЯХ░я╕П рд╕рдЧрд│реА рдПрдХрд╛рдЪ рджрд┐рд╡рд╢реА рд▓рд╛рд╡рд▓реА рд╣реЛрддреА. рдкрдг рдШрдбреНрдпрд╛рд│реЗ рдкрд░рд┐рдкреВрд░реНрдг рдирд╕рддрд╛рдд. рдкреБрдгреНрдпрд╛рдЪреЗ рдШрдбреНрдпрд╛рд│ рдереЛрдбреЗ рдкреБрдвреЗ рдкрд│рддреЗ. рдирд╛рд╢рд┐рдХрдЪреЗ рдереЛрдбреЗ рдорд╛рдЧреЗ рд░рд╛рд╣рддреЗ.

рдПрдХрд╛рдЪ рдХреНрд╖рдгреА, рдкреБрдгреНрдпрд╛рдЪреЗ рдШрдбреНрдпрд╛рд│ 10:00:00.120 рджрд╛рдЦрд╡рддреЗ рдЖрдгрд┐ рдирд╛рд╢рд┐рдХрдЪреЗ 09:59:59.980.

рдкреБрдгреНрдпрд╛рддрд▓реА рдХрддрд░рд┐рдирд╛ "exam at 10" рд▓рд┐рд╣рд┐рддреЗ рдЖрдгрд┐ рддрд┐рдЪреНрдпрд╛ рдШрдбреНрдпрд╛рд│рд╛рдиреБрд╕рд╛рд░ рддреНрдпрд╛рд╡рд░ 10:00:00.100 рдЕрд╢реА рд╡реЗрд│ рдЯрд╛рдХрддреЗ. рдПрдХрд╛ рдХреНрд╖рдгрд╛рдиреЗ рдирдВрддрд░, рдирд╛рд╢рд┐рдХрдордзрд▓реА рдРрд╢реНрд╡рд░реНрдпрд╛ рддреЗ рдмрджрд▓реВрди "exam at 11" рдХрд░рддреЗ рдЖрдгрд┐ рддрд┐рдЪреНрдпрд╛ рдШрдбреНрдпрд╛рд│рд╛рдиреБрд╕рд╛рд░ рддреНрдпрд╛рд╡рд░ 10:00:00.050 рдЕрд╢реА рд╡реЗрд│ рдЯрд╛рдХрддреЗ.

рдЖрддрд╛ рдХреЛрдгреАрддрд░реА рдЪрд┐рдареНрдареНрдпрд╛ рд╡реЗрд│реЗрдиреБрд╕рд╛рд░ рд▓рд╛рд╡рддреЗ. рдкреБрдгреНрдпрд╛рдЪреНрдпрд╛ рдЪрд┐рдареНрдареАрд╡рд░рдЪреА рд╡реЗрд│ рдореЛрдареА рдЖрд╣реЗ. рдореНрд╣рдгреВрди рдкреБрдгреНрдпрд╛рдЪреА рдЪрд┐рдареНрдареА рдЬрд┐рдВрдХрддреЗ. рдкрдг рдРрд╢реНрд╡рд░реНрдпрд╛рдЪрд╛ рдмрджрд▓ рдирдВрддрд░ рдЖрд▓рд╛ рд╣реЛрддрд╛! рддрд┐рдЪрд╛ рдмрджрд▓ рдлреЗрдХреВрди рджрд┐рд▓рд╛ рдЬрд╛рддреЛ, рдЖрдгрд┐ рдХреЛрдгрд╛рдЪреНрдпрд╛ рд▓рдХреНрд╖рд╛рддрд╣реА рдпреЗрдд рдирд╛рд╣реА.

рдореНрд╣рдгреВрди рд╢рд╛рдЦрд╛ рд╡реЗрдЧрд│реА рдХрд▓реНрдкрдирд╛ рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦрд╛ рдПрдХ counter рдареЗрд╡рддреЗ. ЁЯФв рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рддреБрдореНрд╣реА рд▓рд┐рд╣рд┐рддрд╛, 1 рдорд┐рд│рд╡рд╛. рдкреНрд░рддреНрдпреЗрдХ рдЪрд┐рдареНрдареАрд╡рд░ рддреБрдордЪрд╛ counter рдЕрд╕рддреЛ. рддреБрдореНрд╣рд╛рд▓рд╛ рдЪрд┐рдареНрдареА рдорд┐рд│рд╛рд▓реА рдХреА рддреБрдордЪрд╛ counter "рдорд╛рдЭрд╛ рдЖрдгрд┐ рдЪрд┐рдареНрдареАрдЪрд╛ рдпрд╛рдВрдкреИрдХреА рдореЛрдард╛, рдЕрдзрд┐рдХ 1" рдЕрд╕рд╛ рдХрд░рд╛. рдЖрддрд╛ рдЙрддреНрддрд░рд╛рд▓рд╛ рдиреЗрд╣рдореА рддреЗ рдЬреНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рдЪреЗ рдЙрддреНрддрд░ рдЖрд╣реЗ рддреНрдпрд╛рдЪреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдореЛрдард╛ рдХреНрд░рдорд╛рдВрдХ рдЕрд╕рддреЛ. Wall clock рдЪреА рдЧрд░рдЬрдЪ рдирд╛рд╣реА. рддреНрдпрд╛ counter рд▓рд╛ Lamport clock рдореНрд╣рдгрддрд╛рдд.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph wall["ЁЯХ░я╕П wall clocks: Pune is 140 ms ahead"]
      p1["Pune writes 'exam at 10'<br/>stamp 10:00:00.100"] --> n1["Nashik writes 'exam at 11' LATER<br/>stamp 10:00:00.050"]
      n1 --> lww["sort by stamp: Pune's note wins<br/>the later change is lost"]
    end
    subgraph lam["ЁЯФв Lamport clocks"]
      a["Pune writes<br/>L = 1"] -->|"note carries 1"| b["Nashik receives<br/>L = max(0, 1) + 1 = 2"]
      b --> c["Nashik writes<br/>L = 3"]
    end

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l02

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг "рд╕рд░реНрд╡рд╛рдд рдирд╡рд╛ write рдареЗрд╡рд╛" рд╣рд╛ distributed data рдордзрд▓рд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдирд┐рдпрдо рдЖрд╣реЗ, рдЖрдгрд┐ "рд╕рд░реНрд╡рд╛рдд рдирд╡рд╛" рдореНрд╣рдгрдЬреЗ рд╕рд╣рд╕рд╛ "рд╕рд░реНрд╡рд╛рдд рдореЛрдареА time stamp". Skew рдЕрд╕реЗрд▓ рддрд░ рд╕рд░реНрд╡рд╛рдд рдореЛрдареА stamp рдореНрд╣рдгрдЬреЗ рд╕рд░реНрд╡рд╛рдд рдирд╡рд╛ write рдирд╕рддреЛ. Data рдЪреЗ рдиреБрдХрд╕рд╛рди рдЧреБрдкрдЪреВрдк рд╣реЛрддреЗ: error рдирд╛рд╣реА, log line рдирд╛рд╣реА, рдлрдХреНрдд рдПрдХ рдмрджрд▓ рдирд╛рд╣реАрд╕рд╛ рд╣реЛрддреЛ. Lamport clocks рдЕрд╕рд╛ рдХреНрд░рдо рджреЗрддрд╛рдд рдЬреЛ рдиреЗрд╣рдореА рдХрд╛рд░рдг рдЖрдгрд┐ рдкрд░рд┐рдгрд╛рдо рдкрд╛рд│рддреЛ, рдЖрдгрд┐ рддреНрдпрд╛рдЪреА рдХрд┐рдВрдордд рдореНрд╣рдгрдЬреЗ рдкреНрд░рддреНрдпреЗрдХ message рд╡рд░ рдПрдХ рдЫреЛрдЯрд╛ рдЖрдХрдбрд╛.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

dist/sim.py рдордзрд▓реНрдпрд╛ LamportClock рдордзреНрдпреЗ t, tick() (+1) рдЖрдгрд┐ recv(other) (max(t, other) + 1) рдЖрд╣реЗрдд. dist/demo.py рдордзрд▓реЗ clocks() skew рдЪреА рдЧреЛрд╖реНрдЯ рдЖрдгрд┐ рдПрдХ рдкреБрдгреЗ тЖТ рдирд╛рд╢рд┐рдХ рджреЗрд╡рд╛рдгрдШреЗрд╡рд╛рдг print рдХрд░рддреЗ. рдЦрд╛рд▓рдЪреНрдпрд╛ snippet рдордзрд▓рд╛ wall-clock рднрд╛рдЧ рд╕рд╛рдзреЗ рдЧрдгрд┐рдд рдЖрд╣реЗ: рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рдЦреЗрдЪреА time stamp рдореНрд╣рдгрдЬреЗ рдЦрд░реА рд╡реЗрд│ рдЕрдзрд┐рдХ рддреНрдпрд╛ рд╢рд╛рдЦреЗрдЪрд╛ offset, рдЖрдгрд┐ рдзрдбрд╛ 08 рдордзрд▓реЗ resolve(..., "lww") рд╕рд░реНрд╡рд╛рдд рдореЛрдареА stamp рдареЗрд╡рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 dist/demo.py clocks
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import LamportClock, resolve
p, n, g = LamportClock(), LamportClock(), LamportClock()
a = p.tick(); b = p.tick()            # Pune writes twice
c = g.tick()                          # Nagpur writes once, sees nobody
d = n.recv(b)                         # Nashik reads Pune's second note
e = n.tick()                          # Nashik writes
print(f"Pune a={a} b={b} ┬╖ Nagpur c={c} ┬╖ Nashik receives b тЖТ {d}, writes e={e}")
print("b caused e, so b < e:", b < e)
print("c and b never met, yet c < b:", c < b, "тАФ a smaller number does NOT mean 'caused'")
# true time in ms after 10:00:00.000; each branch's clock is off by an offset
for pune_off, nashik_off in ((120, -20), (100, 0), (60, 0), (0, 0)):
    pune_stamp   = -20 + pune_off        # Pune writes FIRST, at true time -20 ms
    nashik_stamp =  70 + nashik_off      # Nashik writes 90 ms LATER, at true time +70 ms
    kept = resolve((pune_stamp, "exam at 10"), (nashik_stamp, "exam at 11"), "lww")
    print(f"Pune ahead by {pune_off - nashik_off:>3} ms тЖТ stamps pune {pune_stamp:>3}, nashik {nashik_stamp:>3} тЖТ last-writer-wins keeps {kept!r}")
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

clocks рд╣реЗ print рдХрд░рддреЗ:

тФАтФА wall clocks drift: Pune's clock reads 10:00:00.120, Nashik's reads 09:59:59.980 at the same instant
   Pune writes 'exam at 10' at 10:00:00.100 by its clock; Nashik then writes 'exam at 11' at 10:00:00.050 by its clock
   sorting by wall time says Pune's note was LAST тАФ the later change is silently thrown away
тФАтФА Lamport: Pune writes (L=1) тЖТ Nashik receives (L=2) тЖТ Nashik writes (L=3) тАФ cause always has a smaller number

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

Pune a=1 b=2 ┬╖ Nagpur c=1 ┬╖ Nashik receives b тЖТ 3, writes e=4
b caused e, so b < e: True
c and b never met, yet c < b: True тАФ a smaller number does NOT mean 'caused'
Pune ahead by 140 ms тЖТ stamps pune 100, nashik  50 тЖТ last-writer-wins keeps 'exam at 10'
Pune ahead by 100 ms тЖТ stamps pune  80, nashik  70 тЖТ last-writer-wins keeps 'exam at 10'
Pune ahead by  60 ms тЖТ stamps pune  40, nashik  70 тЖТ last-writer-wins keeps 'exam at 11'
Pune ahead by   0 ms тЖТ stamps pune -20, nashik  70 тЖТ last-writer-wins keeps 'exam at 11'

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рджреЛрди writes рдордзреНрдпреЗ 90 ms рдЪреЗ рдЕрдВрддрд░ рд╣реЛрддреЗ. 100 рдХрд┐рдВрд╡рд╛ 140 ms рдЪреНрдпрд╛ skew рдиреЗ last-writer-wins рдиреЗ рдЬреБрдирд╛ write рдареЗрд╡рд▓рд╛. 60 ms рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рд╣реВрди рдХрдореА skew рдиреЗ рддреНрдпрд╛рдиреЗ рдмрд░реЛрдмрд░ write рдареЗрд╡рд▓рд╛. рдореНрд╣рдгрдЬреЗ wall-clock рдХреНрд░рдо рдлрдХреНрдд рддреЗрд╡реНрд╣рд╛рдЪ рд╕реБрд░рдХреНрд╖рд┐рдд рдЖрд╣реЗ рдЬреЗрд╡реНрд╣рд╛ skew writes рдордзрд▓реНрдпрд╛ рдЕрдВрддрд░рд╛рдкреЗрдХреНрд╖рд╛ рд▓рд╣рд╛рди рдЕрд╕рддреЛ тАФ рдЖрдгрд┐ рддреБрдореНрд╣рд╛рд▓рд╛ рдпрд╛рдкреИрдХреА рдХреЛрдгрддрд╛рдЪ рдЖрдХрдбрд╛ рдХреНрд╡рдЪрд┐рддрдЪ рдорд╛рд╣реАрдд рдЕрд╕рддреЛ. Lamport рдХреНрд░рдорд╛рдВрдХ рдиреЗрд╣рдореА рдХрд╛рд░рдгрд╛рд▓рд╛ рдкрд░рд┐рдгрд╛рдорд╛рдЪреНрдпрд╛ рдЖрдзреА рдареЗрд╡рддрд╛рдд (b=2 < e=4), рдкрдг c=1 < b=2 рджрд╛рдЦрд╡рддреЗ рдХреА рд▓рд╣рд╛рди рдХреНрд░рдорд╛рдВрдХ рдЕрд╢рд╛ рдШрдЯрдиреЗрдЪрд╛рд╣реА рдЕрд╕реВ рд╢рдХрддреЛ рдЬрд┐рдЪрд╛ рджреБрд╕рд░реАрд╢реА рдХрд╛рд╣реАрдЪ рд╕рдВрдмрдВрдз рдирд╡реНрд╣рддрд╛. рд╣реЗ рджреЛрди рдкреНрд░рдХрд╛рд░ рд╡реЗрдЧрд│реЗ рдУрд│рдЦрдгреЗ рд╣рд╛ рдкреБрдврдЪрд╛ рдзрдбрд╛ рдЖрд╣реЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

On a real server тАФ рдпрд╛ machine рдЪреЗ рдШрдбреНрдпрд╛рд│ рддреНрдпрд╛рдЪреНрдпрд╛ time servers рдкрд╛рд╕реВрди рдХрд┐рддреА рджреВрд░ рдЖрд╣реЗ рддреЗ рддрдкрд╛рд╕рд╛. chrony рд╕рд╣ (рдЕрдиреЗрдХ Linux distributions рд╡рд░рдЪрд╛ default NTP client):

chronyc tracking      # "System time : 0.000012 seconds fast of NTP time", plus the estimated error
chronyc sources -v    # which servers it uses and how far each one is
timedatectl           # "System clock synchronized: yes"

AWS рд╡рд░, Amazon Time Sync Service рдкреНрд░рддреНрдпреЗрдХ VPC рдЪреНрдпрд╛ рдЖрдд 169.254.169.123 рд╡рд░ рдЕрд╕рддреЗ; chrony /etc/chrony.conf рдордзрд▓реНрдпрд╛ рдПрдХрд╛ рдУрд│реАрдиреЗ рддреА рд╡рд╛рдкрд░рддреЗ:

server 169.254.169.123 prefer iburst minpoll 4 maxpoll 4

рдХрд╛рд▓рд╛рд╡рдзреА monotonic clock рдиреЗ рдореЛрдЬрд╛:

import time
start = time.monotonic()
do_work()
elapsed = time.monotonic() - start      # safe even if NTP moves the wall clock meanwhile

рдЬреНрдпрд╛ systems рдирд╛ machines рдкрд▓реАрдХрдбреЗ рд╡реЗрд│ рд▓рд╛рдЧрддреЗ рддреНрдпрд╛ error bar рдЕрд╕рд▓реЗрд▓реЗ рдШрдбреНрдпрд╛рд│ рд╡рд╛рдкрд░рддрд╛рдд, рд╢рдмреНрджрд╛рдВрдд:

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рддреБрдордЪреНрдпрд╛ code рдордзреНрдпреЗ machines рдкрд▓реАрдХрдбреЗ "sort by created_at" рдЖрдгрд┐ time.time() рдкрд╛рд╕реВрди рдмрдирд╡рд▓реЗрд▓реЗ timeouts рд╢реЛрдзрд╛. рдХрд╛рд▓рд╛рд╡рдзреАрд╕рд╛рдареА monotonic clock рд╡рд╛рдкрд░рд╛, рдЖрдгрд┐ рдХреНрд░рдорд╛рд╕рд╛рдареА database sequence, version number рдХрд┐рдВрд╡рд╛ Lamport/HLC time stamp рд╡рд╛рдкрд░рд╛.

тПня╕П рдкреБрдвреЗ

Lamport рдХреНрд░рдорд╛рдВрдХ "рдХрджрд╛рдЪрд┐рдд рдЖрдзреА" рд╕рд╛рдВрдЧрддрд╛рдд. "рдпрд╛ рджреЛрдШрд╛рдВрдирд╛ рдПрдХрдореЗрдХрд╛рдВрдмрджреНрджрд▓ рдорд╛рд╣реАрдд рдирд╡реНрд╣рддреЗ" рд╣реЗ рддреЗ рд╕рд╛рдВрдЧреВ рд╢рдХрдд рдирд╛рд╣реАрдд. Vector clocks рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛рдд.

git checkout lesson-03-ordering

ЁЯХ░я╕П Lesson 02 тАФ Clocks: every branch's clock is a little wrong

ЁЯУН You are here: Lesson 02 of 12 ┬╖ Previous: lesson-01-partial-failure ┬╖ Next: lesson-03-ordering


ЁЯУж What's in this branch

Lesson 01, plus time. Every branch has its own wall clock, and they drift apart. Why wall time cannot safely order events on different machines, what NTP can and cannot fix, monotonic vs wall clocks, and Lamport clocks тАФ a counter that orders cause before effect with no real clock at all. clocks() in dist/demo.py and LamportClock in dist/sim.py.

ЁЯзТ Explain like I'm 5

Every branch has a clock on the wall. ЁЯХ░я╕П They were all set on the same day. But clocks are not perfect. Pune's clock runs a little fast. Nashik's runs a little slow.

At the same moment, Pune's clock says 10:00:00.120 and Nashik's says 09:59:59.980.

Katrina in Pune writes "exam at 10" and stamps it 10:00:00.100 by her clock. A moment later, Aishwarya in Nashik changes it to "exam at 11" and stamps it 10:00:00.050 by her clock.

Now someone sorts the notes by the time stamp. Pune's note has the bigger time. So Pune's note wins. But Aishwarya's change came later! Her change is thrown away, and nobody notices.

So the branches try a different idea. Each branch keeps a counter. ЁЯФв Every time you write, add 1. Every note carries your counter. When you get a note, set your counter to "the bigger of mine and the note's, plus 1". Now an answer always has a bigger number than the question it answers. No wall clock needed. That counter is a Lamport clock.

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph wall["ЁЯХ░я╕П wall clocks: Pune is 140 ms ahead"]
      p1["Pune writes 'exam at 10'<br/>stamp 10:00:00.100"] --> n1["Nashik writes 'exam at 11' LATER<br/>stamp 10:00:00.050"]
      n1 --> lww["sort by stamp: Pune's note wins<br/>the later change is lost"]
    end
    subgraph lam["ЁЯФв Lamport clocks"]
      a["Pune writes<br/>L = 1"] -->|"note carries 1"| b["Nashik receives<br/>L = max(0, 1) + 1 = 2"]
      b --> c["Nashik writes<br/>L = 3"]
    end

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/distributed-systems/lesson-diagrams.html#l02

тЭУ What

ЁЯдФ Why

Because "keep the newest write" is the most common rule in distributed data, and "newest" usually means "biggest time stamp". With skew, the biggest stamp is not the newest write. The data loss is silent: no error, no log line, just a change that vanished. Lamport clocks give an order that always respects cause and effect, at the cost of one small number on every message.

ЁЯФз How (in this repo)

LamportClock in dist/sim.py has t, tick() (+1) and recv(other) (max(t, other) + 1). clocks() in dist/demo.py prints the skew story and one Pune тЖТ Nashik exchange. The wall-clock part of the snippet below is plain arithmetic: each branch's time stamp is the true time plus that branch's offset, and resolve(..., "lww") from lesson 08 keeps the biggest stamp.

ЁЯзк Try it

python3 dist/demo.py clocks
python3 - <<'EOF'
import sys; sys.path.insert(0, "dist"); from sim import LamportClock, resolve
p, n, g = LamportClock(), LamportClock(), LamportClock()
a = p.tick(); b = p.tick()            # Pune writes twice
c = g.tick()                          # Nagpur writes once, sees nobody
d = n.recv(b)                         # Nashik reads Pune's second note
e = n.tick()                          # Nashik writes
print(f"Pune a={a} b={b} ┬╖ Nagpur c={c} ┬╖ Nashik receives b тЖТ {d}, writes e={e}")
print("b caused e, so b < e:", b < e)
print("c and b never met, yet c < b:", c < b, "тАФ a smaller number does NOT mean 'caused'")
# true time in ms after 10:00:00.000; each branch's clock is off by an offset
for pune_off, nashik_off in ((120, -20), (100, 0), (60, 0), (0, 0)):
    pune_stamp   = -20 + pune_off        # Pune writes FIRST, at true time -20 ms
    nashik_stamp =  70 + nashik_off      # Nashik writes 90 ms LATER, at true time +70 ms
    kept = resolve((pune_stamp, "exam at 10"), (nashik_stamp, "exam at 11"), "lww")
    print(f"Pune ahead by {pune_off - nashik_off:>3} ms тЖТ stamps pune {pune_stamp:>3}, nashik {nashik_stamp:>3} тЖТ last-writer-wins keeps {kept!r}")
EOF

тЬЕ Verify тАФ what you should see

clocks prints:

тФАтФА wall clocks drift: Pune's clock reads 10:00:00.120, Nashik's reads 09:59:59.980 at the same instant
   Pune writes 'exam at 10' at 10:00:00.100 by its clock; Nashik then writes 'exam at 11' at 10:00:00.050 by its clock
   sorting by wall time says Pune's note was LAST тАФ the later change is silently thrown away
тФАтФА Lamport: Pune writes (L=1) тЖТ Nashik receives (L=2) тЖТ Nashik writes (L=3) тАФ cause always has a smaller number

Your snippet prints:

Pune a=1 b=2 ┬╖ Nagpur c=1 ┬╖ Nashik receives b тЖТ 3, writes e=4
b caused e, so b < e: True
c and b never met, yet c < b: True тАФ a smaller number does NOT mean 'caused'
Pune ahead by 140 ms тЖТ stamps pune 100, nashik  50 тЖТ last-writer-wins keeps 'exam at 10'
Pune ahead by 100 ms тЖТ stamps pune  80, nashik  70 тЖТ last-writer-wins keeps 'exam at 10'
Pune ahead by  60 ms тЖТ stamps pune  40, nashik  70 тЖТ last-writer-wins keeps 'exam at 11'
Pune ahead by   0 ms тЖТ stamps pune -20, nashik  70 тЖТ last-writer-wins keeps 'exam at 11'

ЁЯПБ What you just proved

The two writes were 90 ms apart. With skew of 100 or 140 ms, last-writer-wins kept the older write. With skew of 60 ms or less, it kept the right one. So wall-clock ordering is only safe when skew is smaller than the gap between writes тАФ and you rarely know either number. Lamport numbers always put cause before effect (b=2 < e=4), but c=1 < b=2 shows that a smaller number can belong to an event that had nothing to do with the other. Telling those apart is the next lesson.

тЪая╕П Common mistakes

ЁЯПн In production

On a real server тАФ check how far this machine's clock is from its time servers. With chrony (the default NTP client on many Linux distributions):

chronyc tracking      # "System time : 0.000012 seconds fast of NTP time", plus the estimated error
chronyc sources -v    # which servers it uses and how far each one is
timedatectl           # "System clock synchronized: yes"

On AWS, the Amazon Time Sync Service is at 169.254.169.123 inside every VPC; chrony uses it with one line in /etc/chrony.conf:

server 169.254.169.123 prefer iburst minpoll 4 maxpoll 4

Measure durations with a monotonic clock:

import time
start = time.monotonic()
do_work()
elapsed = time.monotonic() - start      # safe even if NTP moves the wall clock meanwhile

Systems that need time across machines use a clock with an error bar, in words:

ЁЯПн Why this matters in production: search your code for "sort by created_at" across machines and for timeouts built from time.time(). Use a monotonic clock for durations, and a database sequence, a version number or a Lamport/HLC time stamp for order.

тПня╕П Next

Lamport numbers say "maybe before". They cannot say "these two did not know about each other". Vector clocks can.

git checkout lesson-03-ordering
тЖР Previouspartial failureNext тЖТordering

This page is the lesson's README from the lesson-02-clocks branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.