Foundationপ্রথম নীতি থেকে
LEVEL 9লেসন ২/১৯কঠিন৫০ মিনিট

Failure Models — Crash থেকে Byzantine পর্যন্ত ভাঙার সোপান

Failure Models: From Crash to Byzantine

Partial failure কতভাবে ঘটতে পারে তার নামকরণ — crash-stop থেকে omission, timing, আর সবচেয়ে কঠিন Byzantine পর্যন্ত adversary-strength ladder — আর দেখানো কেন সঠিক model বাছাই replica-সংখ্যা থেকে algorithm-design পর্যন্ত সবকিছু বদলে দেয়।

এই লেসন শেষে আপনি পারবেন

  • Adversary-strength ladder-এর পাঁচ ধাপ — crash-stop, crash-recovery, omission, timing, Byzantine — প্রতিটার সংজ্ঞা ও বাস্তব একটা করে বাসিন্দা-উদাহরণ দিয়ে ব্যাখ্যা করতে পারবেন, আর দেখাতে পারবেন প্রতিটা ধাপ ঠিক আগেরটার strict superset
  • Crash fault tolerance-এর n = 2f+1 আর Byzantine fault tolerance-এর n = 3f+1 replica-হিসাব করে, বাড়তি f-এর প্রয়োজন কেন — honest minority আর lying majority আলাদা করা যায় না বলে — সেই যুক্তি নিজের ভাষায় দিতে পারবেন
  • Raft/etcd-জাতীয় consensus system বনাম PBFT/blockchain-জাতীয় system কোন failure model assume করে তা চিনে, model-বাছাই নতুন system-design-সিদ্ধান্তে যুক্ত করতে পারবেন
  • Fail-stop idealization-কে algorithm-design-এর সুবিধাজনক মিথ্যা হিসেবে সংজ্ঞায়িত করে, GC-pause বা scheduler-starvation-এর মতো বাস্তব দৃশ্যে সেই ধারণা কীভাবে ভাঙে তা concrete scenario দিয়ে ব্যাখ্যা করতে পারবেন
  • নিজে timeout-based failure detector বানিয়ে threshold sweep করে false-positive আর detection-latency-র মধ্যেকার trade-off সংখ্যায় মেপে দেখাতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনে দেখলাম 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-stopProcess যেকোনো মুহূর্তে থামে, চিরতরে চুপ — আর ফেরে নাOOM-killer একটা process মেরে দিল; সেই PID কখনো আর জেগে ওঠে না
২Crash-recoveryProcess থামতে পারে, আবার restart-ও হতে পারে; volatile state হারায়, persisted state থাকেRaft node পাওয়ার-সাইকেলের পরে ফেরে, disk-এ লেখা currentTerm পড়ে আবার যোগ দেয়
৩OmissionProcess নিজে ঠিকই চলে, কিন্তু message হারায় বা পাঠাতে/গ্রহণ করতে ব্যর্থ হয়Switch buffer-overflow-এ একটা packet ফেলে দিল — দুই প্রান্তের কেউ crash করেনি
৪TimingProcess/message crash-ও করে না, message-ও হারায় না — কিন্তু expected সময়সীমার তুলনায় যথেচ্ছ ধীর বা দ্রুত হতে পারে, উত্তর সৎই থাকেGC pause-এ node ৩০ সেকেন্ড কিছু বলল না, তারপর ঠিক উত্তরই দিল — শুধু দেরিতে
৫ByzantineProcess ইচ্ছেমতো যেকোনো আচরণ করতে পারে — 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 সৎ থাকবে” ধরে নেওয়াই যায় না।

