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

Leader Election — একজনকে দায়িত্ব দেওয়ার protocol

Leader Election

Replication-এর চুক্তি একজন leader-এর হাতে থাকে; election protocol (bully, ring) সেই leader বাছে — কিন্তু failure detector ভুল হলে দুই leader জন্মায়, তাই epoch/term fencing number আর majority gate লাগে।

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

  • Primary-based replication-এ কেন একজন leader designate করা হয় — ordering point আর single-writer সরলতা — তা failure-models-এর ভাষায় ব্যাখ্যা করে বলতে পারবেন কোন কাজ leader ছাড়া অসম্ভব, কোন কাজ শুধু কঠিন
  • Bully algorithm আর ring election step-by-step trace করে message count আর convergence latency হিসাব করতে পারবেন, আর bully-র churn pathology কখন জন্মায় তা timeline-এ ধরতে পারবেন
  • Failure detector ভুল বললে election কীভাবে dueling leaders (split brain) বানায় তা partition-timeline-এ trace করে, epoch/term-কে fencing generation number হিসেবে ব্যাখ্যা করতে পারবেন
  • Liveness আর safety-র টানাপোড়েন partition-চলাকালীন কোথায় ভাঙে কোথায় টিকে — majority gate কোনটা কিনে দেয় কোনটা বিসর্জন দিতে হয় — সেটা সিদ্ধান্ত হিসেবে নিতে পারবেন
  • ZooKeeper (ZAB), etcd (Raft), Kafka (KRaft migration) — বাস্তব system-গুলো election কীভাবে চালায় তুলনা করে কোন design কোন workload-এ মানানসই বলতে পারবেন

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

আগে এটা বুঝি

Quorum লেসন শেষে আমরা একটা খোলা প্রশ্ন রেখেছিলাম: primary-backup পরিবারের সব promise primary জীবিত থাকার ওপর দাঁড়িয়ে। primary মারা গেলে — crash, network-isolation, datacenter হারিয়ে — কে দায়িত্ব নেবে? আর সবচেয়ে কঠিন: কে decide করবে যে পুরনো primary আসলেই মরে গেছে?

উত্তরটা শুনতে সরল: বাকিরা ভোট দিয়ে নতুন primary বাছবে। এই যন্ত্রটার নাম leader election। কিন্তু ওই সরল বাক্যের ভেতরে তিনটা ফাঁদ লুকানো:

ফাঁদ ১ : দুইজন একসাথে candidate হলে?          (split vote)
ফাঁদ ২ : ভোট চাইতে গিয়ে দেখা গেল নিজেই isolated?  (quorum-of-eligibility)
ফাঁদ ৩ : নতুন leader এল, কিন্তু পুরনোও নিজেকে leader ভাবছে!  (split brain)

Interview-তে সবাই বলে “leader down hole হলে auto-failover হব” — আজ আমরা দেখব failover-টা ঠিক কোন মুহূর্তে ভুল সিদ্ধান্ত নেয়, আর বাস্তব system-গুলো (ZooKeeper, etcd, Kafka) সেই মুহূর্তকে কীভাবে fencing করে।

মূল ধারণা

Leader কেন দরকার — দুইটা কংক্রিট সুবিধা

Quorum লেসনের LWW-limit মনে করুন: leaderless write-এ concurrent write-এর total order হারিয়ে যেত। একজন ordering point থাকলে সমস্যাটা জন্মায়ই না — সব write একটাই log-এ সারি বাঁধে। দ্বিতীয় সুবিধা single writer: streaming-replication লেসনের primary ঠিক এই কাজটাই করত — WAL-এ append করে standby-দের পাঠায়। N জন writer হলে conflict-resolution লাগে; ১ জন হলে replication মানেই order-preserving forwarding।

দায়িত্বleader ছাড়াleader-সহ
Write orderingLWW/vector-clock mergelog position-ই order
Conflict resolutionapplication-দায়িত্বনেই-ই
Read freshnessR-replica probeleader-এ linearizable
Failure responseপ্রতিটা node নিজে ভাবেএকটা election

দামটাও স্পষ্ট: leader এখন single point of failure। Election হলো সেই SPOF-কে repairable বানানোর যন্ত্র।

