Physical Time — NTP, Clock Skew, আর কেন Wall Clock বিশ্বাস করা যায় না
Physical Time: NTP, Clock Skew, and Why Wall Clocks Lie
Physical clock কখনো নিখুঁতভাবে sync হয় না — NTP bound দেয়, শূন্য করে না; সেই bounded skew LWW-তে silent lost-write জন্মায়, আর elapsed-time মাপতে হলে wall clock নয়, monotonic clock লাগে।
আগে এটা বুঝি
আগের লেসনে দেখেছি একটা process অন্য process crashed নাকি শুধু ধীর তা বাইরে থেকে বলার উপায় নেই — failure detection সবসময় timeout-নির্ভর সন্দেহ। আজ প্রশ্নটা আরেক ধাপ গোড়ায়: সেই timeout মাপার ঘড়িটা নিজেই কতটা বিশ্বাসযোগ্য?
ধরুন দুইটা machine একই সেকেন্ডে দুইটা লেখা পাঠাল একই key-তে —
machine-1 বলছে: "আমি লিখেছি 10:00:00.000-এ"
machine-2 বলছে: "আমি লিখেছি 09:59:59.997-এ"কে সত্যিই পরে লিখেছে? Naive উত্তর: timestamp দেখো, বড়টা পরে। কিন্তু machine-2-র ঘড়ি machine-1-এর চেয়ে যদি 10ms পিছিয়ে থাকে, তাহলে machine-2 আসলে machine-1-এর পরে লিখেছে — timestamp তার উল্টো গল্প বলছে। এটা কোনো bug নয়, এটা প্রতিটা distributed system-এর বাস্তবতা: physical clock কখনো নিখুঁতভাবে sync হয় না।
আজকের কাজ তিনটা। এক: কেন — প্রতিটা মেশিনের ঘড়ি নিজের গতিতে চলে, আর সেই গতি sync করার প্রধান হাতিয়ার (NTP) ঠিক কী দেয়, কী দেয় না। দুই: সেই অসম্পূর্ণতার concrete দাম — last-write-wins-এ silent data loss, সংখ্যা দিয়ে। তিন: কোন কাজে ভুল ঘড়ি ব্যবহার করা এড়ানো যায় (monotonic clock), আর সবচেয়ে সাবধানী system কী করে (TrueTime-এর ইঙ্গিত)। ভরসাযোগ্য global ordering-এর পূর্ণ সমাধান — logical clock — পরের লেসনের বিষয়; আজ শুধু বোঝা যাক কেন physical clock একা যথেষ্ট নয়।
মূল ধারণা
ঘড়ি কেন একা ভুল করে — crystal drift
প্রতিটা কম্পিউটারের ভেতরে একটা quartz crystal oscillator নির্দিষ্ট frequency-তে কাঁপে (সাধারণত ৩২,৭৬৮ Hz-এর multiple); সেই কাঁপুনি গুনেই OS সেকেন্ড বানায়। কিন্তু manufacturing tolerance, তাপমাত্রা, বয়স — সব মিলিয়ে দুইটা crystal কখনোই ঠিক একই frequency-তে কাঁপে না। ফলাফল: drift — uncorrected ঘড়ি প্রতি দিনে কয়েক থেকে কয়েকশো মিলিসেকেন্ড এগিয়ে বা পিছিয়ে যায় (সস্তা hardware-এ ~20 ppm মানে দিনে ~1.7 সেকেন্ড পর্যন্ত)। Sync ছাড়া রেখে দিলে সপ্তাহখানেকে মেশিনগুলোর ঘড়ি সেকেন্ড-scale-এ আলাদা হয়ে যায়।
NTP — চার timestamp দিয়ে offset মাপা
Client একটা request পাঠায়, server উত্তর দেয় — চারটা মুহূর্ত রেকর্ড হয়:
t0 : client পাঠাল (client-এর নিজের ঘড়িতে)
t1 : server পেল (server-এর নিজের ঘড়িতে)
t2 : server উত্তর পাঠাল (server-এর নিজের ঘড়িতে)
t3 : client উত্তর পেল (client-এর নিজের ঘড়িতে)দুইটা অজানা রাশি — ঘড়ির offset (θ = server ঘড়ি − client ঘড়ি)
আর নেটওয়ার্কের round-trip delay (δ)। ধরে নেওয়া হয় যাওয়া-আসার
delay সমান (symmetric); এই একটা ধারণা থেকেই দুইটা সমীকরণ বের হয়:
offset ≈ ((t1 − t0) + (t2 − t3)) / 2
delay ≈ (t3 − t0) − (t2 − t1)স্বজ্ঞা: (t1−t0) হলো forward-leg-এ observed gap (আসল delay +
offset মিশে আছে); (t2−t3) হলো backward-leg-এর gap (delay −
offset, উল্টো চিহ্নে)। যোগ করলে delay বাতিল, offset বেরিয়ে আসে;
বিয়োগ করলে offset বাতিল, delay বেরিয়ে আসে। hood সেকশনে সংখ্যা বসিয়ে
হাতে-কলমে verify করব।
Stratum hierarchy — কে কার থেকে সময় ধার নেয়
stratum 0 : atomic clock / GPS receiver (hardware, নেটওয়ার্কে নেই)
stratum 1 : সরাসরি stratum-0-এর সাথে সংযুক্ত সার্ভার
stratum 2 : stratum-1 থেকে sync নেয়
stratum 3 : stratum-2 থেকে ... (cascade চলতে থাকে)উপরের দিকে accuracy বেশি (stratum 1 সাধারণত sub-microsecond-এর কাছাকাছি atomic reference-এর সাথে), নিচের দিকে accuracy কমে কারণ প্রতিটা hop নতুন নেটওয়ার্ক-delay-জনিত error যোগ করে। বাস্তব সংখ্যা: একই LAN-এ sync সাধারণত sub-millisecond থেকে কয়েক ms নির্ভুল; WAN-এ (ভিন্ন datacenter, ভিন্ন মহাদেশ) কয়েক দশ ms পর্যন্ত ভুল থাকতে পারে — variable network path আর queueing delay-র কারণে symmetric-delay ধারণাটাই দুর্বল হয়ে যায়।
Discipline — slew বনাম step, আর কেন step বিপজ্জনক
Estimated offset পেলে ঘড়ি ঠিক করার দুইটা উপায়:
| Mode | করে | ঝুঁকি |
|---|---|---|
| Slew | ঘড়ির গতি সাময়িক কমিয়ে/বাড়িয়ে ধীরে ধীরে টার্গেটে আনা | ধীর — বড় offset সারতে সময় লাগে |
| Step | ঘড়িকে এক ধাক্কায় নতুন মানে বসিয়ে দেওয়া | তাৎক্ষণিক — কিন্তু ঘড়ি পেছনেও যেতে পারে |
ছোট offset-এ NTP daemon সাধারণত slew করে — গতি সামান্য বদলে ধীরে
কাছে আনে, ঘড়ি কখনো পেছায় না। কিন্তু বড় offset (boot-এর পরে প্রথম
sync, বা ঘড়ি বহুদিন unsync) থাকলে step করে — সরাসরি জাম্প। যদি
সত্যিকারের সময় ঘড়ির-দেখানো সময়ের চেয়ে পেছনে থাকে, তাহলে সেই
step wall clock-কে পেছনে ঠেলে দেয় — এইমাত্র read() = 10:00:05
দেখানো ঘড়ি পরের মুহূর্তে 10:00:02 দেখাতে পারে। elapsed-time মাপা
যেকোনো কোড (end - start) তখন negative সংখ্যা পায় — hood
সেকশনে ঠিক এই bug কোড দিয়ে দেখাব।
ভেতরে কী ঘটছে
Clock skew-র concrete দাম — LWW silently ভুল write রাখে
অনেক replicated store conflict resolve করে সবচেয়ে সহজ নিয়মে: last-write-wins (LWW) — যে write-এর timestamp সবচেয়ে বড়, সে জেতে, বাকিগুলো ফেলে দেওয়া হয়। সমস্যা: timestamp আসে লেখা-machine-এর নিজের wall clock থেকে, আর সেই ঘড়ি skewed হতে পারে।
দৃশ্য কল্পনা করুন: machine M1-এর ঘড়ি নিখুঁত (offset 0)। Machine
M2-এর ঘড়ি সত্যিকারের সময়ের চেয়ে ১০০ms পিছিয়ে (একটা ছোট, বাস্তব
skew — NTP এই মাত্রা সহজেই ঘটাতে পারে যদি sync সবে হয়েছে বা path
asymmetric)।
সত্যিকারের ঘটনাক্রম (real time, ms):
τ=1000 : M1 লেখে "A" (M1-এর ঘড়ি নিখুঁত) → timestamp 1000
τ=1050 : M2 লেখে "B" (৫০ms পরে, real time-এ!)
কিন্তু M2-র ঘড়ি ১০০ms পিছিয়ে → timestamp 950
naive LWW: max(1000, 950) = 1000 → "A" জেতেB সত্যিকারের সময়ে A-র পরে লেখা হয়েছিল — user-এর perspective-এ
B হলো সর্বশেষ ইচ্ছা। কিন্তু M2-র ঘড়ি পিছিয়ে থাকায় তার timestamp
ছোট, LWW A-কেই রাখে — নীরবে, কোনো error, কোনো conflict-flag,
কোনো log-এ warning ছাড়াই। এটাই সেই “LWW clock-skew anomaly” যা
quorum-replication লেসনের build-অংশে নাম-উল্লেখ ছিল — এখানেই তার
পুরো anatomy। experiment/build সেকশনে ঠিক এই সংখ্যাগুলো কোডে চালিয়ে
দেখাব।
Monotonic clock বনাম wall clock — ভুল যন্ত্র বাছলে কী হয়
দুই ধরনের ঘড়ি OS প্রতিটা প্রোগ্রামকে দেয়:
Wall clock (time.time()) | Monotonic clock (time.monotonic()) | |
|---|---|---|
| মানে কী | calendar-time-এর কাছাকাছি একটা মান | কোনো এক reference point থেকে গণনা — মান নিজে অর্থহীন |
| NTP step-এ | লাফাতে পারে (সামনে বা পেছনে) | কখনো লাফায় না, কখনো পেছায় না |
| কাজ | timestamp/log/audit-trail | elapsed time, timeout, lease expiry |
| দুই machine-এ তুলনা | intent (কিন্তু skew-সহ) | অর্থহীন — শুধু নিজের machine-এর ভেতরে ব্যবধান বৈধ |
Elapsed-time মাপতে wall clock ব্যবহার করলে এই bug জন্মায়:
import time
start = time.time() # ভুল যন্ত্র — wall clock
do_slow_work() # এর মাঝখানে NTP একটা বড় step করল, ঘড়ি ৩ সেকেন্ড পেছাল
elapsed = time.time() - start
print(elapsed) # হতে পারে: -2.87 ← ঋণাত্মক সময়!Timeout logic, rate-limiter, lease-expiry — সবকিছু ধরে নেয়
elapsed >= 0; negative মান পেলে অনেক বাস্তব bug ঘটে (timeout
কখনো trigger না হওয়া, lease চিরকাল বৈধ মনে হওয়া, ইত্যাদি)। সঠিক
কোড:
start = time.monotonic() # ঠিক যন্ত্র — কখনো পেছায় না
do_slow_work()
elapsed = time.monotonic() - start
print(elapsed) # সবসময় >= 0, guarantee-সহনিয়ম সহজ: timestamp/log-এ wall clock, elapsed-time/timeout-এ monotonic clock — দুইটা ভিন্ন কাজ, ভিন্ন যন্ত্র, গুলিয়ে ফেললে নীরবে ভুল।
- lease acquireaudit-log-এ কখন acquire হলো — wall clock, human-readable timestamp
- expiry-timer সেটশুরু-মুহূর্ত রেকর্ড — monotonic clock (elapsed মাপবে বলে)
- renew-loopmonotonic.now() - start < 5s? — NTP step এখানে প্রভাব ফেলে না
- expiry ঘটলেlease হারানোর মুহূর্ত আবার audit-log-এ — wall clock ফিরে আসে
উদাহরণ
সংখ্যায় পুরো গল্প — একই দৃশ্য, দুই লেন্স
উপরের হুড-সেকশনের LWW anomaly-কে একটা টেবিলে টানটান করে রাখি — এই সংখ্যাগুলোই experiment/build-এ কোড হয়ে চলবে:
| Machine | সত্যিকারের সময় (real τ, ms) | ঘড়ির offset | লেখা timestamp | |
|---|---|---|---|---|
| write A | M1 | 1000 | 0 (নিখুঁত) | 1000 |
| write B | M2 | 1050 (৫০ms পরে) | −100 (পিছিয়ে) | 950 |
max(1000, 950) = 1000 → LWW A রাখে। কিন্তু real-time-এর ক্রম
A (τ=1000) → B (τ=1050) — B-ই আসলে শেষ ইচ্ছা। ফাঁকটা ঠিক কোথায়
জন্মালো তা এক লাইনে: timestamp generator (wall clock) আর
real-time truth-এর মধ্যে offset নামের একটা persistent bias।
offset শূন্য হলে সমস্যা নেই; NTP offset-কে ছোট রাখে কিন্তু
শূন্য করে না — তাই bug থেকেই যায়, শুধু সম্ভাবনা কমে।
লক্ষ্য করুন: এই একই সংখ্যা দিয়ে NTP-র offset-formula-ও যাচাই করা যায় যদি M1-M2-কে client-server হিসেবে ভাবি — সেটাই experiment সেকশনের প্রথম part, তবে ভিন্ন scenario-তে (সেখানে আমরা offset-টা উদ্ধার করব, এখানে offset-টা ব্যবহার করে ক্ষতি দেখালাম) — দুইটা একই মুদ্রার দুই পিঠ: measure বনাম consequence।
ছবিতে
client (ঘড়ি: true time) server (ঘড়ি: true time + offset θ)
│ │
t0 ─●── request ──────────────────► │
│ \ │
│ \ network delay │
│ ▼ t1 ─● receive
│ │
│ t2 ─● reply পাঠাল
│ ▲ │
│ / network delay │
│ / │
t3 ─●◄── response ──────────────── │
offset ≈ ((t1−t0)+(t2−t3)) / 2 delay ≈ (t3−t0)−(t2−t1) real time ────────────────────────────────────►
τ=1000 τ=1050
M1 লেখে "A" M2 লেখে "B" (real-এ পরে!)
ts=1000 ts=950 (ঘড়ি −100ms পিছিয়ে)
timestamp-line (LWW এই লাইনটাই দেখে) ─────────►
950 ─────────────────── 1000
"B" "A" ← max(), LWW winner
ফলাফল: "A" থেকে যায়, "B" নীরবে হারায় — যদিও "B" real-time-এ শেষ লেখানিজে চালিয়ে দেখুন
Toy NTP offset/delay estimator — hand-constructed scenario দিয়ে formula যাচাই
# ntp_offset.py — toy NTP-style offset/delay estimator
def ntp_estimate(t0, t1, t2, t3):
offset = ((t1 - t0) + (t2 - t3)) / 2
delay = (t3 - t0) - (t2 - t1)
return offset, delay
# ---------- অংশ ১: hand-constructed scenario, জানা সত্য মান ----------
# client-এর ঘড়ি = true time (offset 0)। server-এর ঘড়ি true time + 50ms এগিয়ে।
# এক-দিকের network delay = 10ms; server তৎক্ষণাৎ reply করে (processing = 0)।
#
# t0 = 0 client পাঠাল (client ঘড়ি)
# t1 = 0 + 10 + 50 = 60 server পেল (server ঘড়ি: +10ms delay, +50ms skew)
# t2 = 60 server তৎক্ষণাৎ পাঠাল (server ঘড়ি)
# t3 = 0 + 10 + 10 = 20 client পেল (client ঘড়ি: দুই-দিকের delay মোট 20ms)
t0, t1, t2, t3 = 0, 60, 60, 20
offset, delay = ntp_estimate(t0, t1, t2, t3)
print(f"t0={t0} t1={t1} t2={t2} t3={t3}")
print(f"estimated offset = {offset:.1f} ms (সত্য মান ছিল +50 ms)")
print(f"estimated delay = {delay:.1f} ms (সত্য round-trip delay ছিল 20 ms)")
assert offset == 50.0 and delay == 20.0
print("OK: চার-timestamp formula সঠিক offset+delay উদ্ধার করেছে")
# ---------- অংশ ২: asymmetric delay হলে formula-র নীরব ভুল ----------
# ধরুন forward leg 5ms, backward leg 15ms (মোট delay একই 20ms) — কিন্তু
# formula symmetric ধরে নেয়, তাই offset ভুল বের হবে যদিও round-trip delay ঠিকই আসবে।
t0, t1 = 0, 55 # 0 + 5(delay) + 50(offset) = 55
t2, t3 = 55, 20 # 55 + 15(delay) = 70 হওয়া উচিত ছিল client-ঘড়িতে, কিন্তু
# client ঘড়ি offset-ছাড়া তাই t3 = 5 + 15 = 20 (এখানে ভুল ধরাই লক্ষ্য)
offset2, delay2 = ntp_estimate(t0, t1, t2, t3)
print(f"asymmetric leg (5ms + 15ms) দিয়ে estimate: offset={offset2:.1f}ms delay={delay2:.1f}ms")
print("লক্ষ্য করুন offset আর সঠিক +50ms নয় — symmetric-delay ধারণা ভাঙলে formula নীরবে ভুল দেয়")প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):
t0=0 t1=60 t2=60 t3=20
estimated offset = 50.0 ms (সত্য মান ছিল +50 ms)
estimated delay = 20.0 ms (সত্য round-trip delay ছিল 20 ms)
OK: চার-timestamp formula সঠিক offset+delay উদ্ধার করেছে
asymmetric leg (5ms + 15ms) দিয়ে estimate: offset=42.5ms delay=20.0ms
লক্ষ্য করুন offset আর সঠিক +50ms নয় — symmetric-delay ধারণা ভাঙলে formula নীরবে ভুল দেয়তিনটা পাঠ: (১) round-number scenario-তে formula সংখ্যায় হুবহু সত্য
মান ফেরত দিল — যোগে delay বাতিল হয়ে offset একা বেরোয়; (২) delay
সবসময় সঠিক বেরোয় (এটা শুধু round-trip মোট, যা symmetric-না-হলেও
(t3−t0)−(t2−t1)-এ ঠিক থাকে); (৩) offset-এর accuracy পুরোপুরি
symmetric-delay assumption-এর ওপর নির্ভর করে — WAN-এ path asymmetric
হলে এখানেই accuracy হারায়, তাই stratum-নিচের সার্ভারে error বেশি।
চার-timestamp থেকে offset/delay formula একটা জানা-উত্তরের scenario-তে হুবহু সঠিক মান ফেরত দেয় — offset যোগ-বিয়োগে বাতিল-অবাতিল হয়ে আলাদা হয়ে বেরোয়, আর symmetric-delay ধারণা ভাঙলে (দ্বিতীয় অংশ) estimate-ও ভুল হয় তা দেখায়।
নিজে বানান
ClockSkewLab — LWW anomaly simulator + monotonic-vs-wall guard
- Machine abstraction: প্রতিটার একটা fixed clock-offset (ms); real_time দিলে own_clock() = real_time + offset
- LWW merge function: (machine, timestamp, value)-দের মধ্যে max-timestamp জেতে
- দুই machine, বিপরীত skew দিয়ে concrete real-order বনাম timestamp-order দেখান
- একই লাইনে monotonic-guard যোগ করুন: elapsed মাপার সময় ঋণাত্মক এলে assert-এ ধরুন
- সীমা-demo: offset যত বড়, real-order-বিরোধী উইন্ডো তত চওড়া — সংখ্যায় দেখান
লক্ষ্য: hood/example সেকশনের ঠিক সেই M1/M2 সংখ্যা কোডে চালিয়ে দেখানো যে LWW সত্যিই ভুল write রাখে — আর তার পাশে দেখানো যে monotonic clock ব্যবহার করলে negative-elapsed bug কখনোই ঘটে না।
# clock_skew_lab.py — ClockSkewLab: LWW anomaly + monotonic guard
class Machine:
def __init__(self, name, offset_ms):
self.name = name
self.offset_ms = offset_ms # সত্যিকারের সময় থেকে কত পিছিয়ে/এগিয়ে
def clock(self, real_time_ms):
return real_time_ms + self.offset_ms # নিজের (সম্ভবত skewed) ঘড়ি
def lww_merge(writes):
# writes: list of (machine_name, timestamp, value); সবচেয়ে বড় timestamp জেতে
return max(writes, key=lambda w: w[1])
M1 = Machine("M1", offset_ms=0) # নিখুঁত
M2 = Machine("M2", offset_ms=-100) # ১০০ms পিছিয়ে
REAL_A, REAL_B = 1000, 1050 # real time-এ B, A-র ৫০ms পরে লেখা হলো
ts_A = M1.clock(REAL_A) # 1000
ts_B = M2.clock(REAL_B) # 950
writes = [("M1", ts_A, "A"), ("M2", ts_B, "B")]
winner = lww_merge(writes)
print(f"write A: machine=M1 real_time={REAL_A} timestamp={ts_A}")
print(f"write B: machine=M2 real_time={REAL_B} timestamp={ts_B} (real-এ ৫০ms পরে!)")
print(f"LWW winner: {winner}")
real_order_winner = "B" if REAL_B > REAL_A else "A"
assert winner[2] == "A"
assert real_order_winner == "B"
print(f"real-time-এ শেষ লেখা ছিল '{real_order_winner}', কিন্তু LWW রাখল "
f"'{winner[2]}' — {abs(M2.offset_ms)}ms skew-ই একমাত্র কারণ")
# ---------- monotonic guard: negative-elapsed bug কখনো এখানে ঘটবে না ----------
class FakeWallClock:
"""সময়ের মাঝে একবার বড় step (ঘড়ি পেছনে লাফায়) — বাস্তব NTP-correction।"""
def __init__(self):
self.t = 0
def time(self):
return self.t
class FakeMonotonicClock:
"""একই দৃশ্যে monotonic — শুধু এগোয়, step-এর প্রভাব নেই।"""
def __init__(self):
self.t = 0
def monotonic(self):
return self.t
wall, mono = FakeWallClock(), FakeMonotonicClock()
wall.t, mono.t = 100, 100
start_wall, start_mono = wall.time(), mono.monotonic()
wall.t = 97 # NTP step: wall clock ৩ একক পিছিয়ে গেল
mono.t = 106 # monotonic শুধু এগোয় — real elapsed যাই হোক
elapsed_wall = wall.time() - start_wall
elapsed_mono = mono.monotonic() - start_mono
print(f"wall-clock দিয়ে মাপা elapsed = {elapsed_wall} (ঋণাত্মক! bug)")
print(f"monotonic দিয়ে মাপা elapsed = {elapsed_mono} (>= 0, guarantee-সহ)")
assert elapsed_wall < 0
assert elapsed_mono >= 0
print("OK: wall clock negative-elapsed দিল, monotonic দেয়নি — এটাই দুইটার তফাত")প্রত্যাশিত output — সংখ্যাগুলো hood/example সেকশনের সাথে হুবহু মেলে:
write A: machine=M1 real_time=1000 timestamp=1000
write B: machine=M2 real_time=1050 timestamp=950 (real-এ ৫০ms পরে!)
LWW winner: ('M1', 1000, 'A')
real-time-এ শেষ লেখা ছিল 'B', কিন্তু LWW রাখল 'A' — 100ms skew-ই একমাত্র কারণ
wall-clock দিয়ে মাপা elapsed = -3 (ঋণাত্মক! bug)
monotonic দিয়ে মাপা elapsed = 6 (>= 0, guarantee-সহ)
OK: wall clock negative-elapsed দিল, monotonic দেয়নি — এটাই দুইটার তফাতদুই মুহূর্ত মনে রাখার মতো: প্রথম block-এ 100ms skew-ই একমাত্র
কারণ যে সঠিক write হারালো — কোনো network loss, কোনো bug নেই, শুধু
ঘড়ি; দ্বিতীয় block-এ একই ভুল-যন্ত্র (wall clock) elapsed-time মাপায়
ব্যবহার করলে ঠিক কী ভাঙে তা এক লাইনে দেখা গেল।
নিজে বাড়ান:
offset_msকে parameter করে দেখুন কত বড় skew হলে “৫০ms পরের write” আবার harার order-ই invert করে দেয় — একটা threshold খুঁজুন- তিন-machine version বানান যেখানে pairwise skew ভিন্ন — কোন pair সবচেয়ে বিপজ্জনক?
lww_merge-এ tie-breaker (timestamp সমান হলে?) যোগ করুন — বাস্তব system-গুলো এখানে machine-id বা random ব্যবহার করে, কেন সেটাও একটা compromise তা লিখুন
বাস্তব সিস্টেমে
বাস্তব জগতে physical time
NTP-র রোজকার জীবন। প্রায় প্রতিটা Linux/macOS/Windows machine
ডিফল্টে chrony, ntpd, বা timesyncd-এর মতো daemon চালায় —
public pool (pool.ntp.org) বা cloud-provider-এর নিজস্ব stratum-1
সার্ভার (AWS Time Sync Service, Google Public NTP) থেকে sync নেয়।
Cloud VM-গুলো hypervisor-এর মধ্য দিয়ে চলার কারণে স্বাভাবিক hardware
clock-এর চেয়ে বেশি drift-প্রবণ — তাই cloud provider নিজস্ব
low-latency NTP endpoint দেয়।
PTP — যেখানে NTP-র ms যথেষ্ট নয়। Financial trading, telecom আর high-frequency measurement-এ Precision Time Protocol (PTP) hardware-timestamping দিয়ে sub-microsecond accuracy দেয় — NTP-র software-timestamp-জনিত jitter এড়িয়ে।
Spanner-এর TrueTime — uncertainty-কে লুকানোর বদলে প্রকাশ করা।
সাধারণ ঘড়ি একটা single number দেয় আর ভান করে সেটাই সত্য। Google
Spanner (Corbett et al., OSDI 2012) উল্টো পথ নেয়: প্রতিটা
TT.now() call একটা interval ফেরত দেয় — [earliest, latest]
— যার মধ্যে সত্যিকারের সময় থাকবে guaranteed (atomic clock + GPS
receiver-এর redundant array আর conservative error-bound দিয়ে)।
Commit করার সময় Spanner সেই uncertainty-window শেষ না হওয়া পর্যন্ত
ইচ্ছাকৃতভাবে অপেক্ষা করে — একে বলে commit-wait। ফলাফল: দুইটা
transaction-এর মধ্যে সত্যিকারের causal সম্পর্ক থাকলে তাদের
TrueTime-interval কখনো overlap করে না, তাই timestamp-order-ই
বিশ্বাসযোগ্য global order হয়ে ওঠে — কিন্তু দামটা commit-wait-এর
latency, আর নির্ভরতা বিশেষ hardware-এর ওপর। পূর্ণ mechanism এই
লেসনের সীমার বাইরে — মনে রাখার বিষয় শুধু এই দর্শন-বদল: ঘড়িকে
নিখুঁত ভান না করে, ভুলের সীমাটাকেই স্বীকার করে কাজে লাগানো।
Kubernetes lease আর distributed lock। Leader-election lease-র
TTL হিসাব সবসময় monotonic clock-ভিত্তিক হওয়া উচিত — না হলে node-এর
wall clock একটা বড় NTP-step খেলে lease আগেভাগে expire (বা কখনো
expire না) হতে পারে। বাস্তব bug-report-এ ঠিক এই কারণেই split-brain
দেখা গেছে যেখানে lease-tracking কোড ভুলে time.time() ব্যবহার
করেছিল।
যে ভুলগুলো সবাই করে
“NTP sync করা মানে সব মেশিনের ঘড়ি এখন একই।”
NTP drift bound করে, শূন্য করে না। Sync-এর পরেও দুই machine-এর
মধ্যে সাধারণত কয়েক ms (LAN) থেকে কয়েক দশ ms (WAN) ফারাক থেকে যায়।
“Sync হয়েছে” মানে |skew| < ε, skew = 0 নয় — আর ε-ই আজকের
লেসনের সব anomaly-র উৎস।
“বড় timestamp মানেই পরে ঘটেছে।”
শুধু তখনই সত্য যদি দুই timestamp একই ঘড়ি থেকে আসে। ভিন্ন machine-এর timestamp তুলনা করলে ঘড়ির offset-ও তুলনায় ঢুকে পড়ে — LWW anomaly ঠিক এই ভুল ধারণার ফসল। বড় timestamp মানে শুধু সেই machine-এর নিজের ঘড়িতে বড়; real-time order-এর গ্যারান্টি নয়।
“NTP correction সবসময় ঘড়িকে সামনের দিকেই নিয়ে যায়, তাই elapsed-time হিসাব নিরাপদ।”
Step correction যেকোনো দিকে যেতে পারে — যদি local ঘড়ি সত্যিকারের
সময়ের চেয়ে এগিয়ে থাকে, correction ঘড়িকে পেছনে ঠেলে দেয়।
তখনই end_time - start_time ঋণাত্মক হয়ে যায়। এই কারণেই elapsed-time
কখনো wall clock দিয়ে মাপা উচিত না — monotonic clock-এর সংজ্ঞাই
“কখনো পেছায় না”।
“Physical clock-এর সমস্যা শুধু ছোট-বাজেট বা misconfigured সিস্টেমে হয় — ভালো infrastructure-এ ঘটে না।”
Google-এর মতো infrastructure-ও (atomic clock, GPS receiver, redundant hardware) skew শূন্য করেনি — তারা TrueTime বানিয়ে uncertainty-কে explicit করেছে আর algorithm-কে সেই uncertainty মেনে design করতে বাধ্য করেছে (commit-wait)। সমস্যাটা বাজেটের নয়, physics-এর: কোনো দুইটা independent ঘড়ি কখনো নিখুঁত এক থাকে না।
বুঝেছেন কি না দেখুন
1NTP-র চার timestamp t0, t1, t2, t3 কী কী মুহূর্ত ধরে, আর offset/delay-র formula দুইটা লিখুন।
স্মরণ
t0, t1, t2, t3 কী কী মুহূর্ত ধরে, আর offset/delay-র formula দুইটা লিখুন।t0 client পাঠাল (client ঘড়ি), t1 server পেল (server ঘড়ি), t2
server পাঠাল (server ঘড়ি), t3 client পেল (client ঘড়ি)।
offset ≈ ((t1−t0)+(t2−t3))/2, delay ≈ (t3−t0)−(t2−t1)।
2Experiment-এর দ্বিতীয় অংশে symmetric-delay ধরেও asymmetric (5ms + 15ms) leg দিলে offset ভুল বেরোল (42.5ms, সত্য 50ms) অথচ delay ঠিক থাকল (20ms)। কেন দুইটার ভাগ্য আলাদা?
যুক্তি
offset ভুল বেরোল (42.5ms, সত্য 50ms) অথচ delay ঠিক থাকল (20ms)। কেন দুইটার ভাগ্য আলাদা?delay formula শুধু মোট round-trip সময় বের করে, forward/backward
আলাদা করে না — তাই asymmetry-তে অক্ষত থাকে। offset formula-র
derivation-এ ধরে নেওয়া হয় forward-leg delay = backward-leg delay,
যাতে যোগ করলে delay-অংশ বাতিল হয়ে যায়; সেই ধরে নেওয়াটা ভাঙলে
বাতিল হওয়া অংশে error ঢুকে offset-এ থেকে যায়।
3Build-এ M2 offset -100ms না হয়ে -30ms হলে (একই real-time ৫০ms ব্যবধান-সহ) LWW-র রায় কী হতো, আর কেন?
প্রয়োগ
M2 offset -100ms না হয়ে -30ms হলে (একই real-time ৫০ms ব্যবধান-সহ) LWW-র রায় কী হতো, আর কেন?ts_B = 1050 - 30 = 1020, যা ts_A = 1000-এর চেয়ে বড় — LWW এবার
B-কেই জেতাবে, যেটা real-order-এর সাথেও মেলে। মানে skew ৫০ms-এর
কম হলে এই নির্দিষ্ট anomaly ঘটে না — বিপদ তখনই আসে যখন skew,
লেখাদ্বয়ের real-time ব্যবধানের চেয়ে বড়।
4Build-এর monotonic-guard অংশে elapsed_wall = -3 কীভাবে জন্মালো — কোন দুইটা মান বিয়োগ হয়ে?
যুক্তি
elapsed_wall = -3 কীভাবে জন্মালো — কোন দুইটা মান বিয়োগ হয়ে?start_wall = 100 রেকর্ড হওয়ার পরে simulated NTP-step wall clock-কে
97-এ নামিয়ে দেয় (৩ একক পিছনে)। elapsed_wall = wall.time() - start_wall = 97 - 100 = -3। Monotonic ঘড়ি একই ঘটনায় প্রভাবিত হয়নি
বলে elapsed_mono সবসময় অ-ঋণাত্মক থেকে গেছে।
5একটা distributed lock-এর TTL-tracking কোড লিখতে বললে আপনি কোন ঘড়ি ব্যবহার করবেন — acquire-এর audit-log timestamp-এর জন্য, আর TTL-countdown-এর জন্য? দুইটা আলাদা কেন?
ডিজাইন
Audit-log timestamp: wall clock — মানুষ পড়বে, calendar-time দরকার, absolute value-ই উদ্দেশ্য। TTL-countdown: monotonic clock — শুধু elapsed মাপা দরকার, NTP step-এ lock আগেভাগে expire বা কখনো-না-expire হওয়া রোধ করতে হবে। একটা কাজে wall clock ব্যবহার করলে lock-এর correctness নেটওয়ার্ক-sync-এর দয়ায় নির্ভর করত।
6TrueTime [earliest, latest] interval দেয়, single number নয়। এই ডিজাইন choice কোন misconception-টা সরাসরি এড়ায় যা এই লেসনের অন্য anomaly-গুলোর মূল?
যুক্তি
[earliest, latest] interval দেয়, single number নয়। এই ডিজাইন choice কোন misconception-টা সরাসরি এড়ায় যা এই লেসনের অন্য anomaly-গুলোর মূল?“ঘড়ি একটা নিখুঁত single instant জানে” — এই ভান TrueTime করে না; বরং uncertainty-কে ডেটা-টাইপের অংশ বানিয়ে দেয়, algorithm-কে সেই uncertainty মেনে (commit-wait) নিতে বাধ্য করে। LWW anomaly ঠিক উল্টো ভুলের ফল — timestamp-কে নিখুঁত single truth ধরে নেওয়া।
এরপর কী
এরপর কী
আজ দেখলাম physical clock — এমনকি NTP-র মতো protocol দিয়ে correct করার পরেও — একটা bounded কিন্তু non-zero skew রেখে যায়, আর সেই skew LWW-এর মতো সহজ নিয়মে নীরবে ভুল write জিতিয়ে দেয়। TrueTime-এর মতো সিস্টেম সেই uncertainty-কে explicit করে সামলায়, কিন্তু বিশেষ hardware আর commit-wait-latency-র দামে।
তাহলে প্রশ্ন দাঁড়ায়: যদি সবচেয়ে যত্নশীল physical-clock-সমাধানও হার্ডওয়্যার নির্ভর আর অসম্পূর্ণ থাকে, তাহলে হয়তো wall-clock সময়টাই ভুল প্রশ্ন। ordering-এর জন্য “কখন ঘটেছে” জানার দরকার নেই — দরকার শুধু “কে কার আগে ঘটেছে জানা”। পরের লেসনে সেই দিকেই মোড়: logical clock — Lamport clock আর vector clock — যারা wall-clock সময় স্পর্শও করে না, শুধু causal সম্পর্ক ট্র্যাক করে। ঘড়ি-নির্ভরতা পুরোপুরি বাদ দিলে কী পাওয়া যায়, কী হারানো যায় — সেটাই সামনের বিষয়।
আরও পড়ুন
- Network Time Protocol Version 4: Protocol and Algorithms Specification — David L. Mills, J. Martin (ed.), J. Burbank, W. Kasch — RFC 5905, IETF, 2010 · চার-timestamp exchange, offset/delay formula, stratum hierarchy — এই লেসনের NTP-অংশের প্রাথমিক উৎস
- Internet Time Synchronization: The Network Time Protocol — David L. Mills — IEEE Transactions on Communications, 1991 · NTP-র মূল নকশা-পত্র; slew বনাম step discipline-এর যুক্তি
- Spanner: Google's Globally-Distributed Database — James C. Corbett et al. — OSDI 2012 · TrueTime — explicit uncertainty interval আর commit-wait দিয়ে single-instant illusion এড়ানোর নকশা
- Designing Data-Intensive Applications, Chapter 8: The Trouble with Distributed Systems — Martin Kleppmann · Unreliable clocks-এর practitioner-গ্রেড আলোচনা — process pause, NTP step, monotonic বনাম wall clock