SystemFailure modelf=1-এ replica-সংখ্যা
Raft (etcd, Consul)Crash-recovery3 (১টা crash সহনশীল)
ZooKeeper (ZAB)Crash-recovery3
PBFTByzantine4 (১টা arbitrary/malicious সহনশীল)
Tendermint / বেশিরভাগ blockchain consensusByzantine4, 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 = 3f+1 কোথা থেকে আসে — চার ধাপে
  1. দাবিn নোডের মধ্যে f-টা Byzantine (মিথ্যাবাদী) হলেও সবাই এক সিদ্ধান্তে পৌঁছাবে
  2. quorum size QQ = n - f — কারণ f-টা নোড সাড়াই নাও দিতে পারে, quorum তাদের ছাড়াই গঠনযোগ্য হতে হবে
  3. দুই quorum-এর overlappigeonhole-এ ন্যূনতম overlap = 2Q - n
  4. overlap-এ honest witness লাগবেf-টা liar overlap-এর ভেতরেও থাকতে পারে, তাই safe থাকতে overlap >= f+1 চাই
  5. সমাধান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+1Byzantine — n = 3f+1
134
257
3710

লক্ষ করুন 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-এর বীজ
Zombie-leader anatomy — GC pause-এ N বাইরে থেকে 'মৃত' দেখাল, ভেতরে বেঁচেই ছিল; জেগে উঠে পুরনো belief-এ লেখা পাঠাল।

নিজে চালিয়ে দেখুন

EXPERIMENT

Timeout-based failure detector — একটা threshold-এ কী ঘটে

macOS / Linux / Windows (Python 3)· ১৫ মিনিট

তিনটা 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-টা কাকতালীয় না, কাঠামোগত প্রমাণিত হয়।

নিজে বানান

BUILD IT

FailureDetector — threshold sweep, false-positive বনাম detection-latency

Python · ●●●○○
  1. ARRIVALS-এ প্রতিটা node-এর heartbeat-arrival tick হার্ডকোড করুন — deterministic, RNG নেই
  2. FailureDetector ক্লাস: প্রতি tick-এ arrival আপডেট করে, তারপর t - last_hb > T শর্তে suspect ঘোষণা করে
  3. প্রতিটা T-এর জন্য fresh detector object চালান — আগের T-এর state যেন leak না করে
  4. B (আসলে জীবিত, GC-pause) আর C (আসলে মৃত)-এর ঘটনা আলাদা করে false-positive আর detection-latency বার করুন
  5. শেষে 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 জন্মায়” বাক্যটার সংখ্যাগত ভিত্তি।

নিজে বাড়ান:

  1. দ্বিতীয় flaky node D যোগ করুন যার pause 50 tick — T=35 কি তখনও নিরাপদ?
  2. Phi-accrual failure detector (Cassandra/Akka-তে ব্যবহৃত) ধারণাটা দেখুন — fixed T-এর বদলে observed heartbeat-distribution থেকে suspicion-score বার করে; কেন সেটা latency না বাড়িয়ে false-positive কমাতে পারে
  3. একই 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 পার হয়ে বাইনারি সিদ্ধান্তেই রূপান্তরিত হয় — অনিশ্চয়তা লুকায়, দূর করে না।

বুঝেছেন কি না দেখুন

1

Adversary-strength ladder-এর পাঁচ ধাপের নাম ক্রমানুসারে বলুন, আর “superset” দাবিটা এক বাক্যে ব্যাখ্যা করুন।

স্মরণ

Crash-stop → crash-recovery → omission → timing → Byzantine। Superset মানে: প্রতিটা উঁচু ধাপের node আগের ধাপের সব আচরণ করতে পারে, উপরন্তু নতুন একটা ক্ষমতা যোগ হয় — যেমন Byzantine node চাইলে শুধু crash-ও করতে পারে, আবার মিথ্যাও বলতে পারে।

2

Crash fault tolerance-এ 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 বেরিয়ে আসে।

3

Build-এর সারণিতে 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-র দামে।

4

Fail-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-সংখ্যার দাম কত?

ডিজাইন

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 না ভাঙার নিশ্চয়তা।

6

Leader-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 অংশ