Bully algorithm — সবচেয়ে বড় ভোটে জেতে

Garcia-Molina-র 1982-র classic। প্রতিটা node-এর একটা id; সর্বোচ্চ id-র জীবিত node-ই leader — invariant এইটুকুই।

node3 বুঝল node4 (leader) সাড়া দিচ্ছে না:
  1. node3 -> সবাইকে ELECTION(id=3) পাঠায়
  2. id বেশি যারা জীবিত (node4? না; node5 হ্যাঁ) -> উত্তরে নিজের বড় id
     -> node3 হার মানে, অপেক্ষা
  3. node5 নিজে ELECTION(id=5) চালায় -> কেউ বড় id ফেরত দেয় না
  4. node5 সবাইকে COORDINATOR(node5) ঘোষণা

সরল, deterministic, আর message-count worst case-এ O(N²) — প্রতিটা candidate-ই সবাইকে জিজ্ঞেস করে।

Ring election — একটা token, একটা দিক

Node-গুলো logical ring-এ সাজানো। Candidate একটা ELECTION message ঘুরিয়ে দেয়; প্রতিটা node নিজের id max-এর সাথে merge করে forward করে; message candidate-এর কাছে ফিরে এলে winner জানা যায়, তারপর COORDINATOR message আবার ঘোরে।

ring 0->1->2->3->4, node4 মৃত:
  t1  n0->n1 ELECTION(max=0); n1->n2 ELECTION(max=1);
      n2->n3 ELECTION(max=2); n3->n0 ELECTION(max=3)   [circle done]
  t2  COORDINATOR(leader=3) আবার ঘুরে আসে

Message count মাত্র 2(N−1) — কিন্তু latency hop-count-এ বাড়ে, আর ring-এর মাঝে একটা dead link থাকলে bypass ব্যবস্থা লাগে।

Failure detector — পুরো খেলার আসল জুয়া

Election শুরু হয় একটা অভিযোগ দিয়ে: “leader কেমন আছে, উত্তর নেই।” এই সিদ্ধান্তটা নেয় failure detector — আসলে কিছুই না, একটা timeout timer। আর timer দুই দিকেই ভুল করতে পারে:

GC pause / network jitter / CPU starvation
  -> জীবিত leader উত্তর দিতে দেরি -> detector বলে DEAD (false positive!)
partition
  -> মৃত leader-এর কাছ থেকে কিছু node দূরে -> detector বলে ALIVE (false negative)

failure-models লেসনের চোখে এটা inevitable: asynchronous system-এ crash-আর-slow-এর পার্থক্য নিখুঁতভাবে বলা অসম্ভব। তাই election protocol-গুলোর আসল প্রশ্ন algorithm নয় — ভুল detector-এর ওপরেও safety টেকে কি না।

Dueling leaders — যখন দুই দিকই election জেতে

Partition ধরুন: {A,B} | {C,D,E}, leader ছিল C।

  • A-র timer চলে গেল → A-side election → ছোট side-ও একজনকে leader বানাতে পারে (নিজেদের মধ্যে ভোট!) → dueling leaders;
  • পুরনো leader C নিজের side-এ জীবিত, নিজেকে leader-ই মনে করছে;

দুই leader-ই client-এর write নিলে state দুই ভাগে চিরতরে diverge — এটাই split brain, quorum লেসনের fencing-অধ্যায়ের সেই ভূত।

প্রতিষেধক দুইটা, দুই স্তরে:

  1. Majority gate: leader হতে cluster-এর strict majority (N div 2 + 1) vote লাগবে। দুই side কখনোই একই majority পাবে না — pigeonhole!
  2. Epoch/term fencing: প্রতিটা election-এ এক-ঘর বাড়া generation number — ZooKeeper-এর ভাষায় epoch, Raft/MongoDB-র ভাষায় term। পুরনো epoch-এর প্রমাণ (lease, write-ack) নতুন leader reject করে। fencing token এই number-কেই storage-পর্যন্ত নিয়ে যায়।

lease-ভিত্তিক leadership-এও একই কৌশল: lease grant-এর সাথে epoch জুড়ে দিলে পুরনো lease-holder-এর authority নতুন epoch-এর সামনে কাগজ।

Liveness বনাম safety — partition-এর দরদি বাণিজ্য

Majority gate-এর দামটাও মানতে হবে:

safety  : কখনো একাধিক effective leader নেই        (gate কিনে দেয়)
liveness: minority side-এ কিছুই চলে না            (gate খেয়ে ফেলে)

CAP-এর ভাষায়: consensus-family CP — partition-এ consistency রক্ষা করে availability বিসর্জন দেয়। কোন election-ই এই বাণিজ্য ভাঙতে পারে না; ভাঙলে split-brain ফিরে আসে।

ভেতরে কী ঘটছে

Timeout window-এর গণিত

Leader-dead detection latency = প্রথম follower-এর fire-time। Window [lo, hi] uniform হলে expected min ≈ lo + (hi−lo)/(k+1) (k জন follower)। কিন্তু window সরু হলে collision: দুইজন একই tick-এ fire করলে split vote, সবাই retry — fixed [15,15] window-এ তো collision প্রতি রাউন্ডেই, livelock!

Randomized window-এ collision probability প্রতি রাউন্ডে ≈ 1/(hi−lo+1) — এজন্যই Raft timeout-গুলো randomize করে। Trade-off:

window চওড়া  : collision rare, কিন্তু dead-leader detection ধীর
window সরু    : detection দ্রুত, কিন্তু false-positive বেশি -> churn

Bully-র churn pathology

Bully-র লুকানো বিষ: highest-id node-টা unstable হলে। node5 বারবার unreachable→reachable হলে:

node5 down  -> election -> node3/4 জেতে
node5 back  -> নিজেকে ELECTION(id=5) জানায় -> re-election
node5 down  -> ...                      # প্রতি flap-এ O(N^2) message

পুরো cluster বারবার leadership বদলায় — churn। Real system-এ এজন্যই stable-leader preference (incumbent advantage) রাখা হয়: চলমান leader যতদূর সম্ভব টিকে থাকে।

Election state machine — এক নজরে

election lifecycle — detector থেকে fencing পর্যন্ত
  1. suspectheartbeat timeout পেরলে follower নিজে candidate হয়
  2. canvassvote-request ছড়ায়; split হলে randomized backoff-এ retry
  3. majority gateN div 2 + 1 distinct vote ছাড়া ঘোষণা নেই
  4. announceCOORDINATOR/AppendEntries-এ epoch সহ ঘোষণা
  5. fencestorage/lock layer পুরনো epoch-এর লেখা reject করে

Message complexity-র তুলনা

AlgorithmMessages (best)Messages (worst)WinnerPathology
BullyN−1O(N²)max idflapping-max churn
Ring2(N−1)2(N−1)max iddead-link bypass
Randomized-timeout + majorityO(N) per attemptattempts × O(N)any eligiblesplit-vote rounds

উদাহরণ

Full scenario — N=5, bully, তারপর partition

cluster: node0..node4, leader=node4, heartbeat every 2 ticks

t10  node4 GC-pause 300ms -> node2-র timeout fire
t11  node2 broadcasts ELECTION(2)
t12  node3 replies BIGGER-ID(3); node0,node1 also defer
t13  node3 wins its own round -> COORDINATOR(node3), epoch=2
t14  node4 wakes up, still thinks leader (stale epoch=1!)
t15  node4 accepts a client write            <-- GHOST WRITE attempt
t16  storage rejects: epoch 1 \< epoch 2      <-- fencing saves us
t20  partition: {node0,node1} | {node2,node3,node4}
t21  node0 timeout -> local election -> node1 declared on minority
t22  node3 side: already has leader node3 (epoch 2)  = DUAL LEADERS?
t23  node1 tries commit -> needs 3 votes, has 2 -> BLOCKED forever
t24  net effect: only majority side serves; safety intact

লক্ষ্য করুন তিনটা স্তর: t13-এ election শেষ, t15–16-এ fencing পুরনো leader-কে নিঃশব্দে neutralize করল, t21–23-এ majority gate minority- কে অজু করে দিল। Election, fencing, gate — তিনটাই লাগে; কোনো একটা বাদ দিলে গল্পটা ভাঙে।

ছবিতে

  node0   node1   node2        node3        node4(dead)
    |       |       |--ELECTION->|             X
    |       |       |\<--BIGGER--|             X
    |       |       |  (node3 takes over the canvass)
    |       |       |\<-COORDINATOR(node3, epoch=2)--|
    |       |       |             |
              all nodes record leader=node3, epoch=2
Bully election — suspect থেকে coordinator ঘোষণা পর্যন্ত; বড় id সবসময় উল্টে দেয়।
        partition {n0,n1}  |  {n2,n3,n4}
       ---------------------+---------------------
        n1: votes=2         |  n3: votes=3
        need  3  -> BLOCKED |  need 3 -> LEADER
        writes queued       |  writes served
                            |
        pigeons: 5 votes total, two sides cannot BOTH hold >= 3
Partition-এ majority gate — দুই side-ই election চালাতে পারে, কিন্তু একটাই 3-vote পায়।

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

EXPERIMENT

Leader-death re-election timing + partition-এ dual-leader নাটক

macOS / Linux / Windows (Python 3)· ১৫ মিনিট
# election_probe.py — leader-death re-election timing + partition dual-leader demo
import random


def reelection(n=5, lo=10, hi=40, seed=7, death_at=50, horizon=300):
    """Leader dies; followers fire timeouts uniform in [lo,hi];
    unique earliest fires wins — collision means retry."""
    rng = random.Random(seed)
    alive = [True] * n
    splits = attempts = 0
    latency = None
    t = 0
    while t < horizon:
        t += 1
        if t == death_at:
            alive[n - 1] = False                     # leader dies
        if t <= death_at:
            continue
        fires = [rng.randint(lo, hi) for i in range(n) if alive[i]]
        first = min(fires)
        if fires.count(first) > 1:                   # same-tick collision
            splits += 1
            attempts += 1
            continue                                 # everyone retries
        attempts += 1
        latency = first                              # ticks after death
        break
    return latency, splits, attempts


print("config          leader-by      split-rounds")
for label, lo, hi in (("fixed [15,15]", 15, 15),
                      ("jitter [12,18]", 12, 18),
                      ("wide   [10,40]", 10, 40)):
    lat, sp, att = reelection(lo=lo, hi=hi)
    if lat is None:
        print(f"{label:<14}  NO LEADER by tick 300   {sp}+ splits")
    else:
        print(f"{label:<14}  +{lat:<3} ticks     {sp}/{att}")


def partition_run(majority_gate):
    sides = [{0, 1}, {2, 3, 4}]                      # cut between node1-node2
    result = []
    for side in sides:
        cand = max(side)                             # bully-style pick
        votes = 1                                    # self-vote
        for v in side - {cand}:
            votes += 1                               # VOTE-YES arrives
        ok = votes * 2 > 5 if majority_gate else votes >= 2
        result.append((sorted(side), cand if ok else None))
    return result


for gate in (False, True):
    res = partition_run(gate)
    tag = "majority-gate ON " if gate else "majority-gate OFF"
    parts = "; ".join(
        f"side{side}: {('leader=node%d' % l) if l is not None else 'BLOCKED'}"
        for side, l in res)
    dual = sum(1 for _, l in res if l is not None) > 1
    print(f"{tag}: {parts}  -> {'DUAL-LEADER!' if dual else 'single leader'}")

প্রত্যাশিত output (seed-fixed):

config          leader-by      split-rounds
fixed [15,15]   NO LEADER by tick 300   250+ splits
jitter [12,18]  +13  ticks     0/1
wide   [10,40]  +14  ticks     0/1
majority-gate OFF: side[0, 1]: leader=node1; side[2, 3, 4]: leader=node4  -> DUAL-LEADER!
majority-gate ON : side[0, 1]: BLOCKED; side[2, 3, 4]: leader=node4  -> single leader

পড়ার সময়: প্রথম row-টা হলেন villain — identical timeout মানেই প্রতি রাউন্ডে five-way collision, চিরতরে leaderless। শেষ দুই line-এ gate-এর বাণিজ্য: OFF-এ দুই leader, ON-এ এক leader + এক disabled side।

এটা কী প্রমাণ করে

Fixed timeout window-এ split vote livelock বানায় (কোনো leader-ই নির্বাচিত হয় না), randomized window-এ সাথে সাথে leader আসে; partition-এ majority gate বন্ধ করলে দুই side-ই leader বানায় (DUAL-LEADER!), gate চালু করলে minority block হয় — liveness-vs-safety trade-off live।

নিজে বানান

BUILD IT

ElectionRing — ring election + majority variant, hop-by-hop ASCII timeline

Python · ●●●○○
  1. N-node logical ring বানান; next_alive() dead node skip করে
  2. ELECTION token hop-by-hop ঘোরে, প্রতিটা node নিজের id max-merge করে
  3. Token origin-এ ফিরলে COORDINATOR message দ্বিতীয় circle ঘোরে
  4. প্রতিটা message (tick, from, to, msg) timeline-এ print করুন
  5. Variant: unicast VOTE-REQUEST/VOTE-YES + strict-majority gate, message-count তুলনা করুন

লক্ষ্য: দুই protocol-এর message anatomy চোখের সামনে trace করা — কে কাকে কী পাঠাল, কত message খরচ হলো, winner কে।

# election_ring.py — ElectionRing: ring election vs majority-vote, hop-by-hop trace
N = 5
LEADER = N - 1                      # node 4 starts as leader, then dies


class ElectionRing:
    def __init__(self, mode="ring"):
        self.mode = mode
        self.alive = [True] * N
        self.log = []               # (tick, from, to, msg)
        self.tick = 0
        self.msgs = 0

    def next_alive(self, i):
        j = (i + 1) % N
        while not self.alive[j]:
            j = (j + 1) % N
        return j

    def send(self, frm, to, msg):
        self.msgs += 1
        self.log.append((self.tick, frm, to, msg))

    def kill(self, i):
        self.alive[i] = False

    # ---- ring protocol: ELECTION token carries highest id seen ----
    def elect_ring(self):
        origin = self.next_alive(LEADER)
        hi, cur = origin, origin
        while True:                                  # forward hop-by-hop
            nxt = self.next_alive(cur)
            if cur != origin:
                hi = max(hi, cur)
            self.send(cur, nxt, f"ELECTION(max={hi})")
            if nxt == origin:                        # token completed circle
                break
            cur = nxt
        cur = origin                                 # announce phase
        while True:
            nxt = self.next_alive(cur)
            self.send(cur, nxt, f"COORDINATOR(leader={hi})")
            if nxt == origin:
                break
            cur = nxt
        return hi

    # ---- majority variant: unicast votes, declare only with > N/2 ----
    def elect_majority(self):
        origin = self.next_alive(LEADER)
        votes = 1                                    # own vote
        for v in range(N):
            if v != origin and self.alive[v]:
                self.send(origin, v, "VOTE-REQUEST")
                votes += 1
                self.send(v, origin, f"VOTE-YES(for={origin})")
        if votes > N // 2:
            for v in range(N):
                if v != origin and self.alive[v]:
                    self.send(origin, v, f"COORDINATOR(leader={origin})")
            return origin
        return None

    def run(self):
        self.kill(LEADER)
        self.tick = 1
        return (self.elect_ring() if self.mode == "ring"
                else self.elect_majority())


for mode in ("ring", "majority"):
    r = ElectionRing(mode)
    w = r.run()
    print(f"=== mode={mode}: leader=node{w}, messages={r.msgs}")
    for t, f, to, m in r.log[:6]:
        print(f"  t{t:>2}  node{f} -> node{to}  {m}")
    if len(r.log) > 6:
        print(f"  ... (+{len(r.log) - 6} more)")

প্রত্যাশিত output (deterministic):

=== mode=ring: leader=node3, messages=8
  t 1  node0 -> node1  ELECTION(max=0)
  t 1  node1 -> node2  ELECTION(max=1)
  t 1  node2 -> node3  ELECTION(max=2)
  t 1  node3 -> node0  ELECTION(max=3)
  t 1  node0 -> node1  COORDINATOR(leader=3)
  t 1  node1 -> node2  COORDINATOR(leader=3)
  ... (+2 more)
=== mode=majority: leader=node0, messages=9
  t 1  node0 -> node1  VOTE-REQUEST
  t 1  node1 -> node0  VOTE-YES(for=0)
  t 1  node0 -> node2  VOTE-REQUEST
  t 1  node2 -> node0  VOTE-YES(for=0)
  t 1  node0 -> node3  VOTE-REQUEST
  t 1  node3 -> node0  VOTE-YES(for=0)
  ... (+3 more)

পড়ার সময়: ring-mode winner node3 (max-id invariant), majority-mode winner node0 (initiator) — একই cluster, ভিন্ন চুক্তি। Message-count প্রায় সমান (৮ বনাম ৯), কিন্তু majority-mode-র প্রতিটা vote আলাদা round-trip — WAN-এ ring-এর sequential hops আরও ব্যথা দেয়।

নিজে বাড়ান: node4-কে flapping করান (kill→revive লুপ) আর দেখুন bully-invariant কতবার re-election ট্রিগার করে; majority-mode-এ candidate নিজের বদলে lowest-id পেলে কী হয়?

বাস্তব সিস্টেমে

ZooKeeper (ZAB)। Ensemble-এ fast leader election (epoch-based), তারপর সব write সেই leader-এর atomic broadcast-এ। ZooKeeper নিজেই coordination-service — হাজারো HDFS/YARN/SolrCloud cluster এটাকে authority হিসেবে ভাড়া নেয়।

etcd (Raft)। Kubernetes-এর হৃদয়। Raft election — randomized timeout + term + majority vote; extra production armor: pre-vote (unproven candidate আগে জিজ্ঞেস করে disruption এড়াতে) আর check-quorum (leader নিজে majority হারিয়েছে কি না মাঝপথে যাচাই করে)।

Kafka KRaft migration। Kafka জীবনভর metadata-র জন্য ZooKeeper ভাড়া নিয়েছিল; KRaft (Kafka Raft) metadata topic-এর জন্য built-in Raft quorum controller আনে — dependency এক ঘরে নামল, controller failover সেকেন্ডে নামল। Migration note: একই প্রতিষ্ঠানের দুই generation, একই lesson — election-কে dedicated consensus-layer-এ দিন, ad-hoc নয়।

Kubernetes leader election। Controller-manager-এর HA-র জন্য API-server-এ Lease object: holder identity + renewTime; timeout-এ অন্য replica takeover করে — মূলত এই লেসনের lease+epoch pattern-এর managed রূপ।

যে ভুলগুলো সবাই করে

“সবচেয়ে বড় ID-ই জিতুক — bully সব সময় optimal।”

Bully-র invariant সরল, কিন্তু highest node unstable হলেই churn: প্রতি flap-এ full re-election, worst-case O(N²) message। Stable-leader preference ছাড়া bully busy cluster-এ self-inflicted outage বানায়।

“Election জিতলাম, এখন সব ঠিক আছে — কাজ চলবে।”

Election শেষ মানে শুধু একজনকে দায়িত্ব দেওয়া হলো; পুরনো leader নিজের মৃত্যু মানেনি — ghost-write আটকাতে epoch/fencing আর majority gate লাগে। Detection, designation, enforcement — তিন আলাদা স্তর।

“Dual leader হলেই data corruption নিশ্চিত।”

Fencing layer থাকলে পুরনো epoch-এর write স্টোরে ঢোকেই না — দুই leader দেখতে থাকে, এক leader-ই লেখে। Corruption তখনই, যখন storage-পর্যন্ত fencing number পৌঁছায় না (অথবা non-fenced lock)।

“Randomized timeout এমনি magic — fixed দিলেও তো চলত।”

Fixed window-এ collision deterministic — experiment-এর first row: 250+ split, zero leader। Randomization magic নয়, গণিত: collision probability ≈ 1/window-width; width আর detection-latency-র বাণিজ্য।

“Ring election পুরনো তাই — বাদ দিতেই হবে।”

Message-count bounded (2(N−1)), deterministic, memoryless — embedded/fabric network-এ আজও sensible। Bad হওয়ার কারণ পুরনো নয়; general-purpose WAN consensus-এ majority-vote family-র feature-set (term fencing, log check) richer।

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

1

Bully আর ring election-এ winner কে? Message complexity কত-কত?

স্মরণ

দুটোতেই winner = সর্বোচ্চ id-র জীবিত node। Bully best N−1, worst O(N²); ring সবসময় 2(N−1) (dead-link bypass ছাড়া)।

2

Fixed [15,15] timeout window-এ কী হয় আর কেন? Randomize করলে কী বদলায়?

যুক্তি

পাঁচজনই এক tick-এ fire → collision → split vote → retry → আবার collision: livelock, কোনো leader নেই। Randomization-এ same-tick probability ≈ 1/width; প্রথম unique-min fire-ই জেতে — detection latency মাত্র কয়েক tick।

3

Example-এ t16-এ storage কীভাবে node4-এর write ফেলে দিল? কোন সংখ্যা কার?

প্রয়োগ

Epoch comparison: node4-এর authority epoch 1-এর, স্টোর এখন epoch 2 দেখছে; 1 \< 2 → reject। Epoch-টাই fencing-token: পুরনো generation-এর প্রমাণ নতুন generation-এ কাগজ।

4

Majority gate ছাড়া দুই partition-side-ই leader বানাল; gate দিলে minority BLOCKED। Safety-liveness বাণিজ্যটা বাক্যে বলুন।

যুক্তি

Gate কিনে দেয় at-most-one-leader (safety), খেয়ে ফেলে minority-র availability (liveness)। Pigeonhole: 5-এর মধ্যে দুই disjoint side একসাথে ≥3 vote পায় না — তাই guarantee ভাঙে না।

5

Flapping highest-id node-সহ busy cluster-এ কোন election design বাছবেন, কেন?

ডিজাইন

Randomized-timeout + majority + incumbent-preference (Raft-ধাঁচ): stable leader টিকে থাকে, flapper শুধু নিজের ভোট না দিলেই চলে; bully হলে প্রতি flap-এ গোটা cluster re-election করত। Pre-vote/check-quorum যোগ করলে disruption আরও কমে।

6

ZooKeeper, etcd, Kafka-KRaft — election দিক থেকে এক লাইনে তুলনা করুন।

প্রয়োগ

ZooKeeper: ZAB-এর fast-election + epoch + atomic broadcast। etcd: Raft election (randomized timeout, term, pre-vote, check-quorum)। Kafka: ZooKeeper-dependency KRaft built-in Raft quorum দিয়ে বদলেছে — metadata election এখন broker-cluster-এর নিজের।

এরপর কী

এরপর কী

আমাদের হাতে এখন একজন leader — majority-র সই, epoch-এর গায়ে-বাঁধা। কিন্তু লক্ষ্য করুন: election ঠিক করে কে সিদ্ধান্ত নেবে, কোন value-তে একমত হতে হবে তা নয়। পুরনো leader-এর half-propagated value, নতুন proposer-এর conflicting value — এদের মধ্যে একটা বেছে নেওয়ার নিয়ম কী? এই হলো consensus problem — আর এর ঐতিহাসিক উত্তর Paxos, যার reputation এমন: “সবাই বোঝে consensus দরকার; কেউ বোঝে না Paxos কেন কাজ করে।” পরের লেসনে আমরা intuition দিয়ে শুরু করব — কেন naive attempt গুলো ব্যর্থ হয়, সেখান থেকে Paxos-এর invariant-গুলো নিজেই বেরিয়ে আসবে।

আরও পড়ুন

  • Elections in a Distributed Computing System — Hector Garcia-Molina — IEEE Transactions on Computers, 1982 · Bully আর ring election-এর মূল paper — এই লেসনের দুই classic algorithm এখানেই প্রথম formalize হয়
  • ZooKeeper: Wait-free Coordination for Internet-scale Systems — Patrick Hunt, Mahadev Konar, Flavio P. Junqueira, Benjamin Reed — USENIX ATC 2010 · ZooKeeper-এর ZAB protocol — leader election + atomic broadcast-এর production রূপ
  • In Search of an Understandable Consensus Algorithm (Raft) — Diego Ongaro, John Ousterhout — USENIX ATC 2014 · Raft-এর leader election section — randomized timeout আর term-এর আধুনিক treatment; পরের-পরের লেসনের ভিত্তি