Failure Models — Crash থেকে Byzantine পর্যন্ত ভাঙার সোপান
Failure Models: From Crash to Byzantine
Partial failure কতভাবে ঘটতে পারে তার নামকরণ — crash-stop থেকে omission, timing, আর সবচেয়ে কঠিন Byzantine পর্যন্ত adversary-strength ladder — আর দেখানো কেন সঠিক model বাছাই replica-সংখ্যা থেকে algorithm-design পর্যন্ত সবকিছু বদলে দেয়।
আগে এটা বুঝি
গত লেসনে দেখলাম partial failure অনিবার্য, আর সেটা চেনা অনিশ্চিত — timeout একমাত্র অস্ত্র, আর সেই অস্ত্র দুই দিকেই ভুল করে। কিন্তু “ভাঙা” শব্দটাই আসলে একটা বাক্স — ভেতরে অনেক রকম আচরণ গুঁজে দেওয়া হয়েছে। একটা node বন্ধ হয়ে যাওয়া, একটা message পথে হারানো, একটা উত্তর অস্বাভাবিক দেরিতে আসা, আর একটা node সরাসরি মিথ্যা বলা — এই চারটা ঘটনা user-এর কাছে হয়তো একরকম লাগে (“সিস্টেম গণ্ডগোল করছে”), কিন্তু algorithm-designer-এর কাছে সম্পূর্ণ আলাদা চারটা পৃথিবী।
আজকের প্রশ্ন তাই কোনো নতুন সমস্যা নয় — গত লেসনের “ভাঙা” শব্দটাকে শ্রেণিবদ্ধ করা:
আমার protocol যে “শত্রু”-র বিরুদ্ধে সঠিক থাকবে বলে দাবি করছে, সেই শত্রু ঠিক কতটা শক্তিশালী?
এই প্রশ্নের উত্তরের নাম failure model — এবং এই একটা পছন্দ পুরো design-এর দাম ঠিক করে দেয়। আজ চারটা কাজ। এক: adversary-strength ladder দাঁড় করানো — crash-stop থেকে Byzantine পর্যন্ত, প্রতিটা ধাপ আগেরটাকে গিলে খায়। দুই: real system-রা কে কোন ধাপ assume করে, আর সেই পছন্দের দাম replica-সংখ্যায় কত। তিন: “fail-stop” নামের একটা সুবিধাজনক মিথ্যা, আর সেটা কোথায় ভেঙে zombie-leader জন্ম দেয়। চার: নিজের হাতে একটা timeout-based failure detector বানিয়ে দেখা — নিখুঁত detection বলে কিছু নেই, শুধু trade-off।
মূল ধারণা
Adversary-strength ladder — পাঁচ ধাপ
প্রতিটা ধাপে প্রশ্ন একটাই: node আর message-দের এই দুনিয়ায় “ভুল” ঠিক কতদূর যেতে পারে? পাঁচ ধাপ, দুর্বল থেকে শক্তিশালী:
| ধাপ | নাম | সংজ্ঞা | বাসিন্দা-উদাহরণ |
|---|---|---|---|
| ১ | Crash-stop | Process যেকোনো মুহূর্তে থামে, চিরতরে চুপ — আর ফেরে না | OOM-killer একটা process মেরে দিল; সেই PID কখনো আর জেগে ওঠে না |
| ২ | Crash-recovery | Process থামতে পারে, আবার restart-ও হতে পারে; volatile state হারায়, persisted state থাকে | Raft node পাওয়ার-সাইকেলের পরে ফেরে, disk-এ লেখা currentTerm পড়ে আবার যোগ দেয় |
| ৩ | Omission | Process নিজে ঠিকই চলে, কিন্তু message হারায় বা পাঠাতে/গ্রহণ করতে ব্যর্থ হয় | Switch buffer-overflow-এ একটা packet ফেলে দিল — দুই প্রান্তের কেউ crash করেনি |
| ৪ | Timing | Process/message crash-ও করে না, message-ও হারায় না — কিন্তু expected সময়সীমার তুলনায় যথেচ্ছ ধীর বা দ্রুত হতে পারে, উত্তর সৎই থাকে | GC pause-এ node ৩০ সেকেন্ড কিছু বলল না, তারপর ঠিক উত্তরই দিল — শুধু দেরিতে |
| ৫ | Byzantine | Process ইচ্ছেমতো যেকোনো আচরণ করতে পারে — crash, omission, timing violation, আর সবচেয়ে খারাপ: সরাসরি মিথ্যা বলা বা দুই পক্ষকে দুই রকম কথা বলা | Bit-flip memory-তে ভুল ভোট গণনা হলো; বা compromised replica ইচ্ছাকৃতভাবে দুই peer-কে দুই ভিন্ন value পাঠাল |
প্রতিটা ধাপ ঠিক আগেরটার strict superset — এইটাই ladder-এর আসল বার্তা। একটা Byzantine node চাইলে শুধু crash করতে পারে (সেটাও তো এক ধরনের “যা খুশি তাই” আচরণ), অথবা শুধু message ফেলে দিতে পারে, অথবা শুধু দেরি করতে পারে — সবগুলোই তার “মেনু”-তে আছে, উপরন্তু মিথ্যা বলার বিকল্পও। ফলে:
লক্ষ করুন omission আর timing-এর পার্থক্য: omission মানে message/response সম্পূর্ণ হারানো; timing মানে কিছুই হারায় না, শুধু ঘড়ির প্রতিশ্রুতি ভাঙে। দুটোই “সৎ” ব্যর্থতা — কেউ মিথ্যা বলছে না, শুধু নেটওয়ার্ক বা scheduler অসহযোগিতা করছে। Byzantine-এই প্রথম উদ্দেশ্যমূলক/দুর্ঘটনাজনিত মিথ্যা ঢোকে — এবং এইখান থেকেই protocol-এর দাম লাফিয়ে ওঠে।
ভেতরে কী ঘটছে
কে কোন ধাপ assume করে
Production-এর প্রায় সব consensus system — Raft (etcd, Consul), ZooKeeper-এর ZAB, Multi-Paxos-ভিত্তিক system — crash-recovery model ধরে। ধরে নেয়: node মিথ্যা বলবে না, শুধু থামতে বা ফিরতে পারে। উল্টোদিকে PBFT, Tendermint, আর প্রায় সব public blockchain consensus Byzantine model ধরে — কারণ permissionless নেটওয়ার্কে “participant সৎ থাকবে” ধরে নেওয়াই যায় না।
| System | Failure model | f=1-এ replica-সংখ্যা |
|---|---|---|
| Raft (etcd, Consul) | Crash-recovery | 3 (১টা crash সহনশীল) |
| ZooKeeper (ZAB) | Crash-recovery | 3 |
| PBFT | Byzantine | 4 (১টা arbitrary/malicious সহনশীল) |
| Tendermint / বেশিরভাগ blockchain consensus | Byzantine | 4, permissionless-এ সাধারণত আরও বেশি |
Replica-গণিত — 2f+1 বনাম 3f+1
দুই সংখ্যাই মুখস্থ পাওয়া যায় সবখানে; আজ কেন সেটা দাঁড় করাই।
Crash-tolerant quorum। Crash node শুধু চুপ করে — মিথ্যা বলে না। তাই
majority quorum (⌈(n+1)/2⌉ সদস্য) থেকে উত্তর পেলে সেটা বিশ্বাসযোগ্য। শর্ত
দুটো: (ক) quorum যেন f-টা crashed node ছাড়াই গঠন করা যায় — তাই quorum
size <= n - f; (খ) দুই ভিন্ন quorum-এর মধ্যে অন্তত ১টা সদস্য common
থাকা লাগবে (majority-overlap — এটা মৌলিক pigeonhole ফ্যাক্ট, majority সবসময়
majority-কে ছোঁয়)। দুটো মিলিয়ে সমাধান করলে পাওয়া যায় n >= 2f+1।
Byzantine-tolerant quorum। এখানে খেলাটা বদলে যায় — quorum-এর ভেতরের
সদস্যও মিথ্যা বলতে পারে। শুধু ১টা common সদস্য যথেষ্ট না, কারণ সেই একজনই
হয়তো liar। দরকার: overlap-এ অন্তত ১টা honest সদস্য থাকা নিশ্চিত —
মানে f-টা সম্ভাব্য liar বাদ দিয়েও যেন কেউ থাকে, তাই overlap >= f+1।
- দাবিn নোডের মধ্যে f-টা Byzantine (মিথ্যাবাদী) হলেও সবাই এক সিদ্ধান্তে পৌঁছাবে
- quorum size QQ = n - f — কারণ f-টা নোড সাড়াই নাও দিতে পারে, quorum তাদের ছাড়াই গঠনযোগ্য হতে হবে
- দুই quorum-এর overlappigeonhole-এ ন্যূনতম overlap = 2Q - n
- overlap-এ honest witness লাগবেf-টা liar overlap-এর ভেতরেও থাকতে পারে, তাই safe থাকতে overlap >= f+1 চাই
- সমাধান2Q - n >= f+1 আর Q = n-f বসালে সরল করে পাওয়া যায় n >= 3f+1
এক লাইনে সারকথা: crash node শুধু “না-বলা”-য় বিশ্বাসঘাতকতা করে, তাই একজন
সাক্ষীই যথেষ্ট। Byzantine node “ভুল-বলা”-য়ও বিশ্বাসঘাতকতা করতে পারে, তাই
সাক্ষীর সংখ্যা বাড়িয়ে liar-দের ছাপিয়ে যেতে হয় — honest minority আর lying
majority-কে আলাদা করার একমাত্র উপায় হলো এত বেশি honest সদস্য রাখা যে কোনো
f-সংখ্যক liar কখনো majority বানাতেই না পারে।
| f (সহনশীল fault) | Crash — n = 2f+1 | Byzantine — n = 3f+1 |
|---|---|---|
| 1 | 3 | 4 |
| 2 | 5 | 7 |
| 3 | 7 | 10 |
লক্ষ করুন f বাড়ার সাথে দুই মডেলের ব্যবধান বাড়ে — শুধু constant-factor না,
প্রতিটা extra fault-এর দাম Byzantine-এ তিনগুণ। আর এটা শুধু replica-সংখ্যা
নয়: PBFT-এ প্রতিটা decision-এ extra voting round আর cryptographic signature
লাগে, যাতে liar-দের ধরা যায় ও তাদের বক্তব্য অন্যদের কাছে প্রমাণযোগ্য
হয়। Crash model কখনো “কে মিথ্যা বলল প্রমাণ করো” প্রশ্নটাই তোলে না।
উদাহরণ
Fail-stop idealization — সুবিধাজনক মিথ্যা
Textbook algorithm-এর অনেকগুলোই “fail-stop” নামের একটা তৃতীয় ধারণা ব্যবহার করে — crash-stop-এর চেয়েও শক্তিশালী idealization: process থামে এবং বাকি সবাই সেই মৃত্যুটা তাৎক্ষণিকভাবে ও নির্ভুলভাবে জানে (যেন কোনো নিখুঁত oracle ঘোষণা করে দিল)। কেন designer-রা এই মিথ্যাটা মেনে নেয়? কারণ এটা proof লেখা সহজ করে দেয় — “node X মরেছে” যদি fact হিসেবে ধরে নেওয়া যায়, তাহলে algorithm-কে আর suspicion-এর অনিশ্চয়তা সামলাতে হয় না। Schneider-এর classic fail-stop processor abstraction ঠিক এই সরলীকরণের ওপর দাঁড়িয়ে।
বাস্তবে এই oracle নেই। গত লেসনের কেন্দ্রীয় শিক্ষা মনে করুন: detection মানেই suspicion, fact না। যেখানে fail-stop idealization ভেঙে পড়ে সেই দৃশ্যটা এবার concrete করে দেখি:
t=0 Node-N leader; lock/lease হাতে; সবাইকে জানায় "আমি বেঁচে, আমিই leader"
t=1 Node-N-এর JVM-এ full GC শুরু — stop-the-world, কোনো thread চলছে না,
কোনো heartbeat বের হচ্ছে না — বাইরে থেকে দেখতে ঠিক crash-এর মতোই
t=1..30 বাকি node-রা heartbeat-এর অপেক্ষায়; timeout ফুরোলো — "N মৃত" সিদ্ধান্ত
t=15 (অন্যদের চোখে) নতুন election, Node-M নতুন leader নির্বাচিত, নতুন lease ইস্যু
t=30 N-এর GC শেষ — process ঠিক যেখানে থেমেছিল, ঠিক সেখান থেকে চলা শুরু
করল; নিজের memory-তে এখনও লেখা "আমি leader, lease valid"
t=31 N পুরনো belief নিয়ে write পাঠাল — resource-এ এখন **দুই writer**N কখনো crash করেনি — process বেঁচেই ছিল, শুধু অস্বাভাবিক ধীর। fail-stop idealization ধরে নেয় “মরা” আর “থেমে-থাকা”-র মধ্যে স্পষ্ট সীমারেখা আছে; বাস্তবে নেই। এই একটা ফাঁকই leader election আর distributed lock module-এর কেন্দ্রীয় সমস্যা — কীভাবে জেগে-ওঠা zombie-leader-কে নতুন leader লেখার আগে থামানো যায়, তার সমাধান (fencing token, epoch/term number) পরের লেসনগুলোর বিষয়। আজ শুধু নাম দিলাম প্রশ্নটার — কোনো local information-ই “মৃত” আর “ধীর”-কে নিশ্চিতভাবে আলাদা করতে পারে না; এটা কোনো implementation-এর দুর্বলতা না, asynchronous system-এর গাণিতিক সীমা।
ছবিতে
Ladder-টা ছবিতে — superset কীভাবে গড়ে ওঠে
Byzantine ⊇ timing + omission + crash-recovery + সরাসরি মিথ্যা/contradiction
│
Timing ⊇ omission + crash-recovery + arbitrarily ধীর/দ্রুত (কিন্তু সৎ)
│
Omission ⊇ crash-recovery + message (send/receive) হারানো
│
Crash-Recovery ⊇ crash-stop + restart (volatile state হারায়, persisted state থাকে)
│
Crash-Stop = থামে, চিরতরে চুপ — সবচেয়ে ভদ্র ভাঙা, সবচেয়ে সস্তা প্রমাণ Node-N (leader) বাকি cluster
│ │
lease হাতে, T=0 │
│ ░░░ full GC — stop-the-world ░░░ (heartbeat শূন্য)
│ │
│ timeout ফুরোলো → "N মৃত" ধরে নেয়
│ │── M নতুন leader, নতুন lease
│ │
GC শেষ, N জাগে │
(belief: "lease এখনও আমার!") │── M ইতিমধ্যে লিখছে
│ │
N পুরনো belief নিয়ে write পাঠায় ──────► দুই writer, একই resource — split-brain-এর বীজনিজে চালিয়ে দেখুন
Timeout-based failure detector — একটা threshold-এ কী ঘটে
তিনটা node — A সুস্থ (ছোট jitter), B জীবিত কিন্তু একবার লম্বা GC-pause
খায়, C সত্যিকারের crash — এদের heartbeat-arrival tick হার্ডকোড করা, কোনো
RNG নেই, তাই output ১০০% reproducible:
# failure_detector.py — fixed-threshold heartbeat failure detector (deterministic)
ARRIVALS = {
"A": [10, 21, 29, 41, 49, 61, 71, 79, 91, 101, 109], # সুস্থ, ছোট jitter (max gap 12)
"B": [10, 20, 30, 40, 75, 85, 95, 105], # GC-pause spike: gap 40->75 = 35
"C": [10, 20, 30, 40, 50], # সত্যিকারের crash — এরপর চিরনীরব
}
HORIZON = 110
def run(T, horizon=HORIZON):
last_hb = {n: 0 for n in ARRIVALS}
suspected = {n: False for n in ARRIVALS}
events = []
for t in range(1, horizon + 1):
for node, sched in ARRIVALS.items():
if t in sched:
last_hb[node] = t
if suspected[node]:
suspected[node] = False
events.append((t, node, "CLEAR", 0))
for node in ARRIVALS:
if not suspected[node] and t - last_hb[node] > T:
suspected[node] = True
events.append((t, node, "SUSPECT", t - last_hb[node]))
return events, suspected, last_hb
print("— T=20 হলে কী ঘটে —")
events, suspected, last_hb = run(20)
for tick, node, kind, elapsed in events:
if kind == "SUSPECT":
print(f"t={tick} {node} SUSPECT (elapsed={elapsed})")
else:
print(f"t={tick} {node} CLEAR (heartbeat ফিরল)")
print(f"— t={HORIZON} অবস্থা —")
for node in ARRIVALS:
print(f" {node}: suspected={suspected[node]} last_hb={last_hb[node]}")প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):
— T=20 হলে কী ঘটে —
t=61 B SUSPECT (elapsed=21)
t=71 C SUSPECT (elapsed=21)
t=75 B CLEAR (heartbeat ফিরল)
— t=110 অবস্থা —
A: suspected=False last_hb=109
B: suspected=False last_hb=105
C: suspected=True last_hb=50পড়ার মূল মুহূর্ত: t=61-এ B suspect হলো — অথচ B বেঁচেই ছিল, শুধু
GC-pause-এ ৩৫ tick চুপ ছিল। t=71-এ C suspect হলো — এটাই সত্যিকারের
crash, সঠিক রায়। t=75-এ B-র heartbeat ফিরতেই detector নিজের ভুল
শোধরাল — কিন্তু ততক্ষণে যদি B leader থাকত, তার জায়গায় নতুন election
হয়ে যেত। একই mechanism, একই T — একবার সঠিক, একবার ভুল। এটাই পরের
সেকশনের প্রশ্ন: T বদলালে কী বদলায়?
একই fixed-threshold detector একটা truly-dead node ঠিকই ধরে ফেলে, কিন্তু একটা GC-paused-but-alive node-কেও একই নিয়মে ভুলভাবে suspect করে — deterministic, hardcoded heartbeat-arrival দিয়ে, যাতে false-positive-টা কাকতালীয় না, কাঠামোগত প্রমাণিত হয়।
নিজে বানান
FailureDetector — threshold sweep, false-positive বনাম detection-latency
- ARRIVALS-এ প্রতিটা node-এর heartbeat-arrival tick হার্ডকোড করুন — deterministic, RNG নেই
- FailureDetector ক্লাস: প্রতি tick-এ arrival আপডেট করে, তারপর t - last_hb > T শর্তে suspect ঘোষণা করে
- প্রতিটা T-এর জন্য fresh detector object চালান — আগের T-এর state যেন leak না করে
- B (আসলে জীবিত, GC-pause) আর C (আসলে মৃত)-এর ঘটনা আলাদা করে false-positive আর detection-latency বার করুন
- শেষে latency == T+1 assert করে যাচাই করুন — trade-off-টা linear, বিনামূল্যে নয়
লক্ষ্য: একই heartbeat-data, চারটা ভিন্ন T-তে চালিয়ে দেখানো — threshold
ছোট করলে সত্যিকারের crash তাড়াতাড়ি ধরা পড়ে, কিন্তু জীবিত-শুধু-ধীর node-ও
তত সহজে ভুল-suspect হয়। এই দুই কলামের বিপরীত দিকে চলাই “trade-off”-এর
গাণিতিক প্রমাণ, শুধু দাবি না।
# failure_detector_sweep.py — FailureDetector ক্লাস + threshold sweep
ARRIVALS = {
"A": [10, 21, 29, 41, 49, 61, 71, 79, 91, 101, 109],
"B": [10, 20, 30, 40, 75, 85, 95, 105], # GC-pause: gap 40->75 = 35 tick
"C": [10, 20, 30, 40, 50], # প্রকৃত crash, t=50-এর পরে চিরনীরব
}
HORIZON = 110
class FailureDetector:
"""Fixed-threshold heartbeat detector — একটাই knob: T"""
def __init__(self, T):
self.T = T
self.last_hb = {n: 0 for n in ARRIVALS}
self.suspected = {n: False for n in ARRIVALS}
self.events = []
def tick(self, t):
for node, sched in ARRIVALS.items():
if t in sched:
self.last_hb[node] = t
if self.suspected[node]:
self.suspected[node] = False
self.events.append((t, node, "CLEAR", 0))
for node in ARRIVALS:
if not self.suspected[node] and t - self.last_hb[node] > self.T:
self.suspected[node] = True
self.events.append((t, node, "SUSPECT", t - self.last_hb[node]))
def run(self, horizon=HORIZON):
for t in range(1, horizon + 1):
self.tick(t)
return self.events
print("— threshold sweep: B (জীবিত, ধীর) বনাম C (মৃত) —")
for T in (15, 25, 35, 45):
fd = FailureDetector(T)
events = fd.run()
b_ev = [e for e in events if e[1] == "B"]
c_ev = [e for e in events if e[1] == "C"]
fp = any(e[2] == "SUSPECT" for e in b_ev)
dur = (next(e[0] for e in b_ev if e[2] == "CLEAR") -
next(e[0] for e in b_ev if e[2] == "SUSPECT")) if fp else 0
latency = next(e[0] for e in c_ev if e[2] == "SUSPECT") - 50
assert latency == T + 1, "C সবসময় ঠিক T+1 tick পরে ধরা পড়ে — detector-এর গাণিতিক সীমা"
print(f"T={T} B_false_positive={fp} fp_duration={dur} C_detect_latency={latency}")
print("OK: T >= 35 (আসল pause-length) হলেই false-positive শূন্য, "
"কিন্তু detect-latency তখনও T+1 — বিনামূল্যে নয়")প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):
— threshold sweep: B (জীবিত, ধীর) বনাম C (মৃত) —
T=15 B_false_positive=True fp_duration=19 C_detect_latency=16
T=25 B_false_positive=True fp_duration=9 C_detect_latency=26
T=35 B_false_positive=False fp_duration=0 C_detect_latency=36
T=45 B_false_positive=False fp_duration=0 C_detect_latency=46
OK: T >= 35 (আসল pause-length) হলেই false-positive শূন্য, কিন্তু detect-latency তখনও T+1 — বিনামূল্যে নয়সারণিটা পড়ার মূল কথা: T=15-এ crash ধরা পড়ে ১৬ tick-এ — চমৎকার দ্রুত —
কিন্তু B-র মতো জীবিত-ধীর node ১৯ tick ধরে ভুলভাবে “মৃত” তকমা বহন করে।
T=35-এ পৌঁছালে (ঠিক B-র pause-length-এর সমান) false-positive উধাও, কিন্তু
crash ধরতে এখন ৩৬ tick লাগছে। কোনো T-ই দুটোকেই একসাথে ছোট করতে পারে না —
এই একটা সারণিই ভবিষ্যতের leader-election আর distributed-locks লেসনের
“failure detector ভুল হলে দুই leader জন্মায়” বাক্যটার সংখ্যাগত ভিত্তি।
নিজে বাড়ান:
- দ্বিতীয় flaky node
Dযোগ করুন যার pause 50 tick —T=35কি তখনও নিরাপদ? - Phi-accrual failure detector (Cassandra/Akka-তে ব্যবহৃত) ধারণাটা দেখুন —
fixed
T-এর বদলে observed heartbeat-distribution থেকে suspicion-score বার করে; কেন সেটা latency না বাড়িয়ে false-positive কমাতে পারে - একই node বারবার pause করলে (repeated GC) — single-incident duration-এর বদলে প্রকৃত “rate” বার করুন (কত fraction heartbeat মিস হলো)
বাস্তব সিস্টেমে
বাস্তব জগতে failure model-এর পছন্দ
Raft — etcd, Consul, CockroachDB-এর co-ordination layer। Crash-recovery
model ধরে; n = 2f+1 — তিন নোডে একটা crash সহনশীল, পাঁচে দুইটা। Log entry
persist করা (write-ahead log) এই model-এরই দাবি: restart-এর পরে কী মনে
থাকবে সেটাই algorithm-এর ভিত্তি।
ZooKeeper (ZAB প্রোটোকল)। একই পরিবার — crash-recovery, majority quorum। Kubernetes-এর control plane নিজেই etcd-নির্ভর, তাই পুরো cluster-এর co-ordination-ই এই assumption-এর ওপর দাঁড়িয়ে — একটা compromised (Byzantine) node তাই শুধু availability না, correctness-ও ভাঙতে পারে, কারণ protocol সেই সম্ভাবনার জন্যই বানানো হয়নি।
PBFT আর blockchain consensus (Tendermint, বেশিরভাগ public chain)।
Byzantine model — n = 3f+1, প্রতিটা decision-এ multiple voting round আর
cryptographic signature। খরচ বেশি, কিন্তু permissionless নেটওয়ার্কে “সব
participant সৎ” ধরে নেওয়াই অসম্ভব — কেউ economic incentive-এ মিথ্যা বলতেই
পারে।
Phi-accrual failure detector — Cassandra, Akka। এই লেসনের experiment/build-এর
fixed-threshold detector-এর production-grade উত্তরসূরি: এক সংখ্যার threshold
না রেখে heartbeat-arrival-এর historical distribution থেকে প্রতিটা মুহূর্তে
একটা suspicion-score (φ) হিসেব করে — নেটওয়ার্কের সাধারণ jitter-প্যাটার্নের
সাথে adapt করে, তাই ঘুরে-ফিরে সেই একই ২০-বনাম-৩৫ ট্রেড-অফ, শুধু আরও মসৃণভাবে
handle করা।
Aviation — Boeing 777 primary flight computer। Distributed system শুধু web-backend-এ না — flight control computer-এ redundant channel-দের ভোট Byzantine-resilient ভাবে ডিজাইন করা, কারণ hardware fault শুধু crash না, sensor-corruption-এর মতো “ভুল কিন্তু সঙ্গতিপূর্ণ মিথ্যা”ও দিতে পারে।
যে ভুলগুলো সবাই করে
“Byzantine fault মানেই হ্যাকার বা malicious attacker।”
সবচেয়ে বেশি Byzantine আচরণ আসে দুর্ঘটনাক্রমে: bit-flip memory (cosmic ray!), buggy firmware, ভুল version-এর code deploy, disk corruption যা silent ভুল ডেটা ফেরত দেয়। Attacker একটা subset মাত্র। এই কারণেই end-to-end checksum আর signed log entry শুধু security-র জন্য না, reliability-র জন্যও — malicious intent ছাড়াও ভুল ছড়াতে পারে।
“Crash আর Byzantine fault tolerance-এ শুধু replica-সংখ্যার পার্থক্য, বাকি সব একই।”
সংখ্যাটা (2f+1 বনাম 3f+1) হিমশৈলের চূড়া মাত্র। Crash-tolerant protocol
ধরে নেয় উত্তর যা-ই আসুক, সেটা সৎ — শুধু “এলো কি এলো না” প্রশ্ন। Byzantine-
tolerant protocol-কে অতিরিক্ত কাজ করতে হয়: cryptographic signature (কে
কী বলল প্রমাণযোগ্য করতে), extra voting round (liar-দের বার্তার
সঙ্গতি-অসঙ্গতি ধরতে)। খরচ replica-সংখ্যার বাইরেও প্রতিটা message-এ চাপে।
“Timeout বাড়িয়ে দিলে failure detector প্রায় নিখুঁত হয়ে যায়।”
Build-এর সারণি দেখায় উল্টো: T বাড়ালে false-positive কমে ঠিকই, কিন্তু
detection-latency সমানুপাতে বাড়ে (latency = T+1, প্রতিটা row-এ)।
“নিখুঁত” মানে দুটোই শূন্য — সেটা কোনো T-তেই সম্ভব না যতক্ষণ পর্যন্ত সত্যিকারের
pause আর সত্যিকারের crash-এর মধ্যে কোনো observable পার্থক্য না থাকে। Timeout
কমায়, মেটায় না।
“একটা node 'একটু ধীর' মনে হলে algorithm তাকে আধা-বিশ্বাস করতে পারে — পুরো suspect না করেই।”
বেশিরভাগ practical failure-detector API-তে অবস্থা বাইনারি: suspected অথবা না। “৭০% সন্দেহজনক” মানের কোনো তৃতীয় স্তর সাধারণ consensus protocol-এ নেই — কারণ decision (leader election, lock grant) নিজেও বাইনারি। Phi-accrual স্কোর একটা continuous সংখ্যা দেয় ঠিকই, কিন্তু শেষ পর্যন্ত কোথাও-না-কোথাও একটা threshold পার হয়ে বাইনারি সিদ্ধান্তেই রূপান্তরিত হয় — অনিশ্চয়তা লুকায়, দূর করে না।
বুঝেছেন কি না দেখুন
1Adversary-strength ladder-এর পাঁচ ধাপের নাম ক্রমানুসারে বলুন, আর “superset”
দাবিটা এক বাক্যে ব্যাখ্যা করুন।
স্মরণ
Crash-stop → crash-recovery → omission → timing → Byzantine। Superset মানে: প্রতিটা উঁচু ধাপের node আগের ধাপের সব আচরণ করতে পারে, উপরন্তু নতুন একটা ক্ষমতা যোগ হয় — যেমন Byzantine node চাইলে শুধু crash-ও করতে পারে, আবার মিথ্যাও বলতে পারে।
2Crash fault tolerance-এ n = 2f+1 যথেষ্ট, কিন্তু Byzantine fault tolerance-এ
n = 3f+1 লাগে কেন — এক অনুচ্ছেদে যুক্তিটা দিন।
যুক্তি
n = 2f+1 যথেষ্ট, কিন্তু Byzantine fault tolerance-এ
n = 3f+1 লাগে কেন — এক অনুচ্ছেদে যুক্তিটা দিন।Crash node শুধু চুপ থাকে, মিথ্যা বলে না — তাই দুই quorum-এর মধ্যে ১টা common
সদস্যই যথেষ্ট, কারণ সে যা বলবে সত্যিই বলবে। Byzantine node সেই common
সদস্যও হতে পারে এবং মিথ্যা বলতে পারে, তাই overlap-এ অন্তত f+1 সদস্য চাই —
যাতে f-জন সম্ভাব্য liar বাদ দিয়েও অন্তত ১জন honest সাক্ষী থাকে। এই শর্ত
সমাধান করলেই n >= 3f+1 বেরিয়ে আসে।
3Build-এর সারণিতে T=25-এ এখনও B false-positive পাচ্ছে, কিন্তু T=35-এ
পাচ্ছে না — সংখ্যা দুটো (25 বনাম 35) আর B-র প্রকৃত pause-length-এর সম্পর্ক
কী?
প্রয়োগ
T=25-এ এখনও B false-positive পাচ্ছে, কিন্তু T=35-এ
পাচ্ছে না — সংখ্যা দুটো (25 বনাম 35) আর B-র প্রকৃত pause-length-এর সম্পর্ক
কী?B-র প্রকৃত heartbeat-gap ছিল ৩৫ tick (GC-pause)। যেকোনো T < 35 এই gap-কে
“অস্বাভাবিক” হিসেবে ধরে ফেলবে, তাই suspect হয় — মিথ্যা অ্যালার্ম, কারণ B
আসলে জীবিত। T >= 35 হলেই detector-এর ধৈর্য প্রকৃত pause-লেভেল ছাড়িয়ে যায়,
তাই আর ট্রিগার হয় না। Threshold-কে longest-expected-pause-এর সমান বা বেশি
রাখাই এই নির্দিষ্ট ভুলটা এড়ানোর একমাত্র উপায় — কিন্তু detection-latency-র
দামে।
4Fail-stop idealization টেক্সটবই-এ এত ব্যবহৃত হওয়া সত্ত্বেও কেন “সুবিধাজনক
মিথ্যা” — আর zombie-leader দৃশ্যে ঠিক কোন মুহূর্তে সেই মিথ্যাটা ধরা পড়ে?
যুক্তি
সুবিধা: “node মরলে বাকিরা তাৎক্ষণিক ও নির্ভুলভাবে জানে” ধরে নিলে proof-এ আর
suspicion-এর অনিশ্চয়তা সামলাতে হয় না — tractability। ধরা পড়ে ঠিক সেই মুহূর্তে
যখন N GC-pause থেকে জেগে ওঠে (t=30) আর দেখে সে “মৃত ঘোষিত” হয়নি, শুধু
বাকিরা তাকে temporarily unreachable ধরেছিল — সে আসলে কখনো crash করেইনি,
তাই তার নিজের স্মৃতিতে এখনও পুরনো leadership belief জীবিত, আর সে সেই belief
নিয়ে লিখতে যায়।
5নতুন payment-settlement system বানাচ্ছেন — participant-রা আলাদা আলাদা
ব্যাংকের সার্ভার, একে অপরকে সম্পূর্ণ বিশ্বাস করে না। f=2 পর্যন্ত সহ্য করতে
হবে। কোন failure model বাছবেন, আর replica-সংখ্যার দাম কত?
ডিজাইন
f=2 পর্যন্ত সহ্য করতে
হবে। কোন failure model বাছবেন, আর replica-সংখ্যার দাম কত?Multi-organization, no-shared-trust প্রেক্ষাপটে crash-recovery যথেষ্ট না —
কোনো ব্যাংকের সার্ভার bug বা compromise-এ ভুল/বিরোধপূর্ণ তথ্য পাঠাতে পারে,
সেটা Byzantine পরিসর। তাই Byzantine model বাছতে হবে: n = 3f+1 = 7
participant লাগবে (crash model হলে লাগত মাত্র 2f+1 = 5) — extra ২টা নোড
আর প্রতিটা settlement-এ signature/extra-round-এর খরচ, বিনিময়ে কোনো একক
organization-এর ভুল বা বিশ্বাসঘাতকতায় পুরো ledger না ভাঙার নিশ্চয়তা।
6Leader-election লেসনের forward-reference অনুযায়ী “failure detector ভুল হলে
দুই leader জন্মায়”। আজকের লেসনের কোন ফলাফল দেখায় যে এটা কোনো buggy
detector-এর সমস্যা না, বরং কাঠামোগত?
যুক্তি
Experiment/Build-এর সারণি দেখায়: যেকোনো T-তে false-positive সম্ভব —
T ছোট হলে বেশি, বড় হলে কম কিন্তু শূন্য কখনোই না যদি না pause-length-এর
উচ্চসীমা আগে থেকে নিশ্চিতভাবে জানা থাকে (বাস্তবে থাকে না)। তাই কোনো “ভালো”
threshold detector-কে perfect বানায় না — false suspicion মাঝেমধ্যে ঘটবেই,
আর সেটা ঘটলে election ট্রিগার হবে, পুরনো leader জীবিত থাকা সত্ত্বেও। সমাধান
তাই detector-স্তরে না — algorithm-স্তরে (fencing token, epoch/term
number) খুঁজতে হয়।
এরপর কী
এরপর কী
আজ “ভাঙা”-র নামকরণ হলো — crash থেকে Byzantine, আর দেখলাম কোনো local information-ই “মৃত” আর “শুধু ধীর”-কে নিশ্চিতভাবে আলাদা করতে পারে না।
এখন পর্যন্ত পুরো আলোচনায় ধরে নিয়েছি: কোনো shared clock নেই, node-রা শুধু message আর timeout দিয়েই একে অপরকে বিচার করে। স্বাভাবিক পরের প্রশ্ন — তাহলে যদি সব node-এর ঘড়ি সিঙ্ক করে দিই? wall-clock time দিয়ে event-ক্রম সাজালে সমস্যাটা মিটে যায় না? পরের লেসনে ঠিক এই আশাটা পরীক্ষা করব — physical clock, NTP, আর clock skew। উত্তর আগেভাগে বলে রাখি: physical clock এই সমস্যা বাঁচাতে পারে না, শুধু নতুন রূপে ফিরিয়ে আনে — কেন, সেটাই পরের লেসনের বিষয়।
আরও পড়ুন
- The Byzantine Generals Problem — Leslie Lamport, Robert Shostak, Marshall Pease — ACM TOPLAS 4(3), 1982 · 3f+1 replica-বাউন্ডের জন্মপত্র — n ≥ 3f+1 না হলে loyal generals কখনো consensus-এ পৌঁছাতে পারে না, প্রমাণসহ
- Unreliable Failure Detectors for Reliable Distributed Systems — Tushar Deepak Chandra, Sam Toueg — Journal of the ACM 43(2), 1996 · Timeout-based সন্দেহকে আনুষ্ঠানিক failure-detector abstraction বানানোর ভিত্তি — completeness/accuracy trade-off-এর ভাষা
- Practical Byzantine Fault Tolerance — Miguel Castro, Barbara Liskov — OSDI 1999 · PBFT — production-গ্রেড Byzantine consensus, extra voting round আর signature-এর খরচ হাতে-কলমে দেখায়
- Distributed Systems: Principles and Paradigms, Chapter 8 — Fault Tolerance — Andrew S. Tanenbaum, Maarten van Steen · Failure-model classification-এর পাঠ্যপুস্তক treatment — আগের লেসনের একই উৎস, এবার model-taxonomy অংশ