লিডার ইলেকশন ও কনসেনসাস
এই পাঠে যা শিখবেন
- কেন একাধিক রেপ্লিকার মধ্যে ঠিক একজন লিডার দরকার এবং স্প্লিট-ব্রেইন সমস্যা কী
- Raft-এর উচ্চ-স্তরের আইডিয়া — নোড স্টেট, টার্ম নম্বর, ইলেকশন টাইমআউট ও হার্টবিট
- কেন মেজরিটি ভোট (কোরাম, L31-এর সাথে সম্পর্কিত) স্প্লিট-ব্রেইন গাণিতিকভাবে প্রতিরোধ করে
- Python-এ একটি সরলীকৃত লিডার ইলেকশন সিমুলেশন — ৫টি নোডের মধ্যে ঠিক একজন লিডার নির্বাচন করা
১ · সমস্যা — একজন লিডার দরকার কেন, এবং ক্র্যাশ হলে কী হয়
অনেক ডিস্ট্রিবিউটেড সিস্টেমে (যেমন একটি ডেটাবেস ক্লাস্টার) রাইট অপারেশন সিরিয়ালাইজ করতে বা গুরুত্বপূর্ণ সিদ্ধান্ত সমন্বয় করতে ঠিক একজন লিডার নোড দরকার হয় — অন্য সব নোড ফলোয়ার হিসেবে কাজ করে। কিন্তু লিডার নোড যদি ক্র্যাশ করে বা নেটওয়ার্ক পার্টিশনের কারণে বাকিদের থেকে বিচ্ছিন্ন হয়ে যায়, তাহলে বাকি নোডদের নতুন একজন লিডার বেছে নিতে হবে — এবং এই প্রক্রিয়ায় সবচেয়ে বড় বিপদ হলো স্প্লিট-ব্রেইন: একই সময়ে দুটি ভিন্ন নোড নিজেদের লিডার মনে করা শুরু করলে, দুজনেই একসাথে পরস্পরবিরোধী সিদ্ধান্ত নিতে পারে — ডেটা করাপশনের একটি সরাসরি রাস্তা।
২ · কনসেনসাস অ্যালগরিদম — Paxos ও Raft
কনসেনসাস অ্যালগরিদমConsensus Algorithmএমন একটি প্রোটোকল যা ফেইলিওর ও নেটওয়ার্ক সমস্যা সত্ত্বেও একাধিক ডিস্ট্রিবিউটেড নোডকে একটি মানে (যেমন কে লিডার) নির্ভরযোগ্যভাবে একমত হতে সাহায্য করে। এই সমস্যা সমাধান করে। Paxos সবচেয়ে পুরনো ও প্রমাণিত, কিন্তু বহুল-পরিচিতভাবে বোঝা কঠিন। Raft ডিজাইনই করা হয়েছিল Paxos-এর সমতুল্য গ্যারান্টি দিয়ে কিন্তু অনেক বেশি বোধগম্যভাবে — আজকাল etcd (Kubernetes-এর মূল স্টোরেজ) ও Consul-এর মতো সিস্টেম Raft ব্যবহার করে।
ডিফল্ট স্টেট — লিডার থেকে নিয়মিত হার্টবিট শোনে এবং তার নির্দেশ মেনে চলে।
একটি Follower লিডারের কাছ থেকে হার্টবিট না পেয়ে (টাইমআউট) নিজেকে Candidate ঘোষণা করে এবং বাকিদের কাছে ভোট চায়।
মেজরিটি ভোট পেলে Candidate Leader হয়ে যায় এবং নিয়মিত হার্টবিট পাঠাতে শুরু করে যাতে নতুন ইলেকশন শুরু না হয়।
প্রতিটি ইলেকশনের একটি বাড়তে থাকা টার্ম নম্বর থাকে (কোন ইলেকশন রাউন্ড, তা চিহ্নিত করতে)। যদি একাধিক Follower একই সময়ে টাইমআউট হয়ে Candidate হয়ে যায় (স্প্লিট ভোট), কোনো একজনই মেজরিটি না পেলে একটি নতুন টার্ম শুরু হয় — এই কারণে Raft-এ প্রতিটি নোডের টাইমআউট র্যান্ডমাইজড রাখা হয়, যাতে সব নোড একসাথে Candidate না হয়ে যায় এবং স্প্লিট ভোট বারবার না ঘটে।
ধরুন N নোডের ক্লাস্টারে একটি নোড লিডার হতে হলে মেজরিটি (>N/2) ভোট দরকার। একই নোড সেটের মধ্যে দুটি ভিন্ন মেজরিটি গ্রুপ একসাথে থাকা গাণিতিকভাবে অসম্ভব — কারণ দুটি গ্রুপের প্রতিটিতেই N/2-এর বেশি নোড থাকতে হবে, এবং মোট নোড সংখ্যা N হলে তাদের অবশ্যই কমপক্ষে একটি নোডে ওভারল্যাপ করতে হবে (ঠিক L31-এর কোরাম যুক্তির মতোই — R+W>N মানে রিড ও রাইট সেট অন্তত একটি নোডে মিলবেই)। ফলে এক টার্মে একই সাথে দুইজন ভিন্ন নোড মেজরিটি ভোট পেয়ে লিডার হতে পারে না।
৩ · Python সিমুলেশন — ৫টি নোডের লিডার ইলেকশন
বাস্তব Raft-এ প্রায়োরিটি এলোমেলো টাইমআউট থেকে আসে (যে আগে টাইমআউট হয়, সে প্রথম Candidate হয়)। নিচের সরলীকৃত
সিমুলেশনে প্রতিটি নোডের একটি ফিক্সড-সিড-ভিত্তিক priority আছে (রিপ্রোডিউসিবিলিটির জন্য প্রতিটি নোডের জন্য
random.Random(node_index) ব্যবহার করে) — সর্বোচ্চ priority-র নোডই candidate, এবং সে জিতবে যদি
মেজরিটি নোডও তাকে ভোট দেয়।
import random
num_nodes = 5
# প্রতিটি নোডের একটি ফিক্সড-সিড-ভিত্তিক priority — বাস্তবে এটি randomized election
# timeout-এর মতো কাজ করে (যে আগে timeout হয় সে candidate হয়)
priorities = []
for node_id in range(num_nodes):
node_rng = random.Random(node_id)
priorities.append(node_rng.randint(1, 100))
print("প্রতিটি নোডের priority:")
for node_id, p in enumerate(priorities):
print(f" node_{node_id}: priority = {p}")
candidate = priorities.index(max(priorities))
print(f"\nসর্বোচ্চ priority-র candidate: node_{candidate} (priority={priorities[candidate]})")
# প্রতিটি নোড candidate-কে ভোট দেবে কি না, তা একটি ডিটারমিনিস্টিক নিয়মে ঠিক হচ্ছে:
# একটি নোড ভোট দেয় যদি candidate-এর priority তার নিজের চেয়ে বেশি বা সমান হয়
votes = 0
print(f"\nভোটিং রাউন্ড (candidate = node_{candidate}):")
for node_id in range(num_nodes):
votes_yes = priorities[candidate] >= priorities[node_id]
if votes_yes:
votes += 1
print(f" node_{node_id} ভোট: {'YES' if votes_yes else 'NO'}")
majority_needed = num_nodes // 2 + 1
print(f"\nমোট ভোট পেল: {votes}/{num_nodes} (majority দরকার: {majority_needed})")
if votes >= majority_needed:
leader = candidate
print(f"\n✓ node_{leader} নির্বাচিত হলো নতুন LEADER (majority ভোট পেয়েছে)।")
else:
leader = None
print("\n✗ কোনো candidate majority ভোট পায়নি — নতুন রাউন্ড দরকার (split vote)।")
assert leader is not None, "expected exactly one leader"
print(f"\nনিশ্চিতকরণ: ঠিক একজন leader নির্বাচিত হয়েছে → node_{leader}")
লিডার ইলেকশন ও কনসেনসাস একটি একক নীতির উপর দাঁড়িয়ে — মেজরিটি চুক্তি — যা L31-এর কোরাম রিড/ রাইটের মতোই একটি পিজিয়নহোল-স্টাইল যুক্তি ব্যবহার করে নিশ্চিত করে দুটি পরস্পরবিরোধী সিদ্ধান্ত একসাথে হতে পারবে না। এই একই নীতি L37-এ ফেইলওভারের ভিত্তি হয়ে দাঁড়াবে — কখন ও কীভাবে একটি নতুন প্রাইমারি নোড নিরাপদে প্রমোট করা যায় তা এই কনসেনসাস মেকানিজমের উপরই নির্ভর করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ Raft-এ ইলেকশন টাইমআউট ইচ্ছাকৃতভাবে র্যান্ডমাইজড রাখা হয় কেন — সব নোডে একই ফিক্সড টাইমআউট রাখলে কী সমস্যা হতো?
যদি সব নোডের টাইমআউট একই ফিক্সড হতো, লিডার ক্র্যাশ করলে সব Follower প্রায় একই মুহূর্তে টাইমআউট হয়ে একসাথে Candidate হয়ে যেত — ফলে ভোট সমানভাবে ভাগ হয়ে যাওয়ার (split vote) সম্ভাবনা অনেক বেশি থাকত, এবং কোনো candidate-ই মেজরিটি না পেয়ে বারবার নতুন টার্ম শুরু হতো (কখনো কনভার্জ না করার ঝুঁকি)। র্যান্ডমাইজড টাইমআউট নিশ্চিত করে সাধারণত একটি নোডই প্রথমে টাইমআউট হবে এবং বাকিরা টাইমআউট হওয়ার আগেই তার ভোটের অনুরোধ পাবে — স্প্লিট ভোটের সম্ভাবনা বহুগুণ কমে যায়।
প্র ০২ ৪টি নোডের একটি ক্লাস্টারে (জোড় সংখ্যা) মেজরিটি ভোটিং কি ঠিকভাবে কাজ করে? সমস্যা থাকলে কী?
কাজ করে, তবে জোড় সংখ্যার ক্লাস্টারে একটি বিশেষ ঝুঁকি আছে — নেটওয়ার্ক পার্টিশনে ক্লাস্টার ঠিক ২-২ ভাগে ভাগ হয়ে গেলে কোনো অর্ধেকই মেজরিটি (>৪/২=২, অর্থাৎ কমপক্ষে ৩) পাবে না, ফলে কোনো লিডার নির্বাচিতই হতে পারবে না — সিস্টেম সাময়িকভাবে অনুপলব্ধ (unavailable) হয়ে পড়ে, যদিও ডেটা করাপ্ট হয় না। এই কারণেই ব্যবহারিকভাবে কনসেনসাস ক্লাস্টার প্রায় সবসময় বিজোড় সংখ্যক নোড (৩, ৫, ৭) দিয়ে চালানো হয় — এতে সমান ভাগে পার্টিশন হওয়া অসম্ভব, এবং একই খরচে জোড় সংখ্যার তুলনায় ভালো ফল্ট-টলারেন্স পাওয়া যায়।
প্র ০৩ দুই-ফেজ কমিট (L18)-এর কোঅর্ডিনেটর এবং Raft-এর লিডার — দুটোই "একজন নোড বাকিদের নির্দেশ দেয়" এই ধারণা ব্যবহার করে। মূল পার্থক্য কোথায়?
2PC-এর কোঅর্ডিনেটর একটি ফিক্সড, প্রি-অ্যাসাইনড ভূমিকা — কোঅর্ডিনেটর ক্র্যাশ করলে অংশগ্রহণকারীরা লক ধরে অনির্দিষ্টকালের জন্য আটকে থাকে (ব্লকিং), কারণ কোনো স্বয়ংক্রিয় প্রতিস্থাপন মেকানিজম নেই। Raft-এর লিডার একটি ডায়নামিক, নির্বাচিত ভূমিকা — ক্র্যাশ করলেই বাকি নোডরা স্বয়ংক্রিয়ভাবে মেজরিটি ভোটিং দিয়ে একটি নতুন লিডার নির্বাচন করে ফেলে, তাই সিস্টেম নিজে থেকেই সেরে ওঠে (self-healing), কোনো ম্যানুয়াল হস্তক্ষেপ ছাড়াই।
অনুশীলন
-
পরিবর্তন করুন: উপরের কোড সেলে
num_nodes৭ করুন এবংmajority_neededকীভাবে বদলায় দেখুন। এরপর ভাবুন ৭ নোডের ক্লাস্টার কতগুলো একসাথে-ব্যর্থ নোড সহ্য করতে পারবে (এখনো লিডার নির্বাচন করতে পারবে)।num_nodes = 7হলেmajority_needed = 7 // 2 + 1 = 4। অর্থাৎ কমপক্ষে ৪টি নোড জীবিত ও যোগাযোগযোগ্য থাকলেই একটি লিডার নির্বাচন করা সম্ভব — মানে ৭টির মধ্যে সর্বোচ্চ ৩টি নোড একসাথে ব্যর্থ হলেও (ডাউন বা বিচ্ছিন্ন) ক্লাস্টার এখনো কার্যকরভাবে চলতে পারবে। এটাই দেখায় কেন বেশি নোড রাখা (এখানে ৩-এর বদলে ৭) সরাসরি ফল্ট-টলারেন্স বাড়ায়, যদিও প্রতিটি সিদ্ধান্তে বেশি নোডের ভোট জোগাড় করতে হয়। -
চিন্তা করুন: একটি সিস্টেমে যদি নেটওয়ার্ক পার্টিশনের কারণে ক্লাস্টার ৩-২ ভাগে ভাগ হয়ে যায় (৫ নোডের ক্লাস্টারে), কোন অংশ লিডার নির্বাচন করতে পারবে এবং কোনটি পারবে না?
৫ নোডের ক্লাস্টারে মেজরিটি দরকার কমপক্ষে ৩টি নোড (৫//2+1=3)। ৩-২ ভাগে ভাগ হলে ৩-নোডের অংশটি মেজরিটি (৩ ≥ ৩) পাবে এবং একজন লিডার নির্বাচন করতে পারবে, কিন্তু ২-নোডের অংশটি মেজরিটি (২ < ৩) পাবে না, তাই কোনো লিডার নির্বাচন করতে পারবে না — সেই অংশ শুধু পড়ার (read-only, বা সম্পূর্ণ অনুপলব্ধ) মোডে থেকে যাবে। এটাই নিশ্চিত করে একই সময়ে দুই ভাগেই লিডার তৈরি হয়ে স্প্লিট-ব্রেইন ঘটবে না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫১টি পাঠ পরবর্তী পাঠ — ফেইলওভার, রেপ্লিকেশন ও ডিজাস্টার রিকভারি (L37) — RTO/RPO ও মাল্টি-রিজিয়ন ডিজাইন।
- পূর্ববর্তী পাঠ — রেট লিমিটিং অ্যালগরিদম L35 Token Bucket, Leaky Bucket ও Sliding Window কৌশলগুলো দেখুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics ও System Design — সব এক জায়গায়।