এই পর্বে যা শিখবেন
- Membership সমস্যা, আর failure detector-এর completeness, accuracy ও সময়ের মাপকাঠি
- Push ও pull heartbeat, timeout বাছাইয়ের হিসাব, all-to-all-এর O(n²) খরচ আর phi accrual-এর সূত্র
- Gossip কেন O(log n) রাউন্ডে ছড়ায় — প্রমাণের ধারণা, fanout, push-pull, rumor mongering বনাম anti-entropy
- Heartbeat counter দিয়ে gossip-ভিত্তিক membership, আর SWIM-এর ping, ping-req, suspicion ও incarnation
- Split brain কেন হয়, membership কেন consensus নয়, আর বাস্তবের knob কীভাবে বাছবেন
১ · Membership সমস্যা — cluster-এ কে আছে, কে বেঁচে আছে
একটি কাল্পনিক ride-sharing অ্যাপের কথা ভাবুন। চালকদের location রাখা হয় ৩০০টি সার্ভারের একটি distributed cache-এ, আর কোন চালকের তথ্য কোন সার্ভারে থাকবে তা ঠিক হয় পর্ব ১৬-এর consistent hashing ring দিয়ে। Ring বানাতে প্রত্যেক সার্ভারের হাতে একটি তালিকা লাগে: এই মুহূর্তে কারা আছে. একটি সার্ভার হঠাৎ বিদ্যুৎ হারালে তার ভাগের key অন্যদের নিতে হবে — কিন্তু তার আগে কাউকে টের পেতে হবে যে সে আর নেই।
এখানে আসলে দুটি প্রশ্ন। প্রথমটি সদস্যপদ: কে ইচ্ছা করে যোগ দিল (join), কে ইচ্ছা করে বেরিয়ে গেল (leave). এগুলো ঘোষণা দিয়ে হয়, তাই তুলনামূলক সহজ। দ্বিতীয়টি বেঁচে থাকা: কে কোনো ঘোষণা ছাড়াই crash করল, আটকে গেল, বা network থেকে বিচ্ছিন্ন হলো। এটাই কঠিন অংশ, কারণ মৃত সার্ভার নিজের মৃত্যুর খবর দেয় না।
একটি গার্মেন্টস কারখানার ফ্লোরে হাজিরা খাতার কথা ভাবুন। সকালে কে ঢুকলেন, কে ছুটি নিলেন — খাতায় ওঠে, কারণ মানুষটি নিজেই জানান। কিন্তু দুপুরে কেউ অসুস্থ হয়ে লাইন থেকে সরে গেলে খাতা কিছুই জানে না, যতক্ষণ না supervisor খেয়াল করেন “অমুকের মেশিন অনেকক্ষণ চলছে না।” Supervisor কতক্ষণ অপেক্ষা করবেন? খুব তাড়াতাড়ি ডাক দিলে চা খেতে যাওয়া কর্মীকেও অনুপস্থিত লেখা হবে; খুব দেরিতে ডাকলে অর্ডার পিছিয়ে যাবে। এই পর্বের পুরোটাই এই প্রশ্নের প্রকৌশল।
পর্ব ৩৮-এ দেখেছেন, একটি অনুরোধের উত্তর না এলে আপনি জানেন না অনুরোধটি হারিয়েছে, উত্তর হারিয়েছে, সার্ভার ধীর, না সার্ভার মৃত — এটাই partial failure. তাই কোনো যন্ত্র নিশ্চিতভাবে বলতে পারে না “ও মারা গেছে”; বলতে পারে শুধু “অনেকক্ষণ ওর কোনো সাড়া পাইনি।” Membership সেবার কাজ এই অনিশ্চয়তার ভেতরে যথেষ্ট ভালো একটি তালিকা সবার হাতে রাখা।
একটি membership সেবার দুটি অংশ: (১) failure detector — কে সম্ভবত বিকল, তা অনুমান করা; (২) dissemination — join, leave আর বিকলতার খবর সবার কাছে ছড়ানো। দুটিকে আলাদা করে ভাবলে নকশা পরিষ্কার হয়; SWIM ঠিক এই ভাগটি করেছিল (অংশ ৮)।
ভালো membership সেবার কাছে আমরা চাই: দ্রুত ধরা (পর্ব ৫-এর MTTR-এর প্রথম অংশই টের পাওয়ার সময়), কম ভুল (প্রতিটি ভুল ঘোষণা অকারণ failover আর ডেটা সরানো ডেকে আনে), কম খরচ, বড় হলেও চলা, আর কিছুক্ষণের মধ্যে মোটামুটি মেলানো তালিকা — প্রতি মুহূর্তে হুবহু এক হওয়ার নিশ্চয়তা সাধারণত থাকে না (অংশ ৯)।
২ · Failure detector — completeness, accuracy আর সময়ের টানাপোড়েন
১৯৯৬ সালে Tushar Chandra আর Sam Toueg একটি প্রভাবশালী paper-এ failure detector-কে দুটি গুণ দিয়ে মাপার ভাষা দেন। Completeness: যে সদস্য সত্যিই crash করেছে, তাকে শেষ পর্যন্ত (জীবিত) সবাই সন্দেহ করবে। Accuracy: জীবিত সদস্যকে কেউ সন্দেহ করবে না। দুটিই একসাথে পুরোপুরি পাওয়া মানে একটি “নিখুঁত” detector.
সমস্যা হলো, বাস্তবের network-এ বার্তা কতক্ষণে পৌঁছাবে তার কোনো ঊর্ধ্বসীমা নেই (asynchronous model). সেখানে নিখুঁত detector বানানো যায় না: যত বড় timeout-ই নিন, একটি জীবিত কিন্তু ধীর সদস্য তার চেয়েও দেরি করতে পারে। Fischer, Lynch আর Paterson-এর ১৯৮৫ সালের বিখ্যাত ফলাফল (FLP) এরই আরেক রূপ — এমন মডেলে একটি process-ও crash করতে পারলে সবসময় শেষ হওয়ার নিশ্চয়তাসহ consensus অসম্ভব। Chandra–Toueg দেখান, detector যদি শুধু “এক সময় পরে” নির্ভুল হয় (eventually accurate), তাতেই majority সচল থাকলে consensus সম্ভব — পর্ব ৪২-এর Raft ঠিক এই ধরনের অনুমানের ওপর দাঁড়িয়ে।
প্রকৌশলের ভাষায় তাই প্রশ্নটি দাঁড়ায় তিনটি সংখ্যায় (Wei Chen, Sam Toueg আর Marcos Aguilera-র ২০০২ সালের একটি paper এই মাপকাঠিগুলো বিশদ করে):
| মাপকাঠি | মানে | খারাপ হলে যা হয় |
|---|---|---|
| ধরার সময় (detection time) | crash থেকে “মৃত” ঘোষণা পর্যন্ত সময় | মৃত সার্ভারে অনুরোধ যায়, ব্যবহারকারী timeout দেখেন, MTTR বাড়ে |
| ভুলের হার (mistake rate) | জীবিত সদস্যকে প্রতি ঘণ্টায় কতবার মৃত ভাবা হয় | অকারণ failover, rebalancing, flapping |
| ভুলের স্থায়িত্ব | ভুল সন্দেহ ঠিক হতে কতক্ষণ লাগে | যতক্ষণ ভুল চলে, সেই সদস্যের ক্ষমতা নষ্ট |
বড় timeout → কম false positive, ধীরে ধরা
এই টানাপোড়েন এড়ানোর কোনো উপায় নেই; আছে শুধু আরও বুদ্ধিমান মাপজোখ। পরের অংশগুলোতে তিনটি কৌশল দেখবেন: network-এর আচরণ শিখে নেওয়া (phi accrual), এক চোখের বদলে কয়েক চোখে দেখা (SWIM-এর পরোক্ষ ping), আর সঙ্গে সঙ্গে মৃত না বলে আগে “সন্দেহ” বলা (suspicion).
False positive শুধু অপচয় নয়, বিপদও। একটি জীবিত database leader-কে মৃত ভেবে নতুন leader বানালে, আর পুরোনোটি তখনো লেখা নিতে থাকলে, দুজন leader একসাথে — split brain (পর্ব ২২)। এজন্য বিকলতা ধরার পরের পদক্ষেপ যত দামি, detector তত সাবধান হওয়া উচিত।
৩ · Heartbeat — push বনাম pull, নির্দিষ্ট timeout আর all-to-all-এর O(n²) খরচ
সবচেয়ে সহজ detector হলো heartbeat: প্রতি Δ সময় পরপর একটি ছোট “আমি আছি” বার্তা। দুই রকমে করা যায়:
Push heartbeat
প্রতিটি সদস্য নিজেই নিয়মিত বার্তা পাঠায়। পর্যবেক্ষক শুধু শোনে; শেষ বার্তার পর T সময় চুপ থাকলে সন্দেহ। পর্যবেক্ষকের কাজ কম, কিন্তু পাঠানোর থ্রেড বেঁচে থাকলেই “আছি” বলে — আসল কাজ আটকে থাকলেও।
প্রবাসী ছেলে রোজ রাত ১০টায় মাকে ফোন করেPull (ping-ack)
পর্যবেক্ষক নিজে জিজ্ঞেস করে, উত্তরের অপেক্ষা করে। পর্ব ১৪-এর load balancer-এর health check এই ধরনের। প্রতি পরীক্ষায় দুটি বার্তা, কিন্তু উত্তরে আসল কাজের পথও যাচাই করা যায়।
মা নিজে ফোন করে খোঁজ নেননির্দিষ্ট timeout-এর হিসাব. ধরা যাক Δ = ১ s, আর timeout T = ৩ s (পরপর তিনটি heartbeat না এলে)। Crash ঘটে শেষ heartbeat পৌঁছানোর পর কোনো এলোমেলো মুহূর্তে — ০ থেকে Δ-এর মধ্যে, যখন পরেরটি যাওয়ার কথা। তাই ধরার সময় T − Δ থেকে T-এর মধ্যে, গড়ে প্রায় T − Δ/2 = ২.৫ s. অন্যদিকে, একটি জীবিত সদস্যের heartbeat-এর মাঝের ফাঁক (inter-arrival time) কখনো T ছাড়ালেই false positive. ফাঁক বড় হয় network-এর jitter, packet loss, অতিরিক্ত ব্যস্ত CPU বা GC pause-এর কারণে।
TCP connection নিজে কি মৃত্যু টের পায় না? পায়, কিন্তু খুব ধীরে। TCP keepalive-এর ডিফল্ট নিষ্ক্রিয় সময় Linux-এ প্রায় ২ ঘণ্টা (কনফিগার করা যায়, সিস্টেমভেদে আলাদা)। তাই membership সিস্টেমগুলো সাধারণত অ্যাপের স্তরে নিজস্ব heartbeat চালায়, অনেক সময় UDP-তে, যাতে হারানো বার্তা retransmission-এর আড়ালে লুকিয়ে না থাকে।
সবাই সবাইকে — all-to-all-এর দাম
একটি কেন্দ্রীয় পর্যবেক্ষক সবার heartbeat নিলে সে নিজেই একটি single point of failure, আর তার network খারাপ হলে সে সবাইকে মৃত ভাববে। তাই peer-to-peer সমাধানে স্বাভাবিক চিন্তা: প্রত্যেকে প্রত্যেককে heartbeat পাঠাক। n জন সদস্য হলে প্রতি Δ-তে বার্তা n(n − 1), আর প্রত্যেককে প্রতি Δ-তে n − 1টি বার্তা গ্রহণ ও প্রক্রিয়া করতে হয়।
| n (সদস্য) | প্রতি সেকেন্ডে মোট বার্তা (Δ = ১ s) | প্রতি সদস্যের গ্রহণ |
|---|---|---|
| ১০ | ৯০ | ৯ |
| ১০০ | ৯,৯০০ | ৯৯ |
| ১,০০০ | ৯,৯৯,০০০ (প্রায় ১০ লাখ) | ৯৯৯ |
ভুলও বাড়ে: n(n − 1)টি জোড়া আলাদা করে বিচার করে, তাই n বড় হলে কোনো না কোনো জোড়ার ফাঁক timeout ছাড়াবেই। কয়েক ডজন সদস্যে all-to-all ঠিক আছে; শত শত বা হাজারে নতুন চিন্তা লাগে।
৪ · Phi accrual — “মৃত না জীবিত” নয়, সন্দেহের মাত্রা
নির্দিষ্ট timeout একটি একক সংখ্যা, কিন্তু network-এর আচরণ বদলায়: রাতে শান্ত, দুপুরে ভিড়ে jitter বেশি; এক data center-এ ফাঁক স্থির, দূরের region-এ ছড়ানো। ২০০৪ সালে Naohiro Hayashibara ও সহকর্মীরা (Défago, Yared, Katayama) প্রস্তাব করেন φ (phi) accrual failure detector: detector “মৃত/জীবিত” বলবে না, বরং একটি অবিরাম সংখ্যা দেবে — সন্দেহ কতটা।
পদ্ধতিটি এরকম। সাম্প্রতিক কয়েকশো heartbeat-এর ফাঁক একটি sliding window-এ রাখা হয়, তা থেকে গড় μ আর standard deviation σ. শেষ heartbeat-এর পর t সময় চুপ থাকলে প্রশ্ন: জীবিত থাকলে এত দেরি হওয়ার সম্ভাবনা কত? সেই সম্ভাবনা Plater(t) হলে
φ = 1 → ১০%, φ = 2 → ১%, φ = 3 → ০.১% — জীবিত সদস্যের এত দেরি হওয়ার সম্ভাবনা, অর্থাৎ এখন সন্দেহ করলে ভুল হওয়ার সম্ভাবনা
সুবিধা দুটি। প্রথমত, network ঝাঁকুনি দিলে σ বাড়ে, ফলে একই নীরবতায় φ কম আসে — detector নিজে থেকে ধৈর্য বাড়ায়। দ্বিতীয়ত, একই detector থেকে ভিন্ন কাজ ভিন্ন সীমায় সিদ্ধান্ত নিতে পারে: φ = 2-তে নতুন অনুরোধ পাঠানো বন্ধ (সস্তা, ফেরানো যায়), φ = 8-এ failover শুরু (দামি)। লেখার সময়ের ডকুমেন্টেশন অনুযায়ী Cassandra আর Akka cluster দুটিই phi accrual ব্যবহার করে, ডিফল্ট সীমা ৮-এর কাছাকাছি; সংস্করণভেদে বিবরণ আলাদা।
নিচের simulator এক ঘণ্টার (৩,৬০০টি) heartbeat-এর ফাঁক বানায়: গড়ে ১,০০০ ms, ±৪৫০ ms-এর মধ্যে ছড়ানো (মাঝখানে ঘন, দুই পাশে পাতলা — bell curve-এর মতো), আর ১% ক্ষেত্রে ০–৪,০০০ ms-এর বাড়তি বিরতি (GC pause বা ভিড়ের মতো)। তারপর কয়েকটি নির্দিষ্ট timeout-এ ভুলের সংখ্যা আর ধরার সময় গোনে, আর তিন রকম মডেলে φ হিসাব করে।
// Heartbeat every 1000 ms; arrival gaps jitter, and 1% of gaps hit a long pause.
let seed = 7; function rand() { // mulberry32: seeded PRNG
seed = (seed + 0x6D2B79F5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
function gap() { // ms between two heartbeats of a HEALTHY node
const g = 1000 + 150 * (rand() + rand() + rand() - 1.5) * 2; // ~bell curve around 1000
return rand() < 0.01 ? g + 4000 * rand() : g; // 1%: GC pause / congestion
}
const hour = Array.from({ length: 3600 }, gap); // one hour of heartbeats
console.log("timeout false alarms/hour detect time after crash (avg, max)");
for (const T of [1200, 1500, 2000, 3000, 5000]) {
const fp = hour.filter(g => g > T).length;
// crash happens at a uniform moment after the last heartbeat arrived
let sum = 0; for (let i = 0; i < 1000; i++) sum += T - 1000 * rand();
console.log(String(T).padStart(7) + " " + String(fp).padStart(17) + " " +
(sum / 1000).toFixed(0).padStart(6) + " ms, " + T + " ms");
}
// phi accrual: phi = -log10(P(next gap > t)), normal fit on the last 1000 gaps
const win = hour.slice(-1000);
const mu = win.reduce((a, b) => a + b) / win.length;
const sd = Math.sqrt(win.reduce((a, b) => a + (b - mu) ** 2, 0) / win.length);
const tail = t => { const y = (t - mu) / sd; // logistic approximation of the normal tail
return 1 / (1 + Math.exp(y * (1.5976 + 0.070566 * y * y))); };
console.log("window mean " + mu.toFixed(0) + " ms, std " + sd.toFixed(0) + " ms");
console.log("silent for phi(normal) phi(exponential) phi(measured)");
for (const t of [1000, 1500, 2000, 2500, 3000])
console.log(String(t).padStart(7) + " ms " + (-Math.log10(tail(t))).toFixed(2).padStart(11) +
" " + (t / mu * Math.LOG10E).toFixed(2).padStart(16) +
" " + (-Math.log10(win.filter(g => g > t).length / win.length)).toFixed(2).padStart(13));আউটপুট:
timeout false alarms/hour detect time after crash (avg, max)
1200 346 720 ms, 1200 ms
1500 32 1012 ms, 1500 ms
2000 28 1505 ms, 2000 ms
3000 15 2499 ms, 3000 ms
5000 0 4495 ms, 5000 ms
window mean 1009 ms, std 242 ms
silent for phi(normal) phi(exponential) phi(measured)
1000 ms 0.29 0.43 0.32
1500 ms 1.68 0.65 2.10
2000 ms 4.97 0.86 2.15
2500 ms 11.50 1.08 2.22
3000 ms 22.89 1.29 2.40
Timeout-এর টানাপোড়েন সংখ্যায়: ১,২০০ ms-এ ঘণ্টায় ৩৪৬টি ভুল সংকেত — সাধারণ jitter-ই সীমা পেরোয়। ১,৫০০ ms-এ মাত্র ৩২টি, কারণ jitter ১,৪৫০-এর বেশি যায় না; বাকি থাকে শুধু লম্বা বিরতি। ৫,০০০ ms-এ ভুল ০, কিন্তু মৃত সার্ভার ধরতে গড়ে প্রায় ৪.৫ s — প্রতিবারই প্রায় T − ৫০০ ms, অংশ ৩-এর হিসাবের মতো।
φ-এর শিক্ষা: normal মডেল বলছে ৩,০০০ ms নীরবতার সম্ভাবনা প্রায় 10−23, অথচ আসল ডেটায় এমন দেরি প্রায় ০.৪% (φmeasured = ২.৪০)। লম্বা বিরতির “ভারী লেজ” normal বণ্টন ধরে না, তাই সে অতি-আত্মবিশ্বাসী: এখানে φ = ৮ আসে প্রায় ২,২৭২ ms-এ, যেখানে ঘণ্টায় তবু ২৫টি ভুল থাকত। Exponential মডেল উল্টো — φ = ৮ ছুঁতে প্রায় ১৮.৬ s. তাই implementation-গুলো বাড়তি knob রাখে, যেমন Akka-র “acceptable heartbeat pause” আর σ-এর নিম্নসীমা। φ ততটাই ভালো, যতটা ভালো আপনার ডেটার মডেল।
৫ · কেন্দ্রীয় membership — coordination service-এর হাতে তালিকা
সবচেয়ে প্রচলিত সমাধানটি আসলে সবচেয়ে সরল: তালিকাটি রাখুন একটি coordination service-এ — ZooKeeper বা etcd-এর মতো ছোট, অত্যন্ত নির্ভরযোগ্য একটি cluster, যা নিজে consensus দিয়ে তথ্য মেলায় (পর্ব ৪৩)। প্রতিটি সদস্য যোগ দেওয়ার সময় সেখানে একটি ephemeral node (ZooKeeper) বা lease-যুক্ত key (etcd) বানায়, আর নিজের session বা lease নিয়মিত heartbeat দিয়ে নবায়ন করে। Heartbeat থামলে session-এর মেয়াদ শেষে entry নিজে থেকেই মুছে যায়, আর যারা “watch” করছে তারা সঙ্গে সঙ্গে খবর পায়।
সুবিধা
সবাই একই তালিকা দেখে, একই ক্রমে বদল পায় — কারণ তালিকা একটি consensus-চালিত জায়গায়। Client সরল: শুধু lease নবায়ন আর watch. তালিকার সাথে fencing token-এর মতো ক্রমিক নম্বরও পাওয়া যায়।
শত থেকে কয়েক হাজার সদস্যে প্রচলিতদাম
সব সদস্যের session-heartbeat একটি ছোট cluster-এ যায়, তাই বিশাল আকারে সেটাই সীমা। আর এটি মাপে “সদস্য ↔ coordination service” সংযোগ, “সদস্য ↔ সহকর্মী” নয়: সদস্যটি ব্যবহারকারীদের ঠিকই সেবা দিচ্ছে, কিন্তু শুধু ZooKeeper-এর সাথে link কাটলে তালিকা থেকে বাদ।
নির্ভরতা: coordination service নিজেবাস্তবে এর উদাহরণ অনেক। প্রকাশিত ডকুমেন্টেশন অনুযায়ী Kubernetes-এ প্রতিটি node-এর kubelet নিয়মিত একটি Lease object নবায়ন করে, আর control plane নির্দিষ্ট সময় নবায়ন না দেখলে node-এর Ready অবস্থা Unknown করে দেয় (kubectl-এ NotReady দেখায়); সেই অবস্থা শেষ পর্যন্ত etcd-তে থাকে। Kafka-র পুরোনো সংস্করণে broker-দের তালিকা ZooKeeper-এ ephemeral node হিসেবে থাকত; সাম্প্রতিক সংস্করণ নিজস্ব Raft-ভিত্তিক controller (KRaft)-এ সরে গেছে। পর্ব ৪৭-এ service discovery-তে এই ধরনের registry আবার আসবে।
কখন কোনটি? যেখানে “কে মালিক” সিদ্ধান্তটি নিখুঁত হতে হবে (shard-এর মালিক, leader), আর সদস্য কয়েকশো থেকে কয়েক হাজার, সেখানে কেন্দ্রীয় তালিকা সাধারণত সেরা। হাজার হাজার সদস্য, বা শুধু routing-এর জন্য “কে সম্ভবত বেঁচে আছে” জানলেই চলে — সেখানে gossip আর SWIM.
৬ · Gossip — খবর যেভাবে গ্রামে ছড়ায়
গ্রামে একটি বিয়ের খবর কীভাবে ছড়ায়? কেউ মাইকে ঘোষণা দেয় না। চায়ের দোকানে একজন দুজনকে বলেন; তাঁরা বিকেলে হাটে আরও কয়েকজনকে; পরদিন সকালের মধ্যে প্রায় সবাই জানেন। কেউ পুরো গ্রামের দায়িত্ব নেননি, কেউ একা অনেককে বলেননি — তবু খবর দ্রুত সবার কাছে, আর দু-একজন বাড়িতে না থাকলেও খবর থামে না। Computer science-এ এর নাম gossip বা epidemic protocol — রোগ যেমন ছড়ায়।
১৯৮৭ সালে Xerox PARC-এর Alan Demers ও সহকর্মীরা “Epidemic Algorithms for Replicated Database Maintenance” paper-এ তাঁদের Clearinghouse নামের distributed database-এর কপিগুলো মেলাতে এই ধারণা ব্যবহার করেন। তাঁরা anti-entropy আর rumor mongering আলাদা করেন, আর মুছে ফেলা ডেটা যেন ফিরে না আসে তার জন্য “death certificate” — আজকের tombstone-এর পূর্বসূরি। একই বছর Boris Pittel গাণিতিকভাবে দেখান, push gossip-এ একটি গুজব n জনের কাছে পৌঁছাতে প্রায় log₂ n + ln n রাউন্ড লাগে।
প্রতি রাউন্ডে (ধরা যাক প্রতি সেকেন্ডে) প্রত্যেক সদস্য এলোমেলোভাবে fanout f জন সঙ্গী বাছে। তিনটি রূপ:
- Push: যে জানে, সে সঙ্গীকে বলে। শুরুতে দ্রুত, শেষে ধীর — শেষের দিকে প্রায় সবাই আগেই জানে, তাই বেশিরভাগ বার্তা অপচয়।
- Pull: প্রত্যেকে সঙ্গীকে জিজ্ঞেস করে “নতুন কী?” শুরুতে ধীর (জানে প্রায় কেউই না), শেষে খুব দ্রুত — যে না জানে সে নিজেই খোঁজে।
- Push-pull: দুজন দেখা হলে দুজনেই নিজের খবর দেয়-নেয়। দুই পর্যায়েই ভালো, তাই বাস্তবে বেশি প্রচলিত।
নিচের stepper-এ ৮ জন সদস্য (A থেকে H), push gossip, fanout ২. ● মানে খবর জানে, ○ মানে জানে না। প্রতিটি রাউন্ডে যারা জানে, তারা দুজন করে এলোমেলো সঙ্গীকে বলে।
| রাউন্ড | |
| সদস্যরা | |
| এই রাউন্ডের বার্তা | |
| নতুন জানল | |
| অপচয় (আগেই জানত) |
- রাউন্ড ০ — উৎস। শুধু A খবরটি জানে — “নতুন সদস্য X যোগ দিয়েছে।” কোনো কেন্দ্রীয় ঘোষক নেই; A নিজেও সবাইকে একা বলবে না।
- রাউন্ড ১ — দ্বিগুণের বেশি। A এলোমেলোভাবে C আর F-কে বলল। দুটি বার্তাই কাজে লাগল, অপচয় ০; এখন ৩ জন জানে। শুরুর দিকে প্রায় সবাই অজানা, তাই প্রতিটি বার্তাই নতুন কাউকে পায় — জানার সংখ্যা প্রতি রাউন্ডে প্রায় (১ + f) গুণ বাড়ে।
- রাউন্ড ২ — প্রথম অপচয়। A, C, F মিলে ৬টি বার্তা: B, H, D নতুন জানল; কিন্তু A→F, C→A, F→C — তিনজনই আগে থেকে জানত। এখন ৬ জন।
- রাউন্ড ৩ — শেষ দুজন। ৬ জন × ২ = ১২টি বার্তা; E আর G নতুন জানল (A→E, B→G), বাকি ১০টি অপচয়। ৩ রাউন্ডে ৮ জনই জানে — log₂ ৮ = ৩. শেষ পর্যায়ে বার্তার বেশিরভাগ নষ্ট, এটাই push-এর দুর্বলতা।
- মোট হিসাব। ২০টি বার্তায় ৭ জন নতুন জানল, ১৩টি অপচয়। Rumor mongering-এ তাই একটি থামার নিয়ম থাকে: বারবার “আগেই জানি” শুনলে সদস্যটি খবরটিকে আর “গরম” ভাবে না, বলা বন্ধ করে। এতে খরচ কমে, কিন্তু দুর্ভাগ্যক্রমে কেউ বাদ পড়ে যেতে পারে — সেই ফাঁক পূরণ করে ধীর anti-entropy.
কেন O(log n) রাউন্ড — প্রমাণের ধারণা
Push, fanout ১ ধরে দুই পর্যায়ে ভাবুন। বৃদ্ধির পর্যায়: যতক্ষণ জানে এমন সদস্য অর্ধেকের কম, প্রতিটি বার্তা বেশিরভাগ সময় অজানা কাউকে পায়, তাই জানার সংখ্যা প্রতি রাউন্ডে প্রায় দ্বিগুণ: ১, ২, ৪, ৮ … — n/2-তে পৌঁছাতে প্রায় log₂ n রাউন্ড। শেষ পর্যায়: ধরুন এখন অজানা অংশ s. একটি নির্দিষ্ট অজানা সদস্যকে প্রতিটি বার্তা ১/n সম্ভাবনায় বেছে নেয়; প্রায় n জন পাঠাচ্ছে, তাই সে কারও বার্তা না পাওয়ার সম্ভাবনা (1 − 1/n)n ≈ 1/e. অর্থাৎ প্রতি রাউন্ডে অজানা অংশ e গুণ কমে: s → s/e. n/2 অজানা থেকে ১-এর নিচে নামতে লাগে প্রায় ln n রাউন্ড। মোট ≈ log₂ n + ln n — Pittel-এর ফলাফল।
Fanout f হলে বৃদ্ধির পর্যায়ে গুণক (১ + f), আর শেষ পর্যায়ে না-পাওয়ার সম্ভাবনা ≈ e−f, তাই ≈ log(1+f) n + (ln n)/f. Push-pull-এ শেষ পর্যায় আরও নাটকীয়: অজানা সদস্য নিজে কাউকে জিজ্ঞেস করে, আর সেই সঙ্গীও অজানা হওয়ার সম্ভাবনা s, ফলে s → s² — প্রতি রাউন্ডে অজানা অংশ বর্গ হয়ে কমে। Karp ও সহকর্মীদের ২০০০ সালের একটি গবেষণা দেখায়, push-pull-এ O(log n) রাউন্ডে মোট মাত্র O(n log log n) বার্তায় খবর সবার কাছে পৌঁছানো যায়।
এবার simulator-এ মিলিয়ে দেখুন — ১০০, ১,০০০ আর ১০,০০০ সদস্য, fanout ১ ও ৩, প্রতিটি পাঁচবার চালিয়ে গড়:
// Push gossip: each round every informed node tells `f` random others.
// theory (push, fanout f): log_(f+1)(n) + ln(n)/f rounds; f = 1 gives log2(n) + ln(n)
let seed = 44; function rand() { // mulberry32: seeded PRNG, same output every run
seed = (seed + 0x6D2B79F5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
function spread(n, f) { // returns [rounds until all know, total messages]
const know = new Uint8Array(n); know[0] = 1;
let informed = 1, rounds = 0, msgs = 0;
while (informed < n) {
rounds++;
const senders = [];
for (let i = 0; i < n; i++) if (know[i]) senders.push(i);
for (const s of senders) for (let j = 0; j < f; j++) {
let t = Math.floor(rand() * (n - 1)); if (t >= s) t++; // anyone except self
msgs++; if (!know[t]) { know[t] = 1; informed++; }
}
}
return [rounds, msgs];
}
console.log(" n f rounds(avg of 5) log2(n) theory msgs/node");
for (const n of [100, 1000, 10000]) for (const f of [1, 3]) {
let r = 0, m = 0;
for (let trial = 0; trial < 5; trial++) { const [a, b] = spread(n, f); r += a; m += b; }
const l2 = Math.log2(n), th = Math.log(n) / Math.log(f + 1) + Math.log(n) / f;
console.log(String(n).padStart(6) + " " + f + " " + (r / 5).toFixed(1).padStart(15) +
" " + l2.toFixed(1).padStart(7) + " " + th.toFixed(1).padStart(6) +
" " + (m / 5 / n).toFixed(1).padStart(9));
}আউটপুট:
n f rounds(avg of 5) log2(n) theory msgs/node
100 1 12.2 6.6 11.2 5.5
100 3 5.4 6.6 4.9 5.8
1000 1 17.4 10.0 16.9 7.4
1000 3 8.4 10.0 7.3 9.6
10000 1 22.2 13.3 22.5 8.8
10000 3 10.8 13.3 9.7 11.9
লগারিদমের জাদু: সদস্য ১০০ গুণ বেড়ে ১০০ থেকে ১০,০০০ হলো, অথচ fanout ১-এ রাউন্ড ১২.২ থেকে মাত্র ২২.২. শুধু log₂ n যথেষ্ট নয় — শেষ পর্যায়ের ln n যোগ করা “theory” কলাম (১১.২, ১৬.৯, ২২.৫) অনেক কাছাকাছি। Fanout ৩: রাউন্ড প্রায় অর্ধেক (১০,০০০-এ ১০.৮), কিন্তু প্রতি সদস্যের বার্তা বেশি (৮.৮ থেকে ১১.৯)। এখানে simulation তত্ত্বের চেয়ে আধা থেকে এক রাউন্ড বেশি নেয়, কারণ সূত্রটি বড় n-এর আনুমানিক হিসাব। প্রতি সদস্যের মোট বার্তা মাত্র প্রায় ৫.৫ থেকে ১২টি।
Rumor mongering বনাম anti-entropy
| Rumor mongering | Anti-entropy | |
|---|---|---|
| কী পাঠায় | শুধু নতুন, “গরম” খবর | পুরো অবস্থা তুলনা করে যা অমিল তা |
| কখন থামে | খবরটি পুরোনো লাগলে (counter বা সম্ভাবনা দিয়ে) | কখনো না — নিয়মিত চলতেই থাকে |
| খরচ | কম | বেশি; Merkle tree দিয়ে কমানো যায় |
| নিশ্চয়তা | সম্ভাবনাভিত্তিক — অল্প কেউ বাদ পড়তে পারে | শেষ পর্যন্ত সবাই মিলবেই |
| বাস্তবে | membership-এর বদল দ্রুত ছড়ানো | replica মেলানো (পর্ব ৪১), membership-এর পূর্ণ তালিকা মাঝে মাঝে মেলানো |
সাধারণ নকশায় দুটিই থাকে: দ্রুত খবরে rumor mongering, নিরাপত্তা-জাল হিসেবে ধীর anti-entropy. ফল eventual consistency (পর্ব ৪০): বদল থামলে সবার তালিকা মিলে যায়।
৭ · Gossip-ভিত্তিক membership — heartbeat counter-এর তালিকা মেলানো
Gossip দিয়ে failure detection কীভাবে? ১৯৯৮ সালে Robbert van Renesse, Yaron Minsky আর Mark Hayden একটি সহজ নকশা প্রকাশ করেন। প্রত্যেক সদস্য একটি তালিকা রাখে: প্রতিটি সদস্যের একটি heartbeat counter, আর নিজের ঘড়িতে শেষ কখন সেটি বেড়েছিল। নিয়ম চারটি:
- প্রতি gossip interval-এ নিজের counter এক বাড়ানো, তারপর পুরো তালিকা এলোমেলো একজনকে পাঠানো।
- কারও তালিকা এলে প্রতিটি সদস্যের জন্য বড় counter-টি রাখা; বাড়লে নিজের ঘড়ির সময় লিখে রাখা।
- কারও counter Tfail সময় ধরে না বাড়লে তাকে failed চিহ্নিত করা।
- আরও কিছুক্ষণ, Tcleanup পর্যন্ত, failed entry রেখে দেওয়া, তারপর মুছে ফেলা।
খেয়াল করুন, কেউ কাউকে সরাসরি ping করে না। একজনের counter বাড়ার খবর gossip-এ ছড়ায়; থেমে যাওয়া counter-ই মৃত্যুর চিহ্ন। আর “বড়টি রাখা” নিয়মটি ক্রম-নিরপেক্ষ ও পুনরাবৃত্তি-নিরাপদ — একই তালিকা দুবার এলে বা উল্টো ক্রমে এলেও ফল এক। পর্ব ৪৬-এ দেখবেন, এই ধর্মগুলোই CRDT-র ভিত্তি। নিচে A-এর চোখে তালিকা দেখুন; Tfail = ১০ s, Tcleanup = ২০ s, আর প্রত্যেকে প্রতি সেকেন্ডে নিজের counter এক বাড়ায়।
| A-এর ঘড়ি | |
| A (নিজে) | |
| B | |
| C | |
| D | |
| ঘটনা |
- t = ১০০ — A-এর তালিকা। A নিজের counter ২০ করেছে। B-কে শেষ বাড়তে দেখেছে ৯৮-এ, C-কে ৯৯-এ, D-কে ৯০-এ। D দশ সেকেন্ড ধরে চুপ — সন্দেহজনক, কিন্তু সীমা এখনো পেরোয়নি।
- B-র gossip এল — বড়টি রাখা। B পাঠাল A ১৯, B ১৭, C ১১, D ৩১. A-র নিজের ২০ বড়, তাই বদলায় না; B ১৫ থেকে ১৭ আর C ৯ থেকে ১১ — দুটিই বাড়ল, তাই সময় লেখা হলো t ১০০. D ৩১-ই রইল, B-ও নতুন কিছু জানে না, তাই সময় ৯০-ই থাকে।
- t = ১০১ — D failed. D-এর counter ১০১ − ৯০ = ১১ s ধরে বাড়েনি, Tfail ১০ s পেরিয়ে গেছে। A নিজে, কারও সাথে পরামর্শ না করেই, D-কে failed ধরে। অন্য সদস্যরা একটু আগে বা পরে একই সিদ্ধান্তে আসবে — তালিকাগুলো হুবহু একসাথে বদলায় না।
- t = ১০৫ — পুরোনো খবর এল। C-র তালিকায় B ১৬ (A-এর ১৭-এর চেয়ে ছোট, বাতিল), C ১৭ (বাড়ল), আর D ৩১. D-এর ৩১ নতুন নয়, তাই D failed-ই থাকে। ভাবুন, A যদি ১০১-এই D-কে মুছে ফেলত — তাহলে এই “D ৩১” দেখে তাকে নতুন সদস্য ভেবে আবার যোগ করত! এই জন্যই Tcleanup পর্যন্ত failed entry রেখে দেওয়া — মুছে ফেলা ডেটার tombstone-এর মতো।
- t = ১১১ — cleanup. D-এর শেষ বৃদ্ধি থেকে ২১ s পেরিয়েছে, Tcleanup ২০ s-এর বেশি; এতক্ষণে D-এর পুরোনো counter-এর খবর সবার তালিকা থেকে ঝরে যাওয়ার কথা, তাই A entry-টি মুছে দিল। এর মধ্যে B ২৭ আর C ২২-এর খবরও এসেছে।
- t = ১৩০ — D ফিরল। D restart করল; তার counter আবার ১ থেকে শুরু। কোনো সদস্য যদি তখনো “D ৩১” মনে রাখে, শুধু counter দেখে ১ < ৩১ বলে নতুন D-কে উপেক্ষা করবে! সমাধান: counter-এর সাথে একটি generation নম্বর (যেমন process চালুর সময়) — তুলনা হয় (generation, counter) জোড়ায়, আর gen ২ যেকোনো gen ১-এর চেয়ে নতুন।
এই নকশার দুটি দুর্বলতা আছে। প্রথমত, ধরার সময় n-এর সাথে বাড়ে: counter বাড়ার খবর সবার কাছে পৌঁছাতেই O(log n) রাউন্ড লাগে, তাই Tfail-কেও সেই অনুপাতে বড় রাখতে হয়। দ্বিতীয়ত, বার্তার আকার O(n): প্রতিটি gossip-এ পুরো তালিকা যায়, তাই cluster-জুড়ে প্রতি রাউন্ডে বাইট O(n²)। বাস্তবে তাই শুধু বদল বা digest পাঠানো হয়।
প্রকাশিত নকশা অনুযায়ী Amazon-এর Dynamo (২০০৭) gossip দিয়ে membership আর ring-এর মালিকানার খবর ছড়াত, আর বিচ্ছিন্ন দলগুলো যাতে চিরকাল আলাদা না থাকে সেজন্য কয়েকটি পরিচিত seed node রাখত। Cassandra-র ডকুমেন্টেশন অনুযায়ী তার gossip প্রতি সেকেন্ডে সর্বোচ্চ তিনজনের সাথে অবস্থা বিনিময় করে, entry-তে generation আর version রাখে, আর মৃত্যু বিচার করে phi accrual দিয়ে। Redis Cluster-ও নিজের cluster bus-এ gossip-ধাঁচে ping-pong বিনিময় করে (পর্ব ৩০); সংস্করণভেদে বিবরণ বদলায়।
৮ · SWIM — ping, ping-req, সন্দেহ আর incarnation
২০০২ সালে Cornell-এর Abhinandan Das, Indranil Gupta আর Ashish Motivala প্রকাশ করেন SWIM (Scalable Weakly-consistent Infection-style process group Membership). এর মূল চিন্তা: failure detection আর dissemination আলাদা করা, আর দুটিকেই প্রতি সদস্যের জন্য n-নিরপেক্ষ খরচে চালানো।
Failure detection. প্রতিটি protocol period-এ (ধরা যাক ১ s) সদস্য Mi একজন সদস্য Mj বাছে আর তাকে ping পাঠায়। একটি ছোট timeout-এর (round-trip সময়ের ভিত্তিতে, period-এর চেয়ে অনেক কম) মধ্যে ack না এলে Mi এলোমেলো k জন সদস্যকে ping-req(Mj) পাঠায়, অর্থাৎ অনুরোধ করে যেন তারা Mj-কে ping করে ফল জানায়। তারা Mj-কে ping করে, আর ack পেলে Mi-কে ফেরত পাঠায়। Period শেষেও কোনো ack না এলে Mi সিদ্ধান্ত নেয়। এটিই indirect probe.
SWIM-এর একটি probe — সরাসরি ব্যর্থ, তারপর k = ৩ জনের মাধ্যমে
Direct ping lost; ping-req via three helpers; one ack path returns
গ্রামের বাড়িতে মাকে ফোন করছেন, ধরছেন না। সঙ্গে সঙ্গে খারাপ কিছু ভাবেন না — হয়তো আপনার নেটওয়ার্কে সমস্যা, হয়তো ফোন চার্জে। তাই পাশের বাড়ির চাচি, মামাতো ভাই আর দোকানদার চাচাকে ফোন করে বলেন “একটু খোঁজ নিয়ে জানাবেন?” একজনও “ভালো আছেন” বললেই নিশ্চিন্ত; কারও মাধ্যমেই খবর না এলে তবেই সত্যিকারের চিন্তা। SWIM-এর ping-req ঠিক এই কাজ করে: এক পথের দুর্ভাগ্যকে মৃত্যু ভেবে ভুল না করা।
লক্ষ্য বাছাই. মূল paper দেখায়, প্রতি period-এ এলোমেলো লক্ষ্য বাছলে একটি crash প্রথমবার কারও চোখে পড়ার প্রত্যাশিত সময় প্রায় e/(e − 1) ≈ ১.৫৮ period — n যত বড়ই হোক। তবে এলোমেলো বাছাইয়ে কেউ অনেকক্ষণ বাদ পড়তে পারে; তাই paper-এর উন্নত রূপে তালিকা এলোমেলো সাজিয়ে round-robin-এ ping করা হয়, ফলে সবচেয়ে খারাপ ক্ষেত্রেও 2n − 1 period-এর মধ্যে ধরা পড়ে।
Suspicion আর incarnation — সঙ্গে সঙ্গে মৃত নয়
মূল SWIM-এর একটি বাড়তি অংশ ভুল সন্দেহকে আরও কমায়। ping-req-ও ব্যর্থ হলে Mi সরাসরি “মৃত” না বলে Mj-কে suspect চিহ্নিত করে, আর সেই খবর ছড়ায়। Suspect সদস্য তখনো তালিকায় থাকে। নির্দিষ্ট suspicion timeout-এর মধ্যে খণ্ডন না এলে তবেই “confirm” — মৃত। খণ্ডন করতে পারে শুধু Mj নিজে: নিজের সম্পর্কে সন্দেহ শুনলে সে নিজের incarnation number বাড়িয়ে ঘোষণা দেয় “আমি বেঁচে আছি, #৫”. নিয়মগুলো (একই সদস্য Mj-এর বার্তার মধ্যে):
- alive #x আগের suspect #y বা alive #y-কে বাতিল করে, যদি x > y.
- suspect #x আগের suspect #y-কে বাতিল করে, যদি x > y; আর alive #y-কে, যদি x ≥ y — সমান নম্বরে সন্দেহ জেতে।
- confirm (মৃত) সব কিছুকে বাতিল করে; incarnation নম্বর বাড়াতে পারে শুধু সদস্যটি নিজে।
| সময় | |
| বার্তা | |
| M_i-এর তালিকায় M_j | |
| অন্যদের তালিকায় M_j |
- t = ০ — ping. এই period-এ Mi-এর এলোমেলো সাজানো তালিকায় পরের নাম Mj, তাই তাকে ping (ক্রম ৪১)। সবার তালিকায় Mj এখন alive, incarnation #৪.
- t = ২০০ ms — ack আসেনি। ping timeout পেরোল। Mi জানে না কোনটি ঘটেছে: ping হারাল, ack হারাল, Mj ব্যস্ত, না মৃত। তাই এখনই কোনো রায় নয়; তালিকায় Mj এখনো alive #৪.
- ping-req — তিন চোখে দেখা। Mi এলোমেলো তিনজন Ma, Mb, Mc-কে ping-req পাঠাল। তারা নিজেরা Mj-কে ping করবে, ack পেলে Mi-কে জানাবে। তিনটি স্বাধীন network পথ — একটিতেও ack এলেই Mj জীবিত।
- t = ১ s — suspect. Period শেষ, কোনো পথেই ack নেই। Mi Mj-কে মৃত বলে না, বলে suspect #৪, আর এই খবর পরের ping-গুলোর পিঠে চড়ে ছড়ায়। অন্যরাও এখন Mj-কে suspect #৪ দেখে, কিন্তু তালিকা থেকে বাদ দেয় না।
- খণ্ডন — incarnation #৫. Mj আসলে জীবিত ছিল (হয়তো কয়েকশো ms আটকে ছিল)। কোনো ping-এর পিঠে নিজের সম্পর্কে “suspect #৪” শুনে সে নিজের incarnation ৫ করল আর “alive #৫” ছড়াল। নিয়ম অনুযায়ী alive #৫ > suspect #৪, তাই সবাই আবার alive #৫ — ভুল সন্দেহ মুছে গেল, কোনো failover হয়নি।
- বিকল্প — confirm. যদি Mj সত্যিই মৃত হতো, ৫ s suspicion timeout-এ #৪-এর বড় কোনো alive আসত না। তখন Mi “confirm Mj” ছড়ায়, যা সব নিয়মের ওপরে; Mj তালিকা থেকে বাদ, তার key অন্যরা নেয়। Mj পরে ফিরলে তাকে নতুন করে join করতে হয়।
Dissemination — ping-এর পিঠে চড়ে. SWIM join, leave, suspect, alive, confirm-এর খবর আলাদা বার্তায় পাঠায় না; প্রতিটি ping, ping-req আর ack-এর ভেতরে সাম্প্রতিক কয়েকটি বদল জুড়ে দেয় — একে বলে piggybacking. প্রতিটি বদল প্রায় λ log n বার পিঠে চড়ে, তারপর বাদ পড়ে; যেহেতু প্রতি period-এ প্রত্যেকে কাউকে ping করছেই, খবর gossip-এর মতো O(log n) period-এ ছড়ায়, বাড়তি বার্তা ছাড়াই।
এবার সংখ্যায় দেখি ping-req কতটা কাজের। ধরে নিচ্ছি Mj জীবিত, প্রতিটি বার্তা স্বাধীনভাবে p সম্ভাবনায় হারায়। সরাসরি পথে দুটি বার্তা (ping, ack), আর প্রতিটি পরোক্ষ পথে চারটি (ping-req, ping, ack, ack). তাই একটি ভুল সন্দেহের সম্ভাবনা
// SWIM probe of an ALIVE member M_j: every message is lost with probability p.
let seed = 2002; function rand() { // mulberry32: seeded PRNG
seed = (seed + 0x6D2B79F5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
const ok = p => rand() >= p; // one message arrives?
function probe(p, k) { // true = M_i wrongly suspects M_j
if (ok(p) && ok(p)) return false; // ping -> M_j, ack -> M_i
for (let i = 0; i < k; i++) // ping-req via helper i: 4 messages in a row
if (ok(p) && ok(p) && ok(p) && ok(p)) return false;
return true;
}
const N = 200000;
console.log("loss k simulated formula (false suspicions per probe)");
for (const p of [0.05, 0.20]) for (const k of [0, 3]) {
let bad = 0; for (let i = 0; i < N; i++) if (probe(p, k)) bad++;
const f = (1 - (1 - p) ** 2) * (1 - (1 - p) ** 4) ** k;
console.log((p * 100 + "%").padEnd(5) + " " + k + " " + (100 * bad / N).toFixed(3).padStart(8) +
"% " + (100 * f).toFixed(3).padStart(6) + "%");
}আউটপুট:
loss k simulated formula (false suspicions per probe)
5% 0 9.736% 9.750%
5% 3 0.060% 0.062%
20% 0 36.008% 36.000%
20% 3 7.428% 7.409%
৫% loss-এ শুধু সরাসরি ping-এ বিচার করলে প্রায় ১০% probe-এ জীবিত সদস্যকে সন্দেহ (৯.৭৫%); তিনজন সাহায্যকারী যোগ করলে ০.০৬২% — প্রায় ১৫৭ গুণ কম। ২০% loss-এর খুব খারাপ network-এও ৩৬% থেকে ৭.৪%. ২,০০,০০০ বার simulation আর সূত্র প্রায় হুবহু মিলেছে। তবু ৭.৪% অনেক — এজন্যই suspicion. আর অনুমানটি মনে রাখুন: হারানো স্বাধীন. সমস্যা Mi-এর নিজের network card বা ব্যস্ত CPU-তে হলে সব পথ একসাথে ব্যর্থ হয়, তখন ping-req কাজে আসে না (নিচে Lifeguard).
খরচের তুলনা — all-to-all, gossip আর SWIM
শেষে তিন নকশার খরচ পাশাপাশি। ধরা যাক প্রতি সেকেন্ডে একটি রাউন্ড; প্রতিটি বার্তার header ৬০ byte আর প্রতিটি সদস্যের entry ৪০ byte; gossip প্রতি রাউন্ডে পুরো তালিকা একজনকে পাঠায়; SWIM-এর প্রতিটি বার্তায় সর্বোচ্চ ৬টি বদল piggyback হয়, আর loss-এর বাড়তি ping-req এখানে ধরা হয়নি।
// Messages and bytes per second for failure detection, one round per second.
// Assumed sizes: header 60 B, one member entry 40 B,
// gossip sends the full list to f = 1 peer, SWIM piggybacks up to 6 updates.
const H = 60, E = 40;
const fmt = x => x >= 1e6 ? (x / 1e6).toFixed(1) + " M" : x >= 1e3 ? (x / 1e3).toFixed(1) + " K" : x.toFixed(0) + " ";
console.log(" n scheme msgs/s per node bytes/s");
for (const n of [10, 100, 1000]) {
const rows = [
["all-to-all heartbeat", n * (n - 1), H + E],
["gossip (full list)", n * 1, H + E * n],
["SWIM ping+ack", n * 2, H + E * 6],
];
for (const [name, msgs, size] of rows)
console.log(String(n).padStart(5) + " " + name.padEnd(21) + fmt(msgs).padStart(8) +
String((msgs / n).toFixed(0)).padStart(9) + " " + fmt(msgs * size).padStart(8));
}আউটপুট:
n scheme msgs/s per node bytes/s
10 all-to-all heartbeat 90 9 9.0 K
10 gossip (full list) 10 1 4.6 K
10 SWIM ping+ack 20 2 6.0 K
100 all-to-all heartbeat 9.9 K 99 990.0 K
100 gossip (full list) 100 1 406.0 K
100 SWIM ping+ack 200 2 60.0 K
1000 all-to-all heartbeat 999.0 K 999 99.9 M
1000 gossip (full list) 1.0 K 1 40.1 M
1000 SWIM ping+ack 2.0 K 2 600.0 K
১০ জনে তিনটিই সস্তা — ছোট cluster-এ all-to-all চলে। ১,০০০ জনে ছবি বদলে যায়: all-to-all-এ প্রতি সেকেন্ডে প্রায় ১০ লাখ বার্তা আর প্রায় ১০০ MB; পুরো-তালিকার gossip-এ বার্তা মাত্র ১,০০০টি, কিন্তু প্রতিটি ৪০ KB-এর বেশি, তাই প্রায় ৪০ MB — বার্তা O(n), বাইট O(n²)। SWIM-এ প্রতি সদস্যের বার্তা স্থির ২টি, আকারও স্থির: মোট প্রায় ৬০০ KB, সব দিক থেকে O(n).
Lifeguard — নিজের অসুস্থতাও টের পাওয়া
HashiCorp-এর open-source Go library memberlist SWIM-এর একটি বহুল ব্যবহৃত রূপ; প্রকাশিত ডকুমেন্টেশন অনুযায়ী Consul, Serf আর Nomad এর ওপর দাঁড়ানো। বাস্তবে তাঁরা দেখেন, অনেক false positive-এর উৎস পর্যবেক্ষক নিজে: Mi-এর CPU ব্যস্ত, তাই সে ack সময়মতো পড়তে না পেরে অন্যদের সন্দেহ করে। ২০১৭–১৮ সালের দিকে প্রকাশিত তাঁদের Lifeguard নামের উন্নয়নে তিনটি ধারণা:
- নিজের স্বাস্থ্য মাপা: নিজের probe বারবার ব্যর্থ হলে বা নিজের সম্পর্কে সন্দেহ শুনলে সদস্যটি নিজের timeout বাড়ায়।
- বদলানো suspicion timeout: শুরু লম্বা timeout দিয়ে; অন্য স্বাধীন সদস্যরাও একই সন্দেহ জানালে তা ছোট হয় — একজনের সন্দেহে ধীরে, অনেকের সন্দেহে দ্রুত।
- সন্দেহভাজনকে সরাসরি জানানো: suspect সদস্যের ping-এই সন্দেহের খবর জুড়ে দেওয়া, যাতে সে দ্রুত খণ্ডন করে।
memberlist মাঝে মাঝে TCP-তে দুই সদস্যের পূর্ণ অবস্থাও মিলিয়ে নেয় — piggyback-এর সাথে anti-entropy-র নিরাপত্তা-জাল। বিবরণ সংস্করণভেদে আলাদা।
৯ · Split brain, membership ≠ consensus আর বাস্তবের knob
ধরুন ৫টি সদস্যের cluster: A, B, C এক switch-এর নিচে, D, E আরেকটির নিচে, আর মাঝের link কেটে গেল — network partition. SWIM বা gossip দুই পাশেই ঠিকঠাক কাজ করবে: {A, B, C} বলবে D, E মৃত; {D, E} বলবে A, B, C মৃত। দুই পাশের তালিকাই নিজের চোখে সঠিক. এখন প্রত্যেক পাশ যদি নিজের তালিকা দেখে একজন leader বানায়, বা মৃতদের shard নিজের নামে লিখে নেয় — একই shard-এর দুজন মালিক, দুই দিকে আলাদা লেখা। এটাই split brain.
Membership বলে “কে সম্ভবত বেঁচে আছে”; consensus বলে “আমরা সবাই মিলে কী ঠিক করলাম।” Membership-এর তালিকা দুর্বলভাবে মেলানো (weakly consistent) — দুজন সদস্য একই মুহূর্তে আলাদা তালিকা দেখতে পারে, আর partition-এ দুই পাশ চিরকাল আলাদা থাকতে পারে। তাই এটি routing-এর ইঙ্গিত হিসেবে ব্যবহার করুন — কোথায় অনুরোধ পাঠাব, কাকে এড়াব। কিন্তু যে সিদ্ধান্ত একজনেরই হতে হবে (leader, lock, shard-এর মালিক), তার জন্য লাগে majority quorum-ভিত্তিক consensus (পর্ব ৪২) অথবা coordination service-এর lease আর fencing token (পর্ব ৪৩)।
বাস্তবের নকশাও এই ভাগ মানে। প্রকাশিত ডকুমেন্টেশন অনুযায়ী Consul-এ সব agent gossip-এ (memberlist) membership আর স্বাস্থ্যের খবর ছড়ায়, কিন্তু service catalog-এর মতো সিদ্ধান্ত রাখে কয়েকটি server-এর Raft cluster. Redis Cluster-এ একজন node কাউকে সাড়া না পেলে শুধু নিজের কাছে “সম্ভবত বিকল” (PFAIL) চিহ্ন দেয়; master-দের majority একমত হলে তবেই FAIL, তারপর failover. Elasticsearch-এর পুরোনো সংস্করণে master-এর ন্যূনতম সংখ্যা হাতে ঠিক করতে হতো, আর ভুল মানে split brain; ডকুমেন্টেশন অনুযায়ী সংস্করণ ৭ থেকে cluster নিজেই quorum সামলায়।
বাস্তবের knob — কোনটি বাড়ালে কী হয়
| Knob | বাড়ালে লাভ | বাড়ালে দাম |
|---|---|---|
| Gossip interval / protocol period | কম বার্তা, কম CPU | ধরা আর খবর ছড়ানো ধীর |
| Fanout f | কম রাউন্ডে ছড়ায়, হারানো বার্তায় বেশি সহনশীল | প্রতি রাউন্ডে বেশি বার্তা, বেশি অপচয় |
| Indirect probe সংখ্যা k | lossy পথে false positive সূচকীয় হারে কমে | সন্দেহের মুহূর্তে বাড়তি 4k পর্যন্ত বার্তা; পর্যবেক্ষকের নিজের সমস্যায় লাভ নেই |
| Ping timeout | ধীর পথ বা দূরের region-এ কম ভুল | period-এর ভেতরে ping-req-এর সময় কমে |
| Suspicion timeout | GC pause বা ক্ষণিক ব্যস্ততায় খণ্ডনের সময় মেলে | সত্যিকারের মৃত্যু ঘোষণা দেরিতে; সাধারণত log n-এর সাথে বাড়ানো হয় |
| Phi threshold | কম false positive | ধীরে ধরা; মডেল ভুল হলে দুটিই খারাপ |
দুর্বল link-এর কথা: ধরা যাক একটি কাল্পনিক সেবার কিছু branch-office বা edge যন্ত্র মোবাইল নেটওয়ার্ক বা দুর্বল লিংকে যুক্ত, যেখানে loss আর jitter data center-এর চেয়ে অনেক বেশি হতে পারে। Data center-এর timeout সেখানে হুবহু চাপালে অবিরাম flapping হবে। সাধারণ পথ: এমন সদস্যদের আলাদা দলে বা আলাদা knob-এ রাখা, লম্বা suspicion timeout, আর “বিকল” ধরার পরের পদক্ষেপ ধীরে ও ধাপে ধাপে করা। ঠিক সংখ্যা আসে নিজের network মেপে, অনুমান থেকে নয়।
আরও গভীরে: Chandra ও Toueg (১৯৯৬)-এর পরপরই Chandra, Hadzilacos ও Toueg দেখান, crash-এর মডেলে consensus সমাধানের জন্য সবচেয়ে দুর্বল যে detector যথেষ্ট, তা হলো “eventually weak” ধরনের (◇W). বাস্তবের বড় অভিজ্ঞতা থেকে এসেছে “gray failure”-এর ধারণা (Huang ও সহকর্মীরা, HotOS ২০১৭): একটি যন্ত্র heartbeat-এর চোখে সুস্থ, কিন্তু ব্যবহারকারীর চোখে অসুস্থ — যেমন ডিস্ক ধীর, বা শুধু কিছু অনুরোধ ব্যর্থ।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন আসতে পারে:
- Failure detector-এর completeness আর accuracy কী, নিখুঁত detector কেন অসম্ভব? মৃতকে শেষ পর্যন্ত সন্দেহ আর জীবিতকে কখনো না; asynchronous network-এ ধীর আর মৃত আলাদা করা যায় না।
- n = ১,০০০-এ all-to-all heartbeat-এর বার্তা কত? n(n − 1) = ৯,৯৯,০০০ প্রতি interval; SWIM-এ প্রায় 2n = ২,০০০.
- Push gossip-এ কেন প্রায় log₂ n + ln n রাউন্ড? দ্বিগুণ হওয়ার পর্যায় log₂ n, তারপর অজানা অংশ প্রতি রাউন্ডে e গুণ কমে, ln n.
- SWIM-এ ping-req আর incarnation কেন? এক পথের loss-কে মৃত্যু ভাবা এড়াতে k পরোক্ষ পথ; ভুল সন্দেহ খণ্ডনে শুধু সদস্য নিজে নম্বর বাড়ায়, বড় নম্বর জেতে।
- Gossip membership দিয়ে কি leader বাছা নিরাপদ? না — partition-এ দুই পাশ আলাদা তালিকা দেখে; majority consensus বা lease ও fencing লাগে।
১০ · AI-এর যুগে — membership নকশা যাচাই
Failure detection-এর নকশা চাইলে AI প্রায়ই চটপটে উত্তর দেয়: “প্রতি ১০০ ms-এ heartbeat, একটি মিস হলেই node বাদ, আর Redis-এ সবার তালিকা।” এক নজরে তিনটি সমস্যা: একটি মিসেই বাদ মানে flapping; সবাই সবাইকে মানে n বাড়লে বিস্ফোরণ; আর Redis-এর নিজের বিকলতা কে দেখবে? খসড়া AI দেয়, failure mode আর সংখ্যা যাচাই আপনার।
| AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|
| “Gossip দিয়ে leader election” বা “gossip strong consistency দেয়” | Partition-এ দুই পাশ কী দেখবে, লিখে দেখুন; membership ≠ consensus |
| All-to-all heartbeat-এর খরচ উপেক্ষা | n(n − 1) হিসাব করুন — ৫০০ জনে প্রতি interval-এ ২,৪৯,৫০০ |
| log n-এর হিসাবে ভুল (যেমন “১,০০০ node-এ ১০ রাউন্ডেই সবাই”) | log₂ n-এর সাথে ln n যোগ করুন, আর simulation চালান |
| “phi = 8 মানে ৮ সেকেন্ড” ধরনের ব্যাখ্যা | φ = −log₁₀ P; সময় নয়, সম্ভাবনার লগারিদম |
| পণ্যের ডিফল্ট মান বা সীমা বানিয়ে বলা | অফিসিয়াল ডকুমেন্টেশন আর আপনার সংস্করণের config দেখুন |
| ছোট cluster-এ SWIM, Lifeguard আর আলাদা gossip pool — অতিরিক্ত নকশা | ১০টি node-এ সরল heartbeat বা coordination service-ই যথেষ্ট কি না ভাবুন |
AI-কে নকশার সমালোচক হিসেবে ব্যবহার করুন, আর সংখ্যা ও অনুমান স্পষ্ট করে দিতে বাধ্য করুন:
AI-এর membership নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Timeout যত বড়ই হোক, জীবিত সদস্যকে মৃত বলার ঝুঁকি কেন শূন্য হয় না? তাহলে বাস্তবের সিস্টেম নিরাপদ থাকে কীভাবে?
কারণটি তাত্ত্বিক, কিন্তু খুব বাস্তব। Internet বা data center-এর network-এ একটি বার্তা পৌঁছাতে কত সময় লাগবে, তার কোনো নিশ্চিত ঊর্ধ্বসীমা নেই: switch-এর queue ভরে যেতে পারে, একটি link কিছুক্ষণ ঝামেলা করতে পারে, আর প্রাপকের নিজের CPU কয়েক সেকেন্ড GC pause-এ আটকে থাকতে পারে। এমন মডেলকে বলে asynchronous. এখানে “অনেকক্ষণ সাড়া নেই” আর “মৃত” দুটি অবস্থা বাইরে থেকে হুবহু একই দেখায়। আপনি T সেকেন্ড অপেক্ষা করলে, একটি জীবিত সদস্য T + ১ সেকেন্ড চুপ থাকতে পারে।
১৯৮৫ সালে Fischer, Lynch আর Paterson এই অনিশ্চয়তার চরম ফল দেখান: এমন মডেলে মাত্র একটি process-ও crash করতে পারলে এমন কোনো consensus algorithm নেই যা সবসময় শেষ হওয়ার নিশ্চয়তা দেয়। ১৯৯৬ সালে Chandra আর Toueg এই দেয়ালকে অন্যভাবে দেখান: যদি একটি failure detector দেওয়া থাকে যা মাঝে মাঝে ভুল করলেও “এক সময় পরে” আর ভুল করে না, তাহলে majority সচল থাকলে consensus সম্ভব। অর্থাৎ ভুল করা চলবে, যদি ভুল নিরাপত্তা ভাঙতে না পারে।
বাস্তবের সিস্টেম ঠিক এই নীতি মানে: detector-কে শুধু “কখন চেষ্টা করব”-এর দায়িত্ব দিন, “কী সত্য”-এর নয়। Raft-এ follower heartbeat না পেলে নির্বাচন শুরু করে — ভুল করে শুরু করলেও ক্ষতি নেই, কারণ নতুন term আর majority ভোট ছাড়া কেউ leader হয় না, আর পুরোনো leader-এর term ছোট বলে তার লেখা বাতিল হয়। Lock-এর ক্ষেত্রে fencing token (পর্ব ৪৩): ভুল করে দুজন নিজেকে lock-ধারী ভাবলেও storage ছোট token-এর লেখা ফিরিয়ে দেয়। Detector ভুল করলে দাম হয় শুধু কর্মক্ষমতায় — একটি অকারণ নির্বাচন, কয়েক সেকেন্ডের বিরতি — সঠিকতায় নয়।
মূল উপলব্ধি: নিখুঁত failure detector নেই; ভালো নকশা detector-এর ভুলকে ধীরগতির দামে কিনে নেয়, ভুল ডেটার দামে নয়। Timeout হলো পরামর্শ, আর সত্য ঠিক হয় quorum আর fencing দিয়ে।
প্র ০২SWIM কেন timeout বাড়ানোর বদলে অন্য সদস্যদের দিয়ে পরোক্ষ ping করায়? এই কৌশল কখন কাজে আসে না?
Timeout বাড়ানো মানে প্রতিটি সত্যিকারের মৃত্যু ধরতেও দেরি। অথচ বেশিরভাগ ভুল সন্দেহের কারণ লক্ষ্য নয় — একটি নির্দিষ্ট network পথ: দুই যন্ত্রের মাঝের একটি switch-এ ক্ষণিক ভিড়, একটি হারানো UDP packet, বা একটি ক্লান্ত link. SWIM-এর লেখকেরা দেখান, সেই একই মুহূর্তে আরও কয়েকটি স্বাধীন পথ দিয়ে খোঁজ নিলে প্রতিটি পথ একসাথে ব্যর্থ হওয়ার সম্ভাবনা গুণফলে কমে যায়। সূত্রে: সরাসরি পথে ভুলের সম্ভাবনা 1 − (1 − p)², আর k পরোক্ষ পথ যোগ করলে সেটি [1 − (1 − p)⁴]k দিয়ে গুণ হয় — k-এর সাথে সূচকীয় হারে কমে। এই পর্বের simulator-এ ৫% loss-এ ৯.৭৫% থেকে ০.০৬২% — প্রায় ১৫৭ গুণ কম, অথচ ধরার সময় একই period-এর মধ্যে।
খরচও সামান্য: ping-req পাঠানো হয় শুধু সরাসরি ping ব্যর্থ হলে, তাই স্বাভাবিক অবস্থায় প্রতি সদস্যের বার্তা প্রতি period-এ মোটামুটি দুটিই থাকে।
কিন্তু পুরো যুক্তিটি দাঁড়িয়ে আছে একটি অনুমানে: পথগুলো স্বাধীনভাবে ব্যর্থ হয়। তিন রকম পরিস্থিতিতে এটি ভাঙে। (১) সমস্যা পর্যবেক্ষক Mi-এর নিজের: তার CPU পুরো ব্যস্ত বা network card ঝামেলা করছে — তাহলে সরাসরি আর পরোক্ষ সব ack-ই সে সময়মতো পড়তে পারে না। এই কারণেই HashiCorp-এর Lifeguard পর্যবেক্ষকের “নিজের স্বাস্থ্য” মাপে আর অসুস্থ হলে নিজের timeout বাড়ায়। (২) সমস্যা লক্ষ্য Mj-এর কাছের অংশে — তার rack-এর switch বা তার নিজের CPU; তখন সব পথই একসাথে ব্যর্থ, আর সেটাকে “বিকল” বলা হয়তো যুক্তিসংগতই। (৩) সাহায্যকারীরা সবাই একই অসুস্থ এলাকায় — এলোমেলো বাছাই এই ঝুঁকি কমায়, কিন্তু শূন্য করে না।
মূল উপলব্ধি: এক চোখের ভুল কমাতে কয়েক চোখে দেখা শক্তিশালী কৌশল — যতক্ষণ চোখগুলো সত্যিই স্বাধীন। Correlated failure-এ এই গুণফলের হিসাব ভেঙে পড়ে, তাই নিজের স্বাস্থ্য-সচেতনতা আর suspicion-এর মতো দ্বিতীয় স্তর লাগে।
প্র ০৩Phi accrual detector-এ “threshold ৮” আসলে কী বোঝায়? আর একই threshold দুই ভিন্ন পরিবেশে কেন ভিন্ন আচরণ করে?
φ = −log₁₀ Plater(t), যেখানে Plater(t) হলো — অতীতের heartbeat-ফাঁকের বণ্টন সত্য ধরলে — একটি জীবিত সদস্যের পরের heartbeat t-এর চেয়েও দেরিতে আসার সম্ভাবনা। তাই φ = ৮ মানে “মডেল অনুযায়ী এমন দেরি জীবিত সদস্যের ক্ষেত্রে প্রায় ১০ কোটিতে ১ বার ঘটার কথা।” এটি সেকেন্ড নয়, সম্ভাবনার লগারিদম। Hayashibara ও সহকর্মীদের ২০০৪ সালের paper-এর মূল যুক্তি ছিল ঠিক এটি: detector একটি অবিরাম সংখ্যা দেবে, আর প্রতিটি অ্যাপ্লিকেশন নিজের ঝুঁকি-সহনশীলতা অনুযায়ী সীমা বাছবে।
একই threshold-এর আচরণ বদলায় দুই কারণে। প্রথমত, window থেকে শেখা μ আর σ: শান্ত, কম-jitter-এর data center-এ σ ছোট, তাই গড়ের সামান্য বেশি নীরবতাতেই φ দ্রুত বাড়ে — দ্রুত ধরা। দূরের region বা ব্যস্ত cloud-এ σ বড়, তাই একই φ ছুঁতে অনেক বেশি নীরবতা লাগে — detector নিজেই ধৈর্যশীল। এটাই নকশার উদ্দেশ্য।
দ্বিতীয় কারণটি সূক্ষ্ম: বণ্টনের মডেল. এই পর্বের simulator-এ normal মডেল ৩,০০০ ms নীরবতায় φ ≈ ২২.৯ দিয়েছে, অথচ আসল ডেটায় এমন দেরি ছিল প্রায় ০.৪% (φ ≈ ২.৪)। কারণ GC pause-এর মতো লম্বা বিরতি “ভারী লেজ” তৈরি করে, যা normal বণ্টন ধরে না। ফলে normal-ভিত্তিক φ = ৮ সেখানে ঘণ্টায় প্রায় ২৫টি ভুল দিত। আবার exponential মডেল (কিছু implementation, সংস্করণভেদে, এমন সরলীকরণ ব্যবহার করে বলে জানা যায়) অনেক উদার — একই ডেটায় φ = ৮ ছুঁতে প্রায় ১৮.৬ s লাগত। Akka-র মতো implementation তাই “acceptable heartbeat pause” আর σ-এর নিম্নসীমার মতো বাড়তি knob দেয়।
ব্যবহারিক নিয়ম: threshold বাছার আগে নিজের পরিবেশে heartbeat-ফাঁকের আসল বণ্টন মেপে দেখুন — বিশেষ করে p99 আর p99.9 (পর্ব ৪-এর tail-এর ধারণা)। তারপর simulation-এ দেখুন, সেই threshold-এ ঘণ্টায় কতগুলো ভুল আর গড় ধরার সময় কত।
মূল উপলব্ধি: φ একটি সম্ভাবনার ভাষা, আর তার সঠিকতা পুরোটাই নির্ভর করে মডেলটি আপনার network-এর লেজকে কতটা সৎভাবে ধরে তার ওপর।
প্র ০৪একটি সার্ভার heartbeat-এ সুস্থ, কিন্তু ব্যবহারকারীদের অনুরোধ ব্যর্থ হচ্ছে — এমন “অর্ধেক অসুস্থ” অবস্থা membership কীভাবে সামলাবে?
এই অবস্থার একটি নাম আছে — gray failure. Microsoft-এর গবেষক Peng Huang ও সহকর্মীরা HotOS ২০১৭-এর একটি paper-এ বড় cloud-এর অভিজ্ঞতা থেকে দেখান, অনেক গুরুতর বিভ্রাটের শুরু পুরো crash দিয়ে নয়, বরং এমন অবস্থা দিয়ে যেখানে নজরদারির চোখে যন্ত্রটি সুস্থ, অথচ ব্যবহারকারীর চোখে অসুস্থ। তাঁরা এর মূল কারণ বলেন “differential observability” — detector আর ব্যবহারকারী ভিন্ন জিনিস দেখে।
উদাহরণ সহজেই কল্পনা করা যায়। Heartbeat চলে একটি আলাদা ছোট থ্রেডে, তাই সেটি সময়মতো যায়; কিন্তু আসল কাজের থ্রেড-pool একটি deadlock-এ আটকে আছে। বা ডিস্ক এত ধীর যে প্রতিটি লেখা কয়েক সেকেন্ড নেয়, অথচ ping-এর উত্তর memory থেকেই আসে। বা শুধু একটি নির্দিষ্ট পথের packet হারাচ্ছে, যা heartbeat-এর পথ নয়।
সামলানোর কয়েকটি স্তর আছে। (১) Heartbeat-কে আসল কাজের কাছাকাছি আনা: পর্ব ১৪-এর deep health check-এর মতো, যেখানে উত্তর দিতে আসল queue বা ডিস্কের ছোট একটি কাজ করতে হয়। তবে খুব গভীর পরীক্ষা নিজেই dependency-র সমস্যায় সবাইকে একসাথে “অসুস্থ” দেখিয়ে cascading failure ঘটাতে পারে। (২) ব্যবহারকারীর অভিজ্ঞতা দিয়ে মাপা: client বা load balancer-এর passive health check — আসল অনুরোধের error rate আর latency দেখে outlier সরানো। (৩) সন্দেহ জমানো: কয়েকজন স্বাধীন পর্যবেক্ষক একই সমস্যা দেখলে তবেই ব্যবস্থা, যেমন Lifeguard-এর বদলানো suspicion timeout. (৪) Observability (পর্ব ৫০): p99 latency আর error rate-এর ভিত্তিতে সতর্কবার্তা।
আর একটি বড় শিক্ষা: “জীবিত” একটি বহুমাত্রিক প্রশ্ন। Membership সেবা সাধারণত শুধু একটি মাত্রা (সাড়া দেয় কি না) মাপে; অ্যাপ্লিকেশনকে নিজের দরকারি মাত্রাগুলো আলাদা করে মাপতে হয়।
মূল উপলব্ধি: Failure detector যা মাপে, ব্যবহারকারী তা দেখে না। নজরদারির চোখকে যত বেশি ব্যবহারকারীর চোখের কাছাকাছি আনবেন, gray failure তত তাড়াতাড়ি ধরা পড়বে — তবে সেই চোখ যেন নিজেই নতুন বিপদ না হয়।
প্র ০৫Gossip এত শক্তিশালী আর সস্তা হলে, ZooKeeper বা etcd-এর মতো consensus-চালিত coordination service এখনো এত প্রচলিত কেন?
কারণ দুটি ভিন্ন প্রশ্নের উত্তর দেয়। Gossip-এর গুণ: কোনো কেন্দ্র নেই, প্রতি সদস্যের খরচ সামান্য, যেকোনো কয়েকজন হারালেও খবর থামে না, আর O(log n) রাউন্ডে সবার কাছে পৌঁছায়। ১৯৮৭ সালে Xerox-এর Clearinghouse থেকে শুরু করে Dynamo-ধাঁচের database আর HashiCorp-এর memberlist পর্যন্ত এই গুণগুলোই কাজে লেগেছে। কিন্তু gossip যা দেয় না: একটি নির্দিষ্ট মুহূর্তে সবাই একই কথা জানে — এমন নিশ্চয়তা। খবর সম্ভাবনায় ছড়ায়, দুজনের তালিকা কিছুক্ষণ আলাদা থাকে, আর partition-এ দুই পাশ নিজের মতো সত্য বানায়।
অনেক কাজের জন্য এই নিশ্চয়তা অপরিহার্য। “Shard ৭-এর মালিক এখন কে?” — দুজন মালিক মানে দুই দিকে লেখা। “এই lock কার?” — দুজন ধারক মানে একই টাকা দুবার কাটা। “এই config-এর কোন সংস্করণ চালু?” — অর্ধেক cluster পুরোনো নিয়মে। এসব প্রশ্নে লাগে linearizable উত্তর, যা ZooKeeper (Zab) আর etcd (Raft) দেয় majority quorum-এর মাধ্যমে, সাথে ক্রমিক নম্বর (zxid, revision) যা fencing token হিসেবে কাজ করে। দাম হলো: প্রতিটি লেখায় majority-র সম্মতি, ছোট cluster (সাধারণত ৩ বা ৫ সদস্য), আর লেখার throughput সীমিত।
তাই বাস্তবের পরিণত নকশা সাধারণত দুটিকে মেলায়। প্রকাশিত ডকুমেন্টেশন অনুযায়ী Consul-এ হাজারো agent-এর স্বাস্থ্য ছড়ায় gossip-এ, কিন্তু catalog থাকে কয়েকটি server-এর Raft-এ। Kafka তার metadata আর controller-এর জন্য ZooKeeper থেকে নিজস্ব Raft (KRaft)-এ গেছে — কিন্তু consensus ছাড়েনি। Cassandra-র মতো leaderless database-ও “একবারই হবে” ধরনের lightweight transaction-এ Paxos-ধাঁচের প্রোটোকল ব্যবহার করে।
একটি সহজ নিয়ম মনে রাখতে পারেন: অনেক সদস্য, ঘন ঘন বদল, আর ভুল হলে দাম শুধু একটু ধীরতা — gossip. অল্প কিন্তু গুরুত্বপূর্ণ তথ্য, আর ভুল হলে দাম ডেটার অসংগতি — consensus.
মূল উপলব্ধি: Gossip আর consensus প্রতিদ্বন্দ্বী নয়, একই দলের দুই খেলোয়াড়। একজন দ্রুত ও সস্তায় “সম্ভবত” জানায়, অন্যজন ধীরে কিন্তু নিশ্চিতভাবে “অবশ্যই” ঠিক করে।
অনুশীলন
হাতে হিসাব করুন, তারপর উত্তর মেলান। দরকার হলে ওপরের simulator-গুলোর সংখ্যা বদলে চালিয়ে দেখুন।
-
হিসাব — SWIM বনাম all-to-all: একটি cluster-এ ৫০০ জন সদস্য। Protocol period ১ s, প্রতিটি বার্তা স্বাধীনভাবে ১০% সম্ভাবনায় হারায়, k = ৩. (ক) একটি জীবিত সদস্যকে একটি probe-এ ভুল সন্দেহের সম্ভাবনা কত? (খ) প্রতি period-এ পুরো cluster-এ প্রত্যাশিত কতগুলো ভুল সন্দেহ? (গ) All-to-all heartbeat-এ প্রতি সেকেন্ডে কত বার্তা, আর SWIM-এ সর্বোচ্চ কত? (ঘ) Push gossip, fanout ১-এ একটি খবর সবার কাছে পৌঁছাতে প্রায় কত রাউন্ড?
(ক) সরাসরি ব্যর্থ: 1 − 0.9² = 0.19. একটি পরোক্ষ পথ ব্যর্থ: 1 − 0.9⁴ = 1 − 0.6561 = 0.3439; তিনটিই ব্যর্থ: 0.3439³ ≈ 0.04067. মোট: 0.19 × 0.04067 ≈ 0.00773, অর্থাৎ প্রায় ০.৭৭%.
(খ) প্রতি period-এ ৫০০টি probe, তাই ৫০০ × ০.০০৭৭৩ ≈ ৩.৮৬টি — প্রতি সেকেন্ডে প্রায় চারজন জীবিত সদস্যকে সন্দেহ! Suspicion না থাকলে এরা সবাই মৃত ঘোষিত হতো। তাই এত lossy network-এ suspicion আর খণ্ডন অপরিহার্য।
(গ) All-to-all: ৫০০ × ৪৯৯ = ২,৪৯,৫০০ বার্তা প্রতি সেকেন্ডে। SWIM: প্রতিটি probe-এ ping আর ack ২টি; সরাসরি ব্যর্থ হলে (০.১৯ সম্ভাবনা) সর্বোচ্চ ৪ × ৩ = ১২টি বাড়তি। প্রত্যাশিত সর্বোচ্চ ২ + ০.১৯ × ১২ = ৪.২৮ প্রতি probe, তাই ৫০০ × ৪.২৮ = ২,১৪০ — all-to-all-এর ১০০ ভাগের ১ ভাগেরও কম। (হারানো বার্তার পরের বার্তা আসলে পাঠানোই হয় না, তাই আসল সংখ্যা আরও কম।)
(ঘ) log₂ ৫০০ + ln ৫০০ ≈ ৮.৯৭ + ৬.২১ ≈ ১৫.২, অর্থাৎ প্রায় ১৫–১৬ রাউন্ড; ১ s-এর রাউন্ডে প্রায় ১৫ সেকেন্ড।
-
হিসাব — phi থেকে সময়: একটি সদস্যের heartbeat-ফাঁকের গড় ১,০০০ ms আর standard deviation ২০০ ms, আর ধরা যাক বণ্টনটি normal. (ক) শেষ heartbeat-এর পর ১,৬০০ ms নীরবতায় φ কত? (খ) Threshold φ = ৮ ছুঁতে কত ms নীরবতা লাগবে? (সংকেত: P(Z > 3) ≈ 0.00135; P(Z > z) = 10⁻⁸ হয় z ≈ 5.61-এ।) (গ) আসল ডেটায় যদি ১% ফাঁকে কয়েক সেকেন্ডের GC pause থাকে, এই হিসাব কেন বিপজ্জনক?
(ক) z = (১৬০০ − ১০০০)/২০০ = ৩. Plater ≈ 0.00135, তাই φ = −log₁₀ 0.00135 ≈ ২.৮৭.
(খ) φ = ৮ মানে Plater = 10⁻⁸, z ≈ ৫.৬১. নীরবতা t = ১০০০ + ৫.৬১ × ২০০ ≈ ২,১২২ ms. অর্থাৎ মাত্র প্রায় ২.১ সেকেন্ড চুপ থাকলেই normal মডেল “১০ কোটিতে ১” বলে রায় দেবে।
(গ) ১% ফাঁক যদি কয়েক সেকেন্ডের হয়, তাহলে ২,১২২ ms-এর বেশি নীরবতা আসলে প্রায় ১০০-তে ১ বার ঘটে, ১০ কোটিতে ১ বার নয়। Δ = ১ s-এ ঘণ্টায় ৩,৬০০টি ফাঁক, তাই ঘণ্টায় প্রায় ৩৬টি ভুল সংকেত — অংশ ৪-এর simulator-এ এমন ডেটায় φ = ৮ ঘণ্টায় ২৫টি ভুল দিয়েছিল। প্রতিকার: আসল বণ্টন মাপা, acceptable pause বা σ-এর নিম্নসীমা যোগ করা, আর suspicion-এর মতো দ্বিতীয় স্তর রাখা।
-
নকশা সমালোচনা: একটি কাল্পনিক অনলাইন গেম সেবায় ২০০টি game server. প্রস্তাবিত নকশা: প্রতিটি server প্রতি ৫০০ ms-এ বাকি সবাইকে UDP heartbeat পাঠাবে; কারও একটি heartbeat না এলেই প্রতিটি server নিজের তালিকা থেকে তাকে বাদ দিয়ে consistent hashing ring নতুন করে বানাবে, আর সেই server-এর খেলোয়াড়দের নিজের ring অনুযায়ী অন্য server-এ সরাবে। এই নকশার সমস্যা আর উন্নতি লিখুন।
- খরচ: ২০০ × ১৯৯ = ৩৯,৮০০ বার্তা প্রতি ৫০০ ms, অর্থাৎ প্রতি সেকেন্ডে ৭৯,৬০০; প্রতিটি server প্রতি সেকেন্ডে ৩৯৮টি heartbeat প্রক্রিয়া করে।
- একটি মিসেই বাদ: ০.১% loss-এও প্রতি সেকেন্ডে প্রায় ৮০টি heartbeat হারাবে, অর্থাৎ প্রতি সেকেন্ডে কেউ না কেউ কাউকে বাদ দিচ্ছে — অবিরাম flapping আর খেলোয়াড়দের অকারণ সরানো।
- প্রত্যেকে নিজের মতো ring: server-গুলোর তালিকা আলাদা, তাই ring আলাদা — একই খেলোয়াড়কে দুজন server নিজের ভাবতে পারে (split brain), বা কেউই না।
- উন্নতি: SWIM-ধাঁচের detector (k পরোক্ষ ping, suspicion, incarnation) বা phi accrual; সন্দেহ আর “সরানো” আলাদা করা — সন্দেহে শুধু নতুন খেলোয়াড় পাঠানো বন্ধ, আর মালিকানা বদল একটি consensus-চালিত জায়গায় (etcd-এ lease আর ক্রমিক নম্বরসহ ring-এর সংস্করণ), যাতে সবাই একই ring মানে; সরানো ধীরে ধীরে, হার-সীমাসহ; আর ফিরে আসা server-এর জন্য দেরিতে পুনর্যোগ (hysteresis).
-
আউটপুট অনুমান করুন: নিচের কোড SWIM-ধাঁচের নিয়মে একজন সদস্য Mj-এর অবস্থা মেলায়। প্রতিটি লাইনে কী ছাপা হবে, আগে নিজে লিখুন (উদাহরণ — এই কোডটি পাতায় চালানো যায় না)।
function merge(cur, msg) { if (cur.state === "dead") return cur; // confirm beats everything if (msg.state === "dead") return msg; if (msg.inc > cur.inc) return msg; // newer incarnation wins if (msg.inc === cur.inc && msg.state === "suspect" && cur.state === "alive") return msg; return cur; // older or equal news: ignore } let v = { state: "alive", inc: 3 }; const inbox = [ { state: "suspect", inc: 3 }, { state: "alive", inc: 3 }, { state: "alive", inc: 4 }, { state: "suspect", inc: 3 }, { state: "suspect", inc: 4 }, { state: "alive", inc: 5 }, ]; for (const m of inbox) { v = merge(v, m); console.log(v.state + " " + v.inc); }suspect 3 suspect 3 alive 4 alive 4 suspect 4 alive 5(১) suspect #৩ সমান নম্বরে alive #৩-কে হারায়। (২) alive #৩ সমান নম্বরে suspect-কে হারাতে পারে না — খণ্ডনের জন্য বড় নম্বর লাগে, তাই অবস্থা বদলায় না। (৩) alive #৪ বড় নম্বর, জিতল — এটিই Mj-এর খণ্ডন। (৪) suspect #৩ পুরোনো খবর, উপেক্ষা। (৫) suspect #৪ সমান নম্বরে alive-কে হারায় — নতুন করে সন্দেহ। (৬) alive #৫ আবার খণ্ডন। লক্ষ করুন, ক্রম বদলালেও শেষ অবস্থা বড় নম্বরের খবরই ঠিক করে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমSWIM-এ একজন সদস্যের সম্পর্কে সিদ্ধান্তের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- লক্ষ্য সদস্যকে সরাসরি ping পাঠানো
- ping timeout-এর মধ্যে ack না আসা
- এলোমেলো k জনকে ping-req পাঠানো
- period শেষেও ack না এলে suspect ঘোষণা আর ping-এর পিঠে ছড়ানো
- suspicion timeout-এর মধ্যে বড় incarnation-এর alive না এলে confirm
২০০টি সদস্যের একটি cluster-এ প্রত্যেকে প্রতি সেকেন্ডে বাকি সবাইকে একটি heartbeat পাঠায়। প্রতি সেকেন্ডে মোট কতটি heartbeat বার্তা?
- ২০০
- ৪০০
- ৩৯,৮০০
- ৪০,০০০
SWIM-এ সরাসরি ping-এর ack না এলে সদস্যটি সঙ্গে সঙ্গে “মৃত” না বলে k জনকে ping-req পাঠায় কেন?
- মোট বার্তার সংখ্যা কমানোর জন্য
- একটি নির্দিষ্ট network পথের সমস্যাকে লক্ষ্যের মৃত্যু ভেবে false positive এড়াতে
- সাহায্যকারীদের মধ্যে থেকে নতুন leader বাছার জন্য
- লক্ষ্যের incarnation number বাড়িয়ে দেওয়ার জন্য
SWIM-এ সদস্য Mj-কে “suspect, incarnation ৭” ঘোষণা করা হয়েছে। এই সন্দেহ খণ্ডন করে “alive, incarnation ৮” ঘোষণা কে দিতে পারে?
- শুধু Mj নিজে
- যে সদস্য সন্দেহ করেছিল
- Cluster-এর majority ভোট দিয়ে
- যেকোনো seed node
১,০২৪ জন সদস্যের একটি cluster-এ push gossip, fanout ১. একজনের কাছ থেকে শুরু হয়ে একটি খবর সবার কাছে পৌঁছাতে মোটামুটি কত রাউন্ড লাগবে?
- ঠিক ১০
- প্রায় ১৭
- প্রায় ১,০২৩
- প্রায় ৫১২
একটি phi accrual failure detector একজন সদস্যের জন্য φ = ৩ দেখাচ্ছে। এর সঠিক অর্থ কোনটি?
- শেষ heartbeat-এর পর ৩ সেকেন্ড কেটেছে
- পরপর ৩টি heartbeat হারিয়েছে
- সদস্যটি নিশ্চিতভাবে মৃত
- মডেল অনুযায়ী জীবিত সদস্যের এত দেরির সম্ভাবনা প্রায় ১,০০০-এ ১
৫টি সদস্যের একটি cluster network partition-এ {A, B, C} আর {D, E} দলে ভাগ হলো, আর gossip-ভিত্তিক membership-এ প্রত্যেক দল অন্য দলকে মৃত দেখছে। একটি shard-এর লেখার মালিক নিরাপদে ঠিক করতে কী লাগবে?
- প্রত্যেক দল নিজের তালিকার সবচেয়ে ছোট ID-কে মালিক বানাবে
- Gossip-এর fanout বাড়িয়ে দুই পাশের তালিকা মিলিয়ে নেওয়া
- Majority quorum-ভিত্তিক consensus বা lease, আর fencing token
- Failure detection-এর timeout অনেক বড় করে দেওয়া
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Membership = failure detector + dissemination; network-এ ধীর আর মৃত আলাদা করা যায় না, তাই প্রতিটি detector অনুমান করে।
- ছোট timeout মানে দ্রুত ধরা কিন্তু বেশি false positive; phi accrual সন্দেহকে একটি সম্ভাবনার মাত্রা বানায়, কিন্তু তার মডেল আপনার ডেটার লেজকে ধরতে হবে।
- All-to-all heartbeat O(n²); push gossip প্রায় log₂ n + ln n রাউন্ডে সবার কাছে, push-pull শেষ পর্যায়ে আরও দ্রুত।
- SWIM: ping → ping-req (k জন) → suspect → খণ্ডন বা confirm; বড় incarnation জেতে, বাড়ায় শুধু সদস্য নিজে; খবর ping-এর পিঠে, প্রতি সদস্যের খরচ স্থির।
- Membership consensus নয়: partition-এ দুই পাশ আলাদা সত্য দেখে; মালিক বা leader ঠিক করতে majority quorum আর fencing.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৫ · Probabilistic data structure — Bloom filter, count-min sketch আর HyperLogLogপরবর্তী পর্বঅল্প memory-তে আনুমানিক কিন্তু নিয়ন্ত্রিত ভুলের উত্তর।
- পর্ব ৪৩ · Coordination — distributed lock, lease আর fencing tokenপাশের পর্বকেন্দ্রীয় membership-এর ephemeral node, session আর fencing.
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Computer Networks · UDPগভীরে যানHeartbeat আর gossip কেন প্রায়ই UDP-তে চলে, আর হারানো datagram-এর অর্থ।
- DSA · Randomized algorithmপাশের ট্র্যাকএলোমেলো বাছাই কেন প্রত্যাশিত সময়ে এত ভালো কাজ করে — gossip-এর গণিতের ভিত্তি।