এই পর্বে যা শিখবেন
- Dynamo paper-এর leaderless নকশা: coordinator, preference list আর N, W, R
- R + W > N-এর প্রমাণ, প্রচলিত বিন্যাস, আর কয়টি node বিকল হলে পড়া বা লেখা থামে — হিসাব ও সিমুলেটরে
- সবচেয়ে দ্রুত W টির অপেক্ষায় tail latency কেন কমে, আর quorum কেন linearizability দেয় না
- Sloppy quorum, hinted handoff, read repair আর Merkle tree দিয়ে anti-entropy — ধাপে ধাপে
- Version vector, sibling, tombstone আর প্রতিটি অনুরোধে consistency বেছে নেওয়ার নকশা
১ · Leader ছাড়া replication — Dynamo-র লক্ষ্য, coordinator আর preference list
পর্ব ২২-এ তিন ধরনের replication দেখেছেন; শেষটি ছিল leaderless replication — যেখানে কোনো একটি কপি লেখার ক্রম ঠিক করে না। সেখানে শুধু ছবিটা ছিল; এই পর্বে তার যন্ত্রপাতি: কয়টি কপিতে লিখলে আর কয়টি থেকে পড়লে নতুন মান পাওয়ার নিশ্চয়তা আসে, কপি বন্ধ থাকলে কী হয়, আর পুরোনো কপি কীভাবে নিজে থেকে ঠিক হয়।
একটি পরিবারের তিন ভাইয়ের তিনটি মুদি দোকান — তিন মোড়ে। কোনো “মূল দোকান” নেই; প্রতিটিতে একই বাকির খাতা। ক্রেতা যে দোকানে আসেন, সেই দোকানি খাতায় লেখেন আর বাকি দুই ভাইকে ফোন করেন; অন্তত আরেকজন “লিখেছি” বললেই ক্রেতাকে বলেন “হয়ে গেছে”। বাকি জানতে চাইলে দোকানি দুই ভাইকে জিজ্ঞেস করে যে লেখার তারিখ নতুন, সেটিই বলেন। এক ভাইয়ের দোকান লোডশেডিংয়ে বন্ধ? পাশের দোকানের কাকা একটি চিরকুট রাখেন — “এটি মেজো ভাইয়ের খাতায় উঠবে” — দোকান খুললে পৌঁছে দেন। আর মাস শেষে তিন ভাই খাতা মেলান: আগে মাসের মোট, না মিললে সপ্তাহের, তারপর দিনের। এই পর্বের প্রায় সব ধারণা এই এক দৃশ্যে — W, R, hinted handoff আর Merkle tree.
২০০৭ সালে Amazon-এর DeCandia ও সহকর্মীরা SOSP সম্মেলনে “Dynamo: Amazon’s Highly Available Key-value Store” প্রকাশ করেন। Paper-এর ঘোষিত লক্ষ্য: shopping cart-এর মতো সেবা যেন সবসময় লেখা নেয় — ডিস্ক, সার্ভার বা নেটওয়ার্কের ব্যর্থতাতেও — আর latency-র লক্ষ্য মাপা হয় গড়ে নয়, ৯৯.৯তম percentile-এ। এর লেখকদের একজন Avinash Lakshman পরে Facebook-এ Prashant Malik-এর সাথে Cassandra তৈরি করেন; Riak-ও এই ধারণায় বানানো। এই পর্বে “Cassandra-ধাঁচ” বা “Riak-ধাঁচ” মানে খোলা উৎসের উদাহরণ, খুঁটিনাটি সংস্করণভেদে আলাদা।
একটি অনুরোধের পথ
প্রতিটি key-কে hash করে একটি ring-এ বসানো হয়, আর ring-এ ঘড়ির কাঁটার দিকে হেঁটে প্রথম N টি ভিন্ন সার্ভার হয় সেই key-এর কপি রাখার তালিকা — preference list (পর্ব ১৬-এ ring, virtual node আর এই তালিকা বানানো দেখেছেন; এখানে আর পুনরাবৃত্তি নয়)। N হলো replication factor. একটি অনুরোধ যে node সামলায়, সে coordinator node — সাধারণত preference list-এর প্রথম সচল node. Dynamo paper দুটি পথ বর্ণনা করে: client একটি সাধারণ load balancer দিয়ে যেকোনো node-এ যায়, আর সেই node দরকার হলে অনুরোধটি coordinator-এর কাছে পাঠায়; অথবা partition চেনে এমন client library সরাসরি coordinator-এ যায় — এক hop কম (পর্ব ২৩-এর request routing).
লেখার সময় coordinator নতুন মানটি একটি সংস্করণ-চিহ্নসহ নিজের কাছে লেখে (সে নিজেও replica), তারপর বাকি replica-দের পাঠায়; মোট W টি ack হলেই client-কে “OK”. পড়ার সময় সে replica-দের জিজ্ঞেস করে, R টি উত্তর এলে সংস্করণ মিলিয়ে সবচেয়ে নতুনটি ফেরত দেয়। Coordinator একটি ভূমিকা, স্থায়ী পদ নয় — একটি মরলে পরের node সেই কাজ নেয়, failover-এর সিদ্ধান্ত লাগে না।
Ring-এ key K-এর preference list, coordinator আর W টি ack
Dynamo-style ring: N = 3 replicas, coordinator B, write succeeds after W = 2 acks
“সবচেয়ে নতুন” চিনতে প্রতিটি মানের সাথে একটি সংস্করণ-চিহ্ন যায়। Dynamo রাখত vector clock — কোন node কতবার এই key বদলেছে, তার তালিকা; Cassandra-ধাঁচের store রাখে প্রতিটি column-এর timestamp. কোনটি কী হারায়, অংশ ৭-এ; vector clock-এর পুরো তত্ত্ব পর্ব ৩৯-এ।
২ · N, W, R আর R + W > N — পড়া কেন নতুন লেখা ছোঁয়
তিনটি সংখ্যাই পুরো নকশা: N — একটি key-এর কয়টি কপি; W — কয়টি replica-র ack পেলে লেখা সফল; R — কয়টি replica-র উত্তর পেলে পড়া শেষ। লেখা আসলে N টির সবাইকেই পাঠানো হয়; W শুধু বলে client কতক্ষণ অপেক্ষা করবে। একইভাবে পড়ার অনুরোধ সাধারণত সবাইকে যায়, আর প্রথম R টি উত্তর এলেই কাজ শেষ। এখানে W আর R গোনা হয় কেবল key-এর নির্ধারিত N টি replica থেকে — এর নাম strict quorum; ব্যতিক্রমটি অংশ ৫-এ।
N টি replica-র মধ্যে যেকোনো W টির দল আর যেকোনো R টির দল অন্তত W + R − N টি replica-তে মেলে। তাই R + W > N হলে প্রতিটি পড়ার দলে অন্তত একটি replica থাকে, যে শেষ সম্পন্ন লেখাটি পেয়েছে।
প্রমাণ (pigeonhole): লেখার দল 𝒲, পড়ার দল ℛ — দুটিই N টি replica-র উপসেট। দুটির মিলন N-এর বেশি হতে পারে না: |𝒲 ∪ ℛ| ≤ N. আবার |𝒲 ∩ ℛ| = |𝒲| + |ℛ| − |𝒲 ∪ ℛ| ≥ W + R − N. R + W > N হলে ডান দিক অন্তত ১, তাই অন্তত একটি সাধারণ replica আছে — এটাই quorum intersection. সেই replica-র কাছে সম্পন্ন লেখাটি আছে (সে ack দিয়েছিল), আর coordinator সংস্করণ মিলিয়ে নতুনটি বাছে — তাই উত্তরে নতুন মান আসে। R + W ≤ N হলে এমন দুটি দল বানানো যায় যাদের মাঝে কেউ নেই; তখন একটি stale read সম্ভব। প্রমাণে চারটি লুকানো শর্ত আছে: দলগুলো নির্ধারিত N টি থেকেই (strict), লেখাটি পড়া শুরুর আগে সম্পন্ন, সংস্করণগুলো তুলনা করা যায়, আর ack দেওয়া replica পরে নতুন মানটি হারায় না। অংশ ৪-এ দেখবেন, বাস্তবে প্রতিটিই কখনো না কখনো ভাঙে।
আরেকটি শর্ত মাঝে মাঝে লাগে: W > N/২ হলে যেকোনো দুটি লেখার দলও মেলে, তাই দুটি লেখা একে অপরকে “দেখে” ক্রম ঠিক করতে পারে। ১৯৭৯ সালের প্রথম দিকের quorum-ভিত্তিক নকশায় এটি থাকত (ভাবনার প্রশ্ন ০১)। Dynamo ইচ্ছা করে এটি বাধ্যতামূলক রাখেনি — W = ১-ও চলে — কারণ একসাথে দুটি লেখা ঠেকানোর বদলে সে দুটিকেই রেখে পরে মেলায় (অংশ ৭)।
| N, W, R | R + W > N? | লেখা চলে যতক্ষণ বিকল | পড়া চলে যতক্ষণ বিকল | স্বভাব |
|---|---|---|---|---|
| ৩, ২, ২ | হ্যাঁ (৪ > ৩) | ১টি | ১টি | Dynamo paper-এ বলা সবচেয়ে প্রচলিত বিন্যাস; ভারসাম্য |
| ৩, ৩, ১ | হ্যাঁ | ০টি | ২টি | পড়া খুব দ্রুত; একটি replica বিকল হলেই লেখা থামে |
| ৩, ১, ৩ | হ্যাঁ | ২টি | ০টি | লেখা প্রায় সবসময় চলে আর দ্রুত; পড়ায় সবার অপেক্ষা |
| ৩, ১, ১ | না | ২টি | ২টি | সবচেয়ে দ্রুত আর available; পুরোনো পড়া সাধারণ ঘটনা |
| ৫, ৩, ৩ | হ্যাঁ | ২টি | ২টি | একসাথে দুটি বিকল সহ্য করে; বেশি কপি আর বেশি খরচ |
সূত্রের ওপর ভরসা না করে সব সম্ভাবনা গুনে দেখা যাক। নিচের প্রোগ্রাম N = ৩ আর N = ৫-এর প্রতিটি (W, R)-এর জন্য সব সম্ভাব্য লেখার দল আর পড়ার দল জোড়া বানিয়ে সবচেয়ে কম মিল (worst-case overlap) বের করে, সাথে কয়টি replica বিকল থাকলেও লেখা বা পড়া চলে:
// Every (W, R) choice for N replicas: how many replicas do a write set and a read set
// share in the WORST case? Checked by brute force over all subsets, not by the formula.
const bits = m => { let c = 0; for (; m; m &= m - 1) c++; return c; };
function subsets(n, k) { // all k-element subsets of {0..n-1} as bitmasks
const out = [];
for (let m = 0; m < (1 << n); m++) if (bits(m) === k) out.push(m);
return out;
}
for (const N of [3, 5]) {
const cols = Array.from({ length: N }, (_, i) => i + 1);
console.log(`N = ${N}: worst-case overlap of write set and read set (0 = stale read possible)`);
console.log(" " + cols.map(R => `R=${R} `).join("") + " write works while");
const smallest = [];
for (let W = 1; W <= N; W++) {
let row = `W=${W} `;
for (const R of cols) {
let min = N;
for (const w of subsets(N, W)) for (const r of subsets(N, R)) min = Math.min(min, bits(w & r));
row += ` ${min} `;
if (min > 0 && W + R === N + 1) smallest.push(`(${W},${R})`);
}
console.log(row + ` ${N - W} replicas down`);
}
console.log("read OK " + cols.map(R => ` ${N - R} `).join("") + " <- read works while this many down");
console.log("smallest always-overlapping (W,R): " + smallest.join(" ") + "\n");
}আউটপুট:
N = 3: worst-case overlap of write set and read set (0 = stale read possible)
R=1 R=2 R=3 write works while
W=1 0 0 1 2 replicas down
W=2 0 1 2 1 replicas down
W=3 1 2 3 0 replicas down
read OK 2 1 0 <- read works while this many down
smallest always-overlapping (W,R): (1,3) (2,2) (3,1)
N = 5: worst-case overlap of write set and read set (0 = stale read possible)
R=1 R=2 R=3 R=4 R=5 write works while
W=1 0 0 0 0 1 4 replicas down
W=2 0 0 0 1 2 3 replicas down
W=3 0 0 1 2 3 2 replicas down
W=4 0 1 2 3 4 1 replicas down
W=5 1 2 3 4 5 0 replicas down
read OK 4 3 2 1 0 <- read works while this many down
smallest always-overlapping (W,R): (1,5) (2,4) (3,3) (4,2) (5,1)
প্রতিটি ঘরের সংখ্যা ঠিক max(০, W + R − N) — উপপাদ্যটি brute force-এ মিলে গেল। N = ৩-এ W=2, R=2-এর ঘরে 1: সবচেয়ে খারাপ ক্ষেত্রেও একটি সাধারণ replica; আর সারির শেষে 1 replicas down, নিচের সারিতেও 1 — একটি বিকল হলে পড়া-লেখা দুটিই চলে। কোণের দুটি বিন্যাস দেখুন: (3,1)-এ পড়া ২টি বিকলেও চলে, কিন্তু লেখা একটিতেই থামে; (1,3) ঠিক উল্টো। N = ৫-এ (3,3) একসাথে দুটি বিকল সহ্য করে — N = ৩-এর দ্বিগুণ।
W বাড়ালে লেখা ধীর ও ভঙ্গুর, পড়া সহজ; R বাড়ালে উল্টো। আগে ঠিক করুন ডেটাটি পড়া-ভারী না লেখা-ভারী, আর কোন কাজ বিকলতায়ও চলতেই হবে; তারপর W + R = N + ১-এর রেখায় একটি বিন্দু বাছুন। N সাধারণত বিজোড় (৩ বা ৫) — জোড় N-এ একই সহনশীলতার জন্য বাড়তি একটি কপির দাম দিতে হয় (অনুশীলন ৩)।
৩ · ব্যর্থতায় quorum-এর হিসাব আর tail latency
কয়টি node বিকল হলে কী থামে
Strict quorum-এ লেখার জন্য W টি replica-র নাগাল লাগে, তাই লেখা সহ্য করে N − W টি বিকল; পড়া সহ্য করে N − R টি। প্রতিটি replica স্বাধীনভাবে p সম্ভাবনায় সচল ধরলে, লেখা সফল হওয়ার সম্ভাবনা হলো “N টির মধ্যে অন্তত W টি সচল”: Σk=W..N C(N, k) pk (১ − p)N−k. ধরা যাক p = ০.৯৯:
- N = ৩, W = ২: ৩p²(১ − p) + p³ = ০.০২৯৪০৩ + ০.৯৭০২৯৯ = ০.৯৯৯৭০২ — বিভ্রাটের সম্ভাবনা ০.০০০২৯৮, একটি replica-র ০.০১-এর চেয়ে প্রায় ৩৪ গুণ কম।
- N = ৩, W = ৩: p³ = ০.৯৭০২৯৯ — একটি replica-র চেয়েও খারাপ! সবার ack চাওয়া মানে তিনটির যেকোনোটির ব্যর্থতাই আপনার ব্যর্থতা।
- N = ৫, W = ৩: প্রায় ০.৯৯৯৯৯০ — দুটি বিকল সহ্য করে।
এই হিসাবের বড় অনুমান “স্বাধীন ব্যর্থতা”। একই rack, একই বিদ্যুৎ বা একই ভুল config সব কপিকে একসাথে ফেলে দিলে হিসাব ভেঙে পড়ে (পর্ব ৫-এর correlated failure) — তাই preference list-এ ভিন্ন rack বা zone বাছা হয়। আর network দুই ভাগ হলে ছোট ভাগটি quorum বানাতেই পারে না — strict quorum সেখানে consistency রাখতে availability ছেড়ে দেয়, যা CAP-এর কথা (পর্ব ৪০)।
সবচেয়ে দ্রুত W টি — লেজ কাটার কৌশল
লেখা যায় তিনজনের কাছেই, কিন্তু অপেক্ষা শুধু W টির। মানে latency হলো তিনটি উত্তরের মধ্যে W-তম দ্রুতটির সময় — গণিতে একে order statistic বলে। ধরা যাক প্রতিটি replica স্বাধীনভাবে ৫% সময় ধীর (garbage collection, ডিস্কের ব্যস্ততা, ব্যস্ত প্রতিবেশী)। তাহলে লেখা ধীর হবে তখনই, যখন দ্রুত replica W টির কম:
- W = ১: তিনটিই ধীর — ০.০৫³ = ০.০১২৫%
- W = ২: অন্তত দুটি ধীর — ৩ × ০.০৫² × ০.৯৫ + ০.০৫³ = ০.৭২৫%
- W = ৩: অন্তত একটি ধীর — ১ − ০.৯৫³ = ১৪.২৬%
একক replica-র ৫% ধীরতা W = ২-এ নামে প্রায় সাত ভাগের এক ভাগে, W = ৩-এ ওঠে প্রায় তিন গুণে। এটি পর্ব ৪-এর fan-out-এর উল্টো পিঠ: সবার উত্তর চাইলে সবচেয়ে ধীরটি সময় ঠিক করে, কয়েকটির চাইলে দ্রুতরা। Hedged request একই বুদ্ধি — দেরি হলে আরেকটি replica-কেও জিজ্ঞেস করা। দাম: সব অনুরোধ সব replica-তে যায়, তাই খরচ কম W-তেও কমে না।
নিচের সিমুলেটরে তিনটি replica: R1 আর R2 কাছে আর দ্রুত, R3 একটু ধীর (round trip ২ ms + গড়ে ৪ ms এলোমেলো)। প্রতিটি অনুরোধে ১% সম্ভাবনায় কোনো উত্তর নেই, আর ১% সম্ভাবনায় ৬০ ms-এর একটি বিরতি (যেমন garbage collection). Client লেখা সফল হওয়ার ১ ms পরে পড়ে; লেখাটি একটি replica-তে পৌঁছায় তার round trip-এর অর্ধেক সময়ে। তিন বিন্যাস (W, R) = (১, ১), (২, ২), (৩, ১) — ২০,০০০টি করে লেখা-পড়া, seed নির্দিষ্ট:
// 3 replicas, no leader. A write goes to all 3 and succeeds after W acks. The client reads
// 1 ms after its write succeeded, asking all 3; the read returns after the R fastest replies.
function rng(seed) { // mulberry32: seeded, repeatable
return () => { 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 REP = [{ base: 1, mean: 1 }, { base: 1, mean: 2 }, { base: 2, mean: 4 }]; // round trip ms
const DOWN = 0.01, PAUSE = 0.01; // per request: 1% no reply, 1% a 60 ms pause
function call(rand, r) { // round-trip time of one request to replica r
if (rand() < DOWN) return Infinity;
const t = REP[r].base - REP[r].mean * Math.log(1 - rand());
return rand() < PAUSE ? t + 60 : t;
}
const pct = (a, p) => a[Math.floor(p / 100 * a.length)].toFixed(1).padStart(6);
const show = (a, fail) => [50, 99, 99.9].map(p => pct(a, p)).join("") + String(fail).padStart(5);
const H = x => ["50", "99", "99.9"].map(p => (x + p).padStart(6)).join("") + " fail";
console.log("(W,R) " + H("w") + " |" + H("r") + " | stale");
for (const [W, R] of [[1, 1], [2, 2], [3, 1]]) {
const rand = rng(41), wl = [], rl = []; let wFail = 0, rFail = 0, stale = 0;
for (let i = 0; i < 20000; i++) {
const wRtt = [0, 1, 2].map(r => call(rand, r));
const tw = [...wRtt].sort((a, b) => a - b)[W - 1]; // W-th fastest ack
if (tw === Infinity) { wFail++; continue; } else wl.push(tw);
const tr = tw + 1, rRtt = [0, 1, 2].map(r => call(rand, r));
const got = [0, 1, 2].filter(r => rRtt[r] < Infinity).sort((a, b) => rRtt[a] - rRtt[b]);
if (got.length < R) { rFail++; continue; }
rl.push(rRtt[got[R - 1]]);
// replica r is fresh if the write reached it (half its RTT) before the read did
if (!got.slice(0, R).some(r => wRtt[r] / 2 <= tr + rRtt[r] / 2)) stale++;
}
[wl, rl].forEach(a => a.sort((x, y) => x - y));
console.log(`(${W},${R}) ${show(wl, wFail)} |${show(rl, rFail)} | ${stale}`);
}আউটপুট (ms; w লেখা, r পড়া, fail = W বা R টি উত্তর আসেনি):
(W,R) w50 w99 w99.9 fail | r50 r99 r99.9 fail | stale
(1,1) 1.5 4.0 6.2 0 | 1.5 4.0 5.9 0 | 883
(2,2) 2.6 9.3 22.9 4 | 2.6 9.3 24.2 5 | 0
(3,1) 5.5 63.8 71.6 597 | 1.5 4.0 6.3 0 | 0
(১, ১): সবচেয়ে দ্রুত — p99 মাত্র 4.0 ms, কোনো ব্যর্থতা নেই। কিন্তু ২০,০০০টির মধ্যে 883টি পড়া (প্রায় ৪.৪%) নিজের সদ্য-সফল লেখাই দেখেনি: সবচেয়ে দ্রুত replica-টি লেখা নিয়েছে, আর পড়া গেছে অন্যজনের কাছে, যার কাছে লেখাটি তখনো পথে বা হারিয়ে গেছে। (২, ২): পুরোনো পড়া 0 — উপপাদ্য কাজ করছে। p99 বেড়ে 9.3 ms, কারণ এখন দ্বিতীয় দ্রুতটির অপেক্ষা। ব্যর্থতা মাত্র 4 আর 5 — হিসাবে প্রত্যাশা প্রায় ৬ (দুটি replica একসাথে চুপ: ৩ × ০.০১² × ০.৯৯ + ০.০১³ ≈ ০.০৩%). (৩, ১): পড়া (১, ১)-এর মতোই দ্রুত আর পুরোনো পড়া নেই, কিন্তু লেখার দাম ভয়ানক — মাঝামাঝি মানই 5.5 ms (সবসময় ধীর R3-এর অপেক্ষা), p99 63.8 ms, কারণ প্রায় ১ − ০.৯৯³ ≈ ৩% লেখায় অন্তত একটি replica-র ৬০ ms বিরতি পড়ে; আর 597টি লেখা পুরো ব্যর্থ (হিসাবে প্রায় ১ − ০.৯৯³ ≈ ২.৯৭%, অর্থাৎ প্রায় ৫৯৪টি)।
৪ · Quorum থাকলেও linearizable নয় — পাঁচটি ফাঁক
Linearizability (পর্ব ৪০) চায়: সব কপি মিলে যেন একটিই কপি, আর একটি লেখা বা পড়া শেষ হওয়ার পরে শুরু হওয়া প্রতিটি পড়া অন্তত সেই মান বা তার চেয়ে নতুন দেখবে। নতুনদের সবচেয়ে সাধারণ ভুল: “R + W > N, তাই strongly consistent.” অংশ ২-এর প্রমাণের চারটি লুকানো শর্ত মনে করুন — বাস্তবে সেগুলো ভাঙার পাঁচটি পরিচিত পথ:
- চলমান লেখার সাথে পড়া: প্রমাণ শুধু সম্পন্ন লেখার কথা বলে। লেখা চলাকালীন একজন পাঠক নতুন মান, আর তাঁর পরে আসা আরেকজন পুরোনো মান পেতে পারেন — নিচের stepper-এ।
- ব্যর্থ আংশিক লেখা: লেখাটি একটি replica-তে পৌঁছাল, W টিতে নয়; client পেল “ব্যর্থ”। কিন্তু সেই একটি কপি মুছে ফেলা (rollback) হয় না — পরের পড়ায় কখনো সেটি আসে, কখনো আসে না। “ব্যর্থ” লেখাও টিকে যেতে পারে।
- একসাথে দুটি লেখা: একই key-তে দুটি লেখা একসাথে এলে কোনটি “পরে”, তা ঠিক করার কেউ নেই। LWW একটিকে নিঃশব্দে ফেলে দেয়, আর ঘড়ির পার্থক্যে আসলে পরেরটিই হারাতে পারে (অংশ ৭)।
- Sloppy quorum: নির্ধারিত replica-র বদলে অন্য node-এর ack গোনা হলে লেখার দলটি N টির বাইরে চলে যায় — প্রমাণের প্রথম শর্তই ভাঙে (অংশ ৫)।
- নতুন কপি হারানো: নতুন মান পাওয়া একটি replica-র ডিস্ক নষ্ট, আর তাকে একটি পুরোনো কপি থেকে নতুন করে বানানো হলো — নতুন মানের কপি এখন W টির কম, overlap-এর গ্যারান্টি চুপচাপ উধাও।
| সময় | |
| R1 | |
| R2 | |
| R3 | |
| লেখক | |
| পাঠক A | |
| পাঠক B |
- লেখা শুরু। লেখক x = ১ তিনটি replica-কেই পাঠাল। R1-এর পথ দ্রুত; R2 আর R3-এর পথে ভিড়, তাই লেখাটি সেখানে দেরিতে পৌঁছাবে। এখনো সব কপিতে x = ০।
- t = ১ ms: শুধু R1 পেয়েছে। R1 x = ১ লিখে ack দিল। লেখকের দরকার ২টি ack, আছে ১টি — লেখা এখনো “সম্পন্ন” নয়।
- t = ২ ms: পাঠক A পড়ল R1 আর R2. উত্তর (১, ০); সংস্করণ মিলিয়ে নতুনটি ১ — A পেল x = ১। Overlap কাজ করেনি এমন নয়; A ভাগ্যক্রমে R1-কে ছুঁয়েছে।
- t = ৪ ms: পাঠক B পেল পুরোনো ০। B শুরু করেছে A শেষ হওয়ার পরে, পড়ল R2 আর R3 — দুটিতেই ০। একজন নতুন দেখার পরে আরেকজন পুরোনো দেখলেন: linearizability ভাঙল। অথচ R + W > N ভাঙেনি — overlap-এর প্রতিশ্রুতি কেবল সম্পন্ন লেখার জন্য।
- t = ৬ ms: লেখা সম্পন্ন। R2 লেখাটি পেয়ে ack দিল — ২/২, লেখক “OK” পেল। এখন থেকে যেকোনো দুটি replica-র পড়া অন্তত একটি x = ১ ছোঁবে; R3 পরে পাবে, বা মেরামতে।
- প্রতিকার: ফেরার আগে লিখে দেওয়া। A ১ দেখার পর client-কে ফেরার আগে সেই মান W টি replica-তে নিশ্চিত করে (R2-তে লিখে দেয়)। তখন B-র যেকোনো পড়ার দল সেটি ছোঁবে। এটাই blocking read repair; দাম — অমিল পেলে পড়ায় একটি বাড়তি round trip.
ষষ্ঠ ধাপের কৌশলটি তাত্ত্বিকভাবে প্রমাণিত: Attiya, Bar-Noy ও Dolev-এর অ্যালগরিদম (সংক্ষেপে ABD; JACM ১৯৯৫) দেখায়, সংখ্যাগরিষ্ঠ quorum-এ প্রতিটি পড়া ফেরার আগে পাওয়া মান আবার লিখে দিলে একটি একক register-এর পড়া-লেখা linearizable হয় — অর্ধেকের কম node বিকল হলেও। Cassandra-ধাঁচের store-এ QUORUM পড়ায় অমিল পেলে ফেরার আগে মেরামত করা হয় বলে documentation-এ বলা হয়; খুঁটিনাটি সংস্করণভেদে আলাদা। কিন্তু ABD-ও “মান এখনো ৫ থাকলে তবেই নতুন মান লেখা” (compare-and-set), অনন্য username বা সিট বুকিংয়ের মতো কাজ পারে না — সেখানে সবার একমত হওয়া লাগে, অর্থাৎ consensus (পর্ব ৪২)। Cassandra-র “lightweight transaction” এজন্যই ভেতরে Paxos ব্যবহার করে, প্রকাশিত documentation অনুযায়ী।
“Quorum” শব্দটি দুই অর্থে। Raft বা Paxos-এর majority quorum (পর্ব ৪২) একটি leader আর log-এর ক্রমের অংশ, যার linearizability-র প্রমাণ আছে; Dynamo-ধাঁচের quorum শুধু “কয়টি কপির ack”. কোনটি বোঝাচ্ছেন, স্পষ্ট করুন।
৫ · Sloppy quorum আর hinted handoff — বন্ধ replica-র লেখা কে রাখে
D বন্ধ থাকলে কী হয়? N = ৩, W = ২-এ B আর C দিয়েই W পূর্ণ, তাই লেখা সফল। কিন্তু তিনটি কপির বদলে এখন দুটি — আরেকটি বিকল হলে durability ঝুঁকিতে। Dynamo-র উত্তর: preference list-এ পরের সুস্থ node (ring-এ D-র পরে E) লেখাটি সাময়িক রাখবে, সাথে একটি নোট — “আসল মালিক D”. এই নোট বা hint নিয়ে লেখা পৌঁছে দেওয়াই hinted handoff. Paper অনুযায়ী E এমন লেখা একটি আলাদা local database-এ রাখে, মাঝে মাঝে দেখে, আর D ফিরেছে জানলে (failure detection, পর্ব ৪৪) তাকে পাঠিয়ে নিজের কপি মুছে ফেলতে পারে।
- Client
put(cart:42, v2)OK-এর অপেক্ষায় - B
coordinatorpreference list-এর প্রথম - C, D
replica ২, ৩D বন্ধ - E
পরের সুস্থ nodehint রাখে
| সময় | |
| B | |
| C | |
| D | |
| E-র hint ভান্ডার | |
| Client | |
| নির্ধারিত কপিতে v2 |
- লেখা এল। Client একটি কাল্পনিক অনলাইন দোকানের cart:42-এ v2 লিখছে। Coordinator B; preference list [B, C, D]. D-র বিদ্যুৎ নেই, কিন্তু B এখনো তা জানে না।
- t = ২ ms: B নিজে লিখল। B নিজের কপিতে v2 রাখল — W-এর প্রথম ack. তারপর C আর D-কে পাঠাল।
- t = ৪ ms: C-র ack — client “OK”. B আর C মিলে ২টি, W পূর্ণ। D-র উত্তরের অপেক্ষা নেই — সবচেয়ে দ্রুত W টির নিয়ম। এখানে quorum এখনো strict: দুটি ack-ই নির্ধারিত replica-র।
- t = ৩০ ms: D-র timeout, E-কে hint. কপি তিনটি রাখতে B লেখাটি ring-এ পরের সুস্থ node E-কে পাঠাল, সাথে hint “আসল মালিক D”. E এটি নিজের সাধারণ ডেটার সাথে মেশায় না, আলাদা রাখে।
- ১০ মিনিট পরে: D ফিরল। D-র কাছে এখনো v1. এই মুহূর্তে কেউ D আর আরেকটি পুরোনো কপি থেকে পড়লে পুরোনো মান পেতে পারে — তাই D ফেরার সাথে সাথে মেরামত জরুরি। Failure detector বা gossip (পর্ব ৪৪) জানাল D সচল।
- Hint পৌঁছাল। E লেখাটি D-কে দিল, D ack দিল, E hint মুছল — তিনটি নির্ধারিত কপিতেই v2. E নিজে D ফেরার আগে মরলে hint-টি হারাত; তখন ভরসা anti-entropy (অংশ ৬)।
Strict থেকে sloppy — যখন দুটি বন্ধ
এবার C আর D দুটিই বন্ধ। Strict quorum-এ শুধু B সচল — W = ২ হয় না, লেখা ব্যর্থ। Dynamo এখানে আরেক ধাপ যায়: পড়া আর লেখা হয় preference list-এর “প্রথম N টি সুস্থ node”-এ, যারা ring-এ প্রথম N টি না-ও হতে পারে। তাহলে B, E, F লেখা নেয়, B আর E-র ack-এ W = ২ — লেখা সফল। একে বলে sloppy quorum. লাভ: “সবসময় লেখা নেওয়া” প্রায় সত্যি হয়। দাম: ack দেওয়া দলটি (B, E) আর পরে C, D ফিরে এলে পড়ার দলটি (C, D) মেলে না — R + W > N থাকলেও পুরোনো মান। Sloppy quorum তাই durability-র নিশ্চয়তা দেয় (লেখা কোথাও W টি কপিতে আছে), পড়ার নিশ্চয়তা নয়, যতক্ষণ hint পৌঁছায়নি।
| বিষয় | Strict quorum | Sloppy quorum |
|---|---|---|
| কার ack গোনা হয় | শুধু নির্ধারিত N টি replica-র | প্রথম N টি সুস্থ node-এর, বাইরের হলেও |
| দুটি নির্ধারিত replica বন্ধ (N = ৩, W = ২) | লেখা ব্যর্থ | লেখা সফল, hint-সহ |
| R + W > N-এর overlap | সম্পন্ন লেখার জন্য নিশ্চিত | hint পৌঁছানো পর্যন্ত নিশ্চিত নয় |
| Network ভাগ হলে ছোট ভাগ | quorum বানাতে পারে না, থামে | দুই ভাগই লেখা নিতে পারে — পরে conflict |
| উদাহরণ (প্রকাশিত বিবরণ অনুযায়ী) | Cassandra-ধাঁচ: hint থাকে, কিন্তু consistency level-এ গোনা হয় না (ANY ছাড়া) | Dynamo paper; Riak-ধাঁচে default, primary-র ack বাধ্য করার বিকল্পসহ |
Hint চিরকাল রাখা যায় না। Cassandra-ধাঁচে একটি সময়-সীমা থাকে (লেখার সময়ের default প্রায় ৩ ঘণ্টা; সংস্করণভেদে মিলিয়ে নিন), এর বেশি বন্ধ থাকা node-এর লাগে পুরো মেরামত। Hinted handoff তাই ক্ষণিকের বিকলতার ওষুধ; বাকিটা পরের অংশের anti-entropy-র কাজ।
৬ · Read repair আর anti-entropy — Merkle tree দিয়ে অমিল খোঁজা
Read repair — পড়তে পড়তে মেরামত
পড়ার সময় coordinator কয়েকটি replica-র উত্তর তুলনা করে; কোনোটি পুরোনো পেলে নতুন মানটি তাকে লিখে দেয় — read repair. ধরা যাক D কোনোভাবে v2 পায়নি (hint হারিয়েছে), আর সংস্করণ-চিহ্ন একটি version vector: {B:2} মানে B এই key দুবার বদলেছে।
| সময় | |
| B | |
| C | |
| D | |
| Coordinator-এর সিদ্ধান্ত |
- পড়ার অনুরোধ। Coordinator B তিনটি replica-কেই জিজ্ঞেস করল, কিন্তু অপেক্ষা করবে প্রথম দুটি উত্তরের (R = ২)।
- প্রথম দুটি উত্তর। B নিজে দিল v2 {B:2}, D দিল v1 {B:1}. C-র উত্তর এখনো পথে — তার অপেক্ষা নেই।
- সংস্করণ তুলনা। {B:2}-এর প্রতিটি counter {B:1}-এর সমান বা বড়, তাই v2 পরে এসেছে আর v1-কে “দেখে” লেখা। D পুরোনো।
- উত্তর আর মেরামত। Client পেল v2. সাথে B, D-কে v2 লিখে দিল — read repair. Dynamo paper-এ এটি উত্তরের পরে করা হয়; অংশ ৪-এর blocking রূপে উত্তরের আগে।
- দেরিতে আসা উত্তর। C-র উত্তর এল ৯ ms-এ — v2, মিলে গেছে। অনেক ব্যবস্থা এই দেরির উত্তরও দেখে, যাতে সব কপি মেলে।
- ভিন্ন দৃশ্য: একসাথে লেখা। D-তে যদি থাকত v3 {B:1, D:1}? {B:2}-এ D নেই, {B:1, D:1}-এ B মাত্র ১ — কেউ কাউকে ঢাকে না, দুটি লেখা একে অপরকে না জেনে হয়েছে। এখানে “মেরামত” মানে একটি বেছে নেওয়া নয়; দুটিই ফেরত যায় (sibling), মেলায় অ্যাপ — অংশ ৭ আর পর্ব ৪৬।
কিছু ব্যবস্থা bandwidth বাঁচাতে একটি replica-র কাছে পুরো ডেটা আর বাকিদের কাছে শুধু তার hash (digest) চায় (Cassandra-ধাঁচ)। সীমাটি স্পষ্ট: যে key কেউ পড়ে না, সে কখনো ঠিক হয় না — আর সেই ভুল কপিই হয়তো একদিন একমাত্র কপি হবে।
Anti-entropy — পুরো খাতা মেলানো, কিন্তু চালাকি করে
তাই পেছনে চলে আরেকটি প্রক্রিয়া: replica-রা নিয়মিত জোড়ায় জোড়ায় নিজেদের ডেটা তুলনা করে যার যা নেই তা পাঠায় — anti-entropy. সরল পথে প্রতিটি key আর মান পাঠিয়ে তুলনা — কোটি key-তে প্রতি রাউন্ডে বিপুল ডেটা, অথচ সাধারণত অমিল হাতে গোনা কয়েকটি। শুরুর analogy-র তিন ভাইয়ের মতো: আগে মাসের মোট মেলান; মিললে কাজ শেষ। না মিললে দুই সপ্তাহ-জোড়ার মোট, তারপর যে সপ্তাহ মেলেনি তার দিনগুলো।
এই চিন্তার যন্ত্র Merkle tree — Ralph Merkle-এর নামে, ১৯৭০-এর দশকের শেষ দিকে প্রস্তাবিত hash tree. Key-গুলোকে কয়েকটি key range-এ ভাগ করে প্রতিটি পরিসরের ডেটার একটি hash হয় পাতা (leaf); দুটি সন্তানের hash জুড়ে আবার hash করলে বাবা-node; এভাবে ওপরে একটি root. কোনো একটি key বদলালে তার পাতা থেকে root পর্যন্ত পুরো পথের hash বদলায়, বাকি সব একই থাকে। দুটি replica প্রথমে root মেলায়; সমান হলে কাজ শেষ, আলাদা হলে দুই সন্তান মেলায় আর কেবল অমিল সন্তানের নিচে নামে। Dynamo paper অনুযায়ী প্রতিটি node তার রাখা প্রতিটি key range-এর জন্য আলাদা Merkle tree রাখে, আর দুই node একই পরিসরের root মিলিয়ে কাজ শুরু করে। Paper নিজেই একটি দুর্বলতা স্বীকার করে: node যোগ বা বাদ হলে অনেক পরিসর বদলায়, তখন tree নতুন করে হিসাব করতে হয় (পর্ব ১৬-এর স্থির partition কৌশল এই খরচ কমায়)।
নিচে ৬৪টি key, ১৬টি পরিসরে (প্রতিটিতে ৪টি) ভাগ করা — replica A আর B, যাদের মধ্যে মাত্র দুটি key আলাদা (k13 আর k50). Hash হিসেবে FNV-1a 32-bit — সরল, দ্রুত, কিন্তু ক্রিপ্টোগ্রাফিক নয়; শুধু demo-র জন্য। প্রোগ্রাম প্রতিটি স্তরে কোন node-গুলো তুলনা হলো আর কোনগুলো মেলেনি, তা দেখায়:
// Anti-entropy with a Merkle tree: 64 keys in 16 key ranges (4 keys each) on replicas A, B.
// B differs in 2 keys. Hash = FNV-1a 32-bit: simple and NOT cryptographic (demo only).
function fnv1a(s) {
let h = 0x811c9dc5;
for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }
return h.toString(16).padStart(8, "0");
}
const A = {}; for (let k = 0; k < 64; k++) A["k" + k] = "v" + k;
const B = { ...A, k13: "v13-new", k50: "v50-old" }; // the 2 differing keys
function tree(db) { // levels[0] = root ... levels[4] = 16 leaf ranges
let lvl = [];
for (let r = 0; r < 16; r++) {
let s = ""; for (let k = 4 * r; k < 4 * r + 4; k++) s += "k" + k + "=" + db["k" + k] + ";";
lvl.push(fnv1a(s));
}
const levels = [lvl];
while (lvl.length > 1) {
lvl = lvl.filter((_, i) => i % 2 === 0).map((h, j) => fnv1a(h + lvl[2 * j + 1]));
levels.unshift(lvl);
}
return levels;
}
const ta = tree(A), tb = tree(B);
let todo = [0], compared = 0; // node indexes to compare at this level
for (let d = 0; d < ta.length; d++) {
const diff = todo.filter(i => ta[d][i] !== tb[d][i]);
compared += todo.length;
console.log(`level ${d}: compare ${todo.map(i => `#${i} ${ta[d][i]}${ta[d][i] === tb[d][i] ? "=" : "!="}${tb[d][i]}`).join(" ")}`);
todo = d < ta.length - 1 ? diff.flatMap(i => [2 * i, 2 * i + 1]) : diff;
}
console.log(`ranges to sync: ${todo.join(", ")} (keys ${todo.map(r => `k${4 * r}-k${4 * r + 3}`).join(", ")})`);
console.log(`Merkle: ${compared} hashes compared, ${todo.length * 4} keys sent`);
console.log(`naive : all 64 keys (or all 16 range hashes) compared, every round`);
console.log(`2^20 ranges, 2 bad: Merkle <= ${1 + 2 * 2 * 20} hashes, naive ${2 ** 20} range hashes`);আউটপুট:
level 0: compare #0 3ee6e4b3!=ae5fdcf5
level 1: compare #0 ad75a2cb!=37eae559 #1 096a11fe!=2bc2f0d9
level 2: compare #0 cb53b94e!=f9e9d40f #1 0ff8ff83=0ff8ff83 #2 a8948b84=a8948b84 #3 3752ebc0!=97cbd77c
level 3: compare #0 08d173c5=08d173c5 #1 30e1c49c!=aaecabdb #6 8c845ded!=9a619492 #7 1893b2c6=1893b2c6
level 4: compare #2 a69e1fb7=a69e1fb7 #3 43e42c3d!=2c01ac70 #12 5ce96101!=c2f4e959 #13 cc843f3d=cc843f3d
ranges to sync: 3, 12 (keys k12-k15, k48-k51)
Merkle: 15 hashes compared, 8 keys sent
naive : all 64 keys (or all 16 range hashes) compared, every round
2^20 ranges, 2 bad: Merkle <= 81 hashes, naive 1048576 range hashes
level 0-এ root দুটি আলাদা — কোথাও অমিল আছে। level 1-এ দুই অর্ধেকই আলাদা (একটি অমিল প্রথম অর্ধেকে, একটি দ্বিতীয়তে)। level 2-এ চারটি node-এর মধ্যে #1 আর #2 মিলে গেল — সেই দুই চতুর্থাংশের ৩২টি key আর কখনো দেখা হবে না। এভাবে নামতে নামতে পাতার স্তরে অমিল শুধু পরিসর #3 (k12–k15, যেখানে k13) আর #12 (k48–k51, যেখানে k50). মোট 15টি hash-এর তুলনা আর 8টি key পাঠানো, ৬৪টি সব পাঠানোর বদলে। ছোট গাছে লাভ সামান্য মনে হতে পারে — ১৬টি পাতার hash সরাসরি মেলালেও ১৬টি তুলনা। আসল শক্তি দুই জায়গায়: replica দুটি মিলে থাকলে (সাধারণ অবস্থা) মাত্র ১টি তুলনা; আর বড় গাছে — শেষ লাইন: ২২০টি পরিসরে ২টি অমিল খুঁজতে বড়জোর 81টি hash (প্রতি স্তরে অমিল node-গুলোর দুই সন্তান, অর্থাৎ ১ + ২ × ২ × ২০), সরাসরি পথে 1048576টি। একটি সতর্কতা: 32-bit hash-এ দুটি ভিন্ন ডেটার একই hash হওয়া (collision) অসম্ভব নয় — তখন অমিল ধরা পড়ে না; বাস্তব ব্যবস্থা তাই আরও লম্বা ও শক্ত hash ব্যবহার করে।
| কৌশল | কখন চলে | কী ঠিক করে | কী পারে না |
|---|---|---|---|
| Hinted handoff | লেখার সময় replica বন্ধ থাকলে | ক্ষণিকের বিকলতায় হারানো লেখা | hint-রক্ষক মরলে বা সময়-সীমা পেরোলে |
| Read repair | প্রতিটি পড়ায় | প্রায়ই পড়া key দ্রুত | কখনো না-পড়া key |
| Anti-entropy (Merkle tree) | পেছনে, নিয়মিত | সব key, শেষ পর্যন্ত | তাৎক্ষণিক নয়; tree বানাতে CPU আর ডিস্কের খরচ |
৭ · একসাথে হওয়া লেখা আর delete — version vector, sibling আর tombstone
Leaderless-এ দুজন ব্যবহারকারী (বা একজনের দুটি ফোন) একই key-তে প্রায় একসাথে লিখলে দুটি coordinator দুটি লেখা ছড়ায়, আর replica-রা সেগুলো ভিন্ন ক্রমে পায়। শেষে সব কপিকে একই মানে পৌঁছাতে হবে — কোনটিতে? দুটি প্রচলিত উত্তর:
⏱️ Last-write-wins
প্রতিটি লেখার সাথে একটি timestamp; বড়টি জেতে, বাকিটি নিঃশব্দে বাদ (last-write-wins). Cassandra-ধাঁচের store-এ প্রতিটি column-এর জন্য এটিই নিয়ম। সরল, কোনো sibling নেই — কিন্তু একসাথে হওয়া দুটি লেখার একটি হারায়, আর ঘড়ি পিছিয়ে থাকলে পরের লেখাটিই হারাতে পারে।
সহজ, কিন্তু লেখা হারায়🧮 Version vector আর sibling
প্রতিটি মানের সাথে node-প্রতি counter-এর তালিকা। একটি তালিকা অন্যটিকে ঢেকে দিলে (প্রতিটি counter সমান বা বড়) পুরোনোটি বাদ; না ঢাকলে দুটিই রাখা হয় — siblings. Dynamo-র ধাঁচ; Riak-ধাঁচের store-এও পাওয়া যায়।
কিছু হারায় না, মেলানোর দায় অ্যাপেরDynamo-তে এই চক্রটি API-তেই বাঁধা: get(key) একটি মান বা কয়েকটি sibling ফেরত দেয়, সাথে একটি context (তাদের vector clock). অ্যাপ sibling মিলিয়ে (cart-এর ক্ষেত্রে union) put(key, context, value) করলে নতুন লেখাটির vector সব পুরোনোকে ঢেকে দেয়, আর sibling মিলিয়ে যায়। Vector অসীম না বাড়াতে paper একটি সীমা (উদাহরণে ১০টি জোড়া) পেরোলে সবচেয়ে পুরোনো জোড়া ফেলে দেয় — তাত্ত্বিকভাবে ঝুঁকিপূর্ণ, তবে paper-এর ভাষ্যে বাস্তবে সমস্যা দেখা যায়নি। Vector clock আর version vector-এর সূক্ষ্ম পার্থক্য পর্ব ৩৯-এ; sibling না রেখেই নিজে থেকে মিলে যায় এমন data type (CRDT) পর্ব ৪৬-এ। পর্ব ২২-এর multi-leader demo-তে একই নিয়ম চালিয়ে দেখেছেন।
| সংস্করণ ১ | সংস্করণ ২ | সম্পর্ক | ফল |
|---|---|---|---|
{B:1} | {B:2} | ২ ঢেকে দেয় ১-কে | শুধু ২ রাখা |
{B:2} | {B:1, D:1} | কেউ কাউকে ঢাকে না | দুটিই sibling |
{B:2}, {B:1, D:1} | {B:3, D:1} (দুটি দেখে লেখা) | নতুনটি দুটিকেই ঢাকে | শুধু নতুনটি |
Delete — মৃত ডেটা কেন জেগে ওঠে
Leaderless store-এ delete মানে মান সরিয়ে ফেলা নয়; একটি বিশেষ লেখা — tombstone, “এই key মৃত” চিহ্ন (পর্ব ১৯-এর LSM-tree-এ দেখেছেন)। কেন? ধরুন B আর C থেকে মানটি সোজা মুছে দেওয়া হলো, কিন্তু D তখন বন্ধ ছিল। পরে anti-entropy দেখবে: “D-র কাছে আছে, B আর C-র কাছে নেই” — আর কাউকে মুছতে না বলে D-র কপি ফিরিয়ে আনবে! Tombstone থাকলে তুলনায় বোঝা যায় মুছে ফেলাটাই নতুন খবর। কিন্তু tombstone চিরকাল রাখলে ডিস্ক ভরে, তাই একটি grace সময় পরে compaction তাদের ফেলে দেয় — Cassandra-ধাঁচে লেখার সময়ের default প্রায় ১০ দিন। নিয়মটি তাই কড়া: প্রতিটি replica-তে পুরো মেরামত (anti-entropy) grace সময়ের চেয়ে ঘন ঘন চালাতে হবে; নইলে grace পেরোনোর পর ফেরা একটি পুরোনো replica মুছে ফেলা ডেটা আবার ছড়িয়ে দিতে পারে।
৮ · Tunable consistency — প্রতিটি অনুরোধে ONE, QUORUM না ALL
N স্থির থাকে টেবিল বা bucket-এর জন্য, কিন্তু W আর R অনেক ব্যবস্থায় প্রতিটি অনুরোধে বলে দেওয়া যায় — tunable consistency (পর্ব ২০-এ প্রথম দেখেছেন)। Cassandra-ধাঁচের store-এ এর নাম consistency level; documentation-এ প্রচলিত কয়েকটি (তালিকা আর অর্থ সংস্করণভেদে মিলিয়ে নিন):
| Level | কয়টি replica-র উত্তর | RF = ৩ (এক ডেটা সেন্টার) | মন্তব্য |
|---|---|---|---|
ONE | ১টি | W বা R = ১ | দ্রুততম; দুটিতেই ONE হলে overlap নেই |
QUORUM | ⌊মোট RF ÷ ২⌋ + ১ | ২ | লেখা আর পড়া দুটিতেই হলে ২ + ২ > ৩ |
ALL | সবকটি | ৩ | একটি replica বিকলেই কাজ থামে |
LOCAL_QUORUM | নিজের ডেটা সেন্টারের সংখ্যাগরিষ্ঠ | ২ | অন্য ডেটা সেন্টারের round trip এড়ায়; overlap শুধু ভেতরে |
ANY (শুধু লেখা) | যেকোনো node, hint-ও চলে | ১, hint হলেও | সবচেয়ে available; পড়ার কোনো নিশ্চয়তা নেই |
একটি হিসাবের ফাঁদ: দুটি ডেটা সেন্টারে RF ৩ করে মোট ৬টি কপি। QUORUM = ⌊৬ ÷ ২⌋ + ১ = ৪ — অর্থাৎ প্রতিটি অনুরোধে অন্য ডেটা সেন্টারের অন্তত একটি replica-র অপেক্ষা, আর দূরের সংযোগ বিচ্ছিন্ন হলে লেখা থামে। LOCAL_QUORUM = ২, শুধু নিজের ৩টির মধ্যে; দ্রুত আর স্থানীয়, কিন্তু দুই ডেটা সেন্টারের লেখার মধ্যে overlap-এর নিশ্চয়তা নেই। Riak-ধাঁচে প্রতিটি অনুরোধে r, w-এর সাথে primary replica-র ack বাধ্য করার বিকল্প (pr, pw) থাকে বলে documentation-এ বলা হয়।
একটি কাল্পনিক ride-sharing অ্যাপে একই store-এ ডেটাভেদে আলাদা পছন্দ:
| ডেটা | পছন্দ | কারণ |
|---|---|---|
| চালকের সর্বশেষ অবস্থান (প্রতি কয়েক সেকেন্ডে) | লেখা ONE, পড়া ONE | পুরোনো মান কয়েক সেকেন্ডেই বদলায়; latency আর availability আগে |
| শেষ হওয়া ট্রিপের ভাড়া | লেখা QUORUM, পড়া QUORUM | লেখার পর রসিদে ঠিক অঙ্ক দেখাতে হবে; একটি replica বিকলেও চলে |
| ট্রিপের ইতিহাসের তালিকা | লেখা QUORUM, পড়া ONE | কয়েক সেকেন্ড পুরোনো তালিকা চলে, পড়া দ্রুত থাকে |
| একটি ফোন নম্বরে একটিই অ্যাকাউন্ট | consensus-ভিত্তিক শর্তযুক্ত লেখা | “আগে না থাকলে তবেই লেখা” — quorum একা এটি পারে না (অংশ ৪) |
আরও গভীরে: R + W > N-এর ধারণা Dynamo-র অনেক আগের — David Gifford-এর “Weighted Voting for Replicated Data” (SOSP ১৯৭৯), যেখানে প্রতিটি কপির ভোট আলাদা ওজনের হতে পারে। R + W ≤ N-এর “partial quorum” বাস্তবে কতটা পুরোনো মান দেয়, Bailis ও সহকর্মীরা তা সম্ভাবনায় মেপেছেন (“Probabilistically Bounded Staleness”, VLDB ২০১২)। সবচেয়ে দ্রুত উত্তরের অপেক্ষা আর hedged request-এর যুক্তি Dean ও Barroso-র “The Tail at Scale” (CACM ২০১৩)-এ; আর পুরো বিষয়ের পরিচিত পাঠ্য Kleppmann-এর Designing Data-Intensive Applications (২০১৭)-এর replication অধ্যায়।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এমন প্রশ্ন আসতে পারে:
- R + W > N কেন নতুন মান দেয় — প্রমাণ করুন। — দুটি দলের মিলন ≤ N, তাই ছেদ ≥ W + R − N ≥ ১; সেই replica শেষ সম্পন্ন লেখা রাখে।
- N = ৩, W = ২, R = ২-এ কয়টি বিকল সহ্য হয়? — লেখায় N − W = ১, পড়ায় N − R = ১।
- Quorum থাকলে কি linearizable? — না; চলমান লেখা, ব্যর্থ আংশিক লেখা, sloppy quorum, LWW আর হারানো কপি — প্রতিকার blocking read repair বা consensus.
- Hinted handoff আর sloppy quorum-এর পার্থক্য? — Hinted handoff বন্ধ replica-র লেখা অন্যজনের রাখা; sloppy quorum সেই অন্যজনের ack-কে W-এ গোনা।
- Merkle tree anti-entropy-তে কী বাঁচায়? — Root থেকে শুধু অমিল শাখায় নেমে অল্প কয়েকটি hash তুলনায় অমিল পরিসর খোঁজা; মিলে থাকলে একটি তুলনাই যথেষ্ট।
৯ · AI-এর যুগে quorum-নকশা যাচাই
“একটি highly available key-value store নকশা করো” লিখলে AI প্রায় সবসময় বলে: “Cassandra নিন, QUORUM দিন, strong consistency পাবেন, সাথে Kafka আর Redis.” শুনতে পেশাদার; কিন্তু quorum-এর আলোচনায় AI কয়েকটি ভুল প্রায় নিয়ম করে করে:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| “R + W > N মানে strongly consistent বা linearizable” | চলমান লেখা, ব্যর্থ আংশিক লেখা, sloppy quorum, LWW — অংশ ৪-এর পাঁচ ফাঁক | জিজ্ঞেস করুন: লেখা চলাকালীন দুটি পড়ায় কী হবে? Compare-and-set কে করবে? |
| “N = ৪, W = ২, R = ২ — quorum” | ২ + ২ = ৪, N-এর চেয়ে বড় নয়; worst-case overlap শূন্য | অংশ ২-এর প্রোগ্রামে N বদলে চালিয়ে দেখুন |
| বিকলতার ভুল হিসাব: “QUORUM, RF ৩ — দুটি node মরলেও চলবে” | সহ্য করে N − W = ১টি | N − W আর N − R নিজে লিখুন; সম্ভাবনা Σ C(N, k) pk(১ − p)N−k দিয়ে |
| ACID আর CAP-এর consistency গুলিয়ে ফেলা; QUORUM আর LOCAL_QUORUM-কে এক ভাবা | Replica-র মিল আর ডেটার নিয়ম আলাদা প্রশ্ন; বহু ডেটা সেন্টারে QUORUM দূরের ack চায় | ⌊মোট RF ÷ ২⌋ + ১ হিসাব করুন; কোন consistency, লিখিয়ে নিন |
| বানানো default বা সীমা: hint-এর সময়-সীমা, tombstone-এর grace, read repair-এর setting | এসব পণ্য আর সংস্করণভেদে আলাদা, বদলায়ও | অফিসিয়াল documentation; নিজের cluster-এ setting পড়ে দেখা |
| সাধারণ CRUD অ্যাপে Dynamo-ধাঁচের store আর tunable সবকিছু | অপ্রয়োজনীয় জটিলতা; একটি single-leader database অনেক সময় যথেষ্ট (পর্ব ২২) | লেখার হার, availability-র লক্ষ্য আর conflict-এর দাম আগে লিখুন |
AI-কে তথ্য আর শর্ত দিন, আর প্রতিটি সংখ্যা হিসাবসহ চান — যাতে আপনি মিলিয়ে দেখতে পারেন:
AI-এর দেওয়া quorum-নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
W + R − N গুনুন, N − W গুনুন, আর জিজ্ঞেস করুন — লেখা চলাকালীন পড়লে কী হয়?
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। সূত্র মুখস্থ নয়, কোন নকশা কেন বেছে নেওয়া হয় সেটাই আসল।
প্র ০১R + W > N কি Dynamo-র আবিষ্কার? ১৯৭৯ সালের weighted voting কেন W > N/২-ও চাইত, আর Dynamo কেন সেটি ছেড়ে দিল?
না। ১৯৭৯ সালেই দুটি কাজ প্রায় একসাথে এই ধারণা দেয়: Robert Thomas-এর majority consensus (ACM TODS) আর David Gifford-এর “Weighted Voting for Replicated Data” (SOSP). Gifford-এর নকশায় প্রতিটি কপির কয়েকটি ভোট থাকতে পারে — নির্ভরযোগ্য বা দ্রুত সার্ভারের বেশি, দূরের বা দুর্বলের কম। মোট ভোট V; পড়তে লাগে r ভোট, লিখতে w. পাঠ্যবইয়ে শর্ত দুটি সাধারণত এভাবে লেখা হয়: r + w > V, যাতে প্রতিটি পড়া শেষ লেখা ছোঁয়; আর w > V/২, যাতে দুটি লেখার দল মেলে।
দ্বিতীয় শর্তটি কেন? কারণ তখনকার লক্ষ্য ছিল একটি সাধারণ database-এর মতো আচরণ — প্রতিটি লেখা আগের লেখার সংস্করণ-নম্বর দেখে তার পরের নম্বর পায়, তাই সব লেখার একটি সরলরেখার ক্রম তৈরি হয়। দুটি লেখার দল না মিললে দুটি লেখা একই সংস্করণ-নম্বর দাবি করতে পারত, আর ক্রম ভেঙে যেত।
Dynamo-র লক্ষ্য আলাদা: “সবসময় লেখা নেওয়া”। W > N/২ রাখলে N = ৩-এ অন্তত ২টি ack লাগবেই, আর দুটি বিকল হলে cart-এ জিনিস যোগ করা যাবে না। Dynamo তাই একসাথে লেখা ঠেকানোর চেষ্টা ছেড়ে চিনে নেওয়ার পথ নিল — vector clock দিয়ে কোন লেখা কাকে দেখেছে তা বোঝা, আর একসাথে হওয়াগুলো sibling হিসেবে রেখে অ্যাপকে মেলাতে দেওয়া। এতে W = ১-ও বৈধ হলো: একটি node-এ লিখলেই হলো, ক্রমের দায় পরে। আর sloppy quorum দিয়ে “N টি” বলতে নির্ধারিত N টি না বুঝিয়ে প্রথম N টি সুস্থ node বোঝানো হলো।
মূল উপলব্ধি: Quorum-এর প্রতিটি শর্ত একটি নির্দিষ্ট নিশ্চয়তা কেনে — r + w > V কেনে নতুন পড়া, w > V/২ কেনে লেখার ক্রম; কোনটি লাগবে তা ঠিক করে আপনার ব্যবসার কোন ভুল সহ্য হয় আর কোনটি নয়।
প্র ০২Sloppy quorum লেখা নেওয়া বাড়ায় কিন্তু overlap ভাঙে — Dynamo, Cassandra আর Riak এই দর-কষাকষিতে কে কোন দিকে গেছে, আর আপনি কীভাবে বাছবেন?
Dynamo paper খোলাখুলি বলে: সব পড়া-লেখা হয় preference list-এর প্রথম N টি সুস্থ node-এ, যারা ring-এর প্রথম N টি না-ও হতে পারে। কারণটি ব্যবসায়িক — একটি ডেটা সেন্টার বিচ্ছিন্ন হলে বা কয়েকটি সার্ভার একসাথে বিকল হলেও ক্রেতা cart-এ জিনিস যোগ করতে পারবেন। Paper-এর ভাষায় এটি “always writeable” লক্ষ্যের অংশ, আর দামটিও paper মেনে নেয়: এমন সময়ে পড়া নতুন লেখা না-ও দেখতে পারে, আর একসাথে হওয়া সংস্করণ পরে মেলাতে হয়।
Cassandra-ধাঁচের নকশা, প্রকাশিত documentation অনুযায়ী, অন্য দিকে ঝুঁকেছে: বন্ধ replica-র জন্য hint রাখা হয় — durability আর দ্রুত মেরামতের জন্য — কিন্তু QUORUM বা ONE-এর মতো consistency level-এ hint গোনা হয় না; ব্যতিক্রম লেখার ANY level, যেখানে hint-ও যথেষ্ট। ফলে QUORUM-এর অর্থ পরিষ্কার থাকে — নির্ধারিত replica-দের সংখ্যাগরিষ্ঠ — আর দাম হলো, যথেষ্ট নির্ধারিত replica না থাকলে লেখা ব্যর্থ। Riak-ধাঁচের store-এ documentation অনুযায়ী default আচরণ sloppy, কিন্তু প্রতিটি অনুরোধে pr আর pw দিয়ে বলা যায় “এতগুলো primary replica-র উত্তর চাই-ই” — অর্থাৎ দুই পথই খোলা।
বাছবেন কীভাবে? প্রশ্নটি নিজেকে করুন: বিকলতার সময় একটি লেখা প্রত্যাখ্যান করা খারাপ, নাকি পরে কিছুক্ষণ পুরোনো মান দেখানো? Cart-এ যোগ, “like”, sensor-এর মাপ, analytics-এর event — প্রত্যাখ্যান খারাপ, sloppy চলে। লেখার পরেই যে মান পড়ে সিদ্ধান্ত নেওয়া হয় — অর্ডারের অবস্থা, রসিদ — সেখানে strict.
মূল উপলব্ধি: Sloppy quorum হলো availability-র পক্ষে একটি সচেতন বাজি — লেখা কোথাও নিরাপদে থাকে, কিন্তু “কোথায়” আর “কখন সবাই জানবে” প্রশ্ন দুটি মেরামত-ব্যবস্থার হাতে চলে যায়।
প্র ০৩LWW আর tombstone — Jepsen-এর বিশ্লেষণে হারানো লেখা আর “জেগে ওঠা” মুছে ফেলা ডেটা থেকে leaderless store চালানোর কী শিক্ষা?
২০১৩ সালে Kyle Kingsbury তাঁর Jepsen সিরিজে কয়েকটি distributed database-এ network ভাগ আর একসাথে লেখার পরীক্ষা চালিয়ে প্রকাশ করেন। Cassandra-র বিশ্লেষণে (সেই সময়ের সংস্করণে) মূল পর্যবেক্ষণগুলোর একটি ছিল: একই key-তে একসাথে অনেক লেখা হলে last-write-wins-এর নিয়মে “OK” পাওয়া লেখার উল্লেখযোগ্য অংশ নিঃশব্দে হারিয়ে যেতে পারে — কোনো error ছাড়াই। এটি কোনো bug নয়, LWW-এর সংজ্ঞাই: দুটি একসাথে হওয়া লেখার একটি ফেলে দেওয়া হবে।
দ্বিতীয় ফাঁদটি delete-এ। LSM-ভিত্তিক leaderless store-এ মোছা মানে একটি tombstone লেখা; tombstone ফেলা যায় grace সময় পেরোলে (Cassandra-ধাঁচে লেখার সময়ের default প্রায় ১০ দিন)। এখন ধরুন একটি replica ১২ দিন বন্ধ ছিল, বা চালু থাকলেও কোনো কারণে তার মেরামত ১২ দিন চলেনি, আর সে delete-টি পায়নি। এর মধ্যে বাকিরা tombstone ফেলে দিয়েছে। পরের anti-entropy দেখবে: “এর কাছে একটি মান আছে, অন্যদের কাছে কিছুই নেই” — আর মানটি সবার কাছে ফিরিয়ে দেবে। মুছে ফেলা ব্যবহারকারীর তথ্য, বাতিল করা অর্ডার আবার দেখা দেয়; চালকেরা একে প্রায়ই “zombie data” বলেন।
শিক্ষা তিনটি। প্রথম, conflict-এর নিয়ম ডেটার অর্থ দেখে বাছতে হয় — যে ডেটায় একসাথে লেখা হয়, সেখানে LWW মানে পরিকল্পিত ডেটা হারানো। দ্বিতীয়, anti-entropy কোনো ঐচ্ছিক রক্ষণাবেক্ষণ নয়, শুদ্ধতার শর্ত: প্রতিটি replica-তে পুরো মেরামত grace সময়ের চেয়ে ঘন ঘন চালানো আর তা পর্যবেক্ষণ করা (পর্ব ৫০)। তৃতীয়, দীর্ঘ সময় বন্ধ থাকা node-কে সরাসরি ফিরিয়ে না এনে নতুন করে বানানো নিরাপদ।
মূল উপলব্ধি: Leaderless store-এর শুদ্ধতা শুধু W আর R-এ নয় — conflict-এর নিয়ম, tombstone-এর জীবনকাল আর মেরামতের সময়সূচি মিলেই ঠিক করে কোন ডেটা টিকবে আর কোনটি ফিরে আসবে।
প্র ০৪“Eventually consistent” কতটা eventual? R + W ≤ N-এর partial quorum বাস্তবে কেন এত চলে, আর পুরোনো পড়া কীভাবে মাপবেন?
Eventual consistency-র সংজ্ঞা কোনো সময়-সীমা দেয় না — নতুন লেখা থামলে “কোনো এক সময়” সব কপি মিলবে। অথচ বহু ব্যবস্থা latency বাঁচাতে R = W = ১-এর মতো partial quorum চালায়, আর ব্যবহারকারীরা সাধারণত সমস্যা টের পান না। কেন?
Bailis, Venkataraman, Franklin, Hellerstein ও Stoica-র “Probabilistically Bounded Staleness for Practical Partial Quorums” (VLDB ২০১২) প্রশ্নটিকে সম্ভাবনায় নিয়ে আসে: একটি লেখা সম্পন্ন হওয়ার t ms পরে একটি পড়া নতুন মান পাবে, তার সম্ভাবনা কত? উত্তর নির্ভর করে লেখা replica-দের কাছে পৌঁছানোর দেরির বণ্টনের ওপর। লেখা আসলে N টির সবাইকে পাঠানো হয়; W শুধু অপেক্ষা ঠিক করে। তাই বাকি replica-রাও সাধারণত কয়েক ms-এর মধ্যে লেখাটি পায়, আর “পুরোনো পড়ার জানালা” ছোট। তাঁরা কয়েকটি প্রতিষ্ঠানের প্রকাশিত latency-র মাপ দিয়ে মডেল চালিয়ে দেখান, অনেক ক্ষেত্রে partial quorum-ও লেখার অল্প পরেই খুব বড় সম্ভাবনায় নতুন মান দেয় — তবে লেজ থাকে, আর ধীর replica সেটি লম্বা করে।
অংশ ৩-এর সিমুলেটর এর ছোট রূপ: (১, ১)-এ লেখার মাত্র ১ ms পরে পড়ায় প্রায় ৪.৪% পুরোনো। পাঠক যদি লেখার কয়েকশো ms পরে আসেন (যা মানুষের ক্ষেত্রে সাধারণ), হারটি অনেক কমবে; কিন্তু একই ব্যবহারকারী লিখে সাথে সাথে পড়লে (“পোস্ট করে পাতা খুললেন”) সমস্যা চোখে পড়ে — তখন read-your-writes-এর প্রতিকার লাগে (পর্ব ২২)।
মাপবেন কীভাবে? লেখার পর নির্দিষ্ট বিরতিতে পড়ে পুরোনো উত্তরের ভাগ গুনুন, replica-প্রতি প্রয়োগের দেরির percentile রাখুন, আর read repair-এর হার দেখুন — হঠাৎ বাড়লে কোনো replica পিছিয়ে পড়ছে।
মূল উপলব্ধি: “Eventual” একটি প্রতিশ্রুতি নয়, একটি বণ্টন — নকশায় সেই বণ্টন মাপুন, তার লেজ কোথা থেকে আসে খুঁজুন, আর যেখানে লেজ সহ্য হয় না সেখানে W, R বা read-your-writes দিয়ে নিশ্চয়তা কিনুন।
প্র ০৫Amazon নিজেই পরে DynamoDB সেবায় প্রতিটি partition-এ একটি leader আর Paxos ব্যবহার করেছে বলে প্রকাশ করেছে — তাহলে leaderless নকশা কি ভুল ছিল?
২০২২ সালের USENIX ATC সম্মেলনে Elhemali ও সহকর্মীরা “Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service” প্রকাশ করেন। নামের মিল থাকলেও সেখানে বর্ণিত নকশা ২০০৭ সালের Dynamo থেকে আলাদা: প্রতিটি partition-এর কয়েকটি replica একটি দল, দলটি Multi-Paxos দিয়ে একটি leader বাছে; লেখা যায় leader-এ, আর replica-দের একটি quorum log-এ রাখলে লেখা সফল। Strongly consistent পড়া leader-এর কাছ থেকে, আর eventually consistent পড়া যেকোনো replica থেকে — paper-এর বিবরণ অনুযায়ী।
এর মানে leaderless নকশা ভুল ছিল না; দুটি ভিন্ন প্রশ্নের দুটি উত্তর। ২০০৭-এর Dynamo ছিল Amazon-এর ভেতরের কয়েকটি সেবার জন্য একটি key-value store, যাদের প্রধান কাজ সরল put আর get, আর যারা sibling মেলানোর যুক্তি নিজেরাই লিখতে রাজি ছিল। একটি সাধারণ মানুষের জন্য খোলা, fully managed সেবায় অবস্থা অন্য: হাজারো ভিন্ন গ্রাহক শর্তযুক্ত লেখা (“এই মান থাকলে তবেই বদল” ধাঁচের API), পড়ার পরিষ্কার নিশ্চয়তা আর পরে transaction চান; প্রত্যেককে sibling মেলাতে বলা কঠিন অভিজ্ঞতা। অংশ ৪-এ দেখেছেন, compare-and-set আর অনন্যতার জন্য quorum যথেষ্ট নয় — একটি ক্রম লাগে, আর ক্রমের জন্য leader বা consensus.
দাম কোথায় গেল? Leader বিকল হলে নতুন leader বাছা পর্যন্ত সেই partition-এ লেখা থামে — সেকেন্ডের হিসাবে ছোট, কিন্তু শূন্য নয়; leaderless নকশায় এই বিরতিটুকুই থাকে না। আবার প্রতিটি লেখা একটি leader-এর ভেতর দিয়ে যায় বলে ধীর leader পুরো partition-কে ধীর করে, যেখানে leaderless নকশা সবচেয়ে দ্রুত W টির উত্তর নিয়ে লেজ কাটে।
মূল উপলব্ধি: Leaderless আর leader-ভিত্তিক নকশা প্রতিদ্বন্দ্বী মতবাদ নয়, ভিন্ন দামের যন্ত্র; একটি পরিণত ব্যবস্থা প্রায়ই দুটিই রাখে — যেখানে ক্রম লাগে সেখানে consensus, যেখানে সবসময় লেখা নেওয়া আর ছোট লেজ লাগে সেখানে quorum.
অনুশীলন
খাতা-কলম আর উপরের সিমুলেটর পাশে রাখুন। আগে নিজে করুন, তারপর উত্তর মিলিয়ে নিন।
-
বিকলতায় quorum-এর হিসাব: একটি কাল্পনিক order-history store-এ প্রতিটি key-এর ৫টি কপি (N = ৫)। প্রতিটি replica স্বাধীনভাবে ৯৮% সময় সচল ধরুন, আর মাস মানে ৩০ দিন (৪৩,২০০ মিনিট)। (ক) W = ৩, R = ৩-এ কয়টি replica বিকল হলেও লেখা আর পড়া চলবে, আর লেখার availability কত? মাসে মোটামুটি কত মিনিট লেখা থামবে? (খ) কেউ প্রস্তাব দিলেন “নিরাপদ থাকতে W = ALL”. তখন availability আর মাসে থেমে থাকার সময় কত? (গ) N = ৩, W = ২-এর সাথে তুলনা করুন। (ঘ) কোন বাস্তব কারণে এই হিসাব খুব আশাবাদী হতে পারে?
(ক) লেখা সহ্য করে N − W = ২টি, পড়াও N − R = ২টি বিকল। R + W = ৬ > ৫, তাই overlap আছে (অন্তত ১টি)। Availability = P(৫টির মধ্যে অন্তত ৩টি সচল) = Σk=৩..৫ C(৫, k) ০.৯৮k ০.০২৫−k ≈ ০.৯৯৯৯২২৪. থামার সম্ভাবনা ≈ ০.০০০০৭৭৬, মাসে ৪৩,২০০ × ০.০০০০৭৭৬ ≈ ৩.৪ মিনিট।
(খ) W = ৫: ০.৯৮৫ ≈ ০.৯০৩৯ — প্রায় ৯.৬% লেখা ব্যর্থ, মাসে প্রায় ৪,১৫১ মিনিট, অর্থাৎ প্রায় ৬৯ ঘণ্টা! “নিরাপদ” প্রস্তাবটি একটি replica-র (২% বিভ্রাট) চেয়েও প্রায় পাঁচ গুণ খারাপ। Durability বাড়াতে চাইলে W = ৩-ই যথেষ্ট — লেখা অন্তত তিনটি কপিতে, বাকিরা পরে পায়।
(গ) N = ৩, W = ২: ৩ × ০.৯৮² × ০.০২ + ০.৯৮³ ≈ ০.৯৯৮৮১৬ — মাসে প্রায় ৫১ মিনিট থামা। N = ৫-এ দুটি বাড়তি কপির দামে থামার সময় প্রায় ১৫ গুণ কমে, আর একসাথে দুটি বিকল সহ্য হয়।
(ঘ) হিসাব ধরে নেয় ব্যর্থতাগুলো স্বাধীন। একই rack বা zone-এর বিদ্যুৎ, সব node-এ ছড়ানো ভুল config, বা upgrade-এ দুটি node একসাথে বন্ধ — এসবে সম্ভাবনা বহুগুণ বাড়ে। তাই replica ভিন্ন failure domain-এ রাখা আর upgrade-এ একবারে একটি node বন্ধ করা জরুরি।
-
একটি নকশার সমালোচনা: একটি কাল্পনিক mobile wallet সেবার দল প্রস্তাব দিল: “ব্যালান্স রাখব একটি Cassandra-ধাঁচের leaderless store-এ, N = ৩, লেখা আর পড়া দুটিই ONE, conflict হলে last-write-wins. এতে সেবা সবসময় চালু থাকবে আর খুব দ্রুত হবে।” টাকা পাঠানোর সময় অ্যাপ ব্যালান্স পড়ে, যথেষ্ট থাকলে নতুন ব্যালান্স লেখে। নকশাটির সমস্যাগুলো বের করুন আর একটি ভালো বিকল্প দিন। কোন ডেটায় leaderless store এই সেবাতেও মানাবে?
- Overlap নেই: ONE + ONE = ২, N = ৩-এর চেয়ে বড় নয়। টাকা পাঠানোর পরপরই পড়লে পুরোনো ব্যালান্স আসতে পারে (অংশ ৩-এর সিমুলেটরে লেখার ১ ms পরে প্রায় ৪.৪%).
- “আগে পড়া, তারপর লেখা”-র দৌড়: দুটি ফোন থেকে একসাথে ১০০ টাকার ব্যালান্সে ৮০ টাকা করে পাঠানো — দুটিই ১০০ পড়ে, দুটিই ২০ লেখে। QUORUM দিলেও এটি থামে না; একসাথে হওয়া লেখার ক্রম ঠিক করার কেউ নেই (অংশ ৪)।
- LWW নিঃশব্দে হারায়: একসাথে একটি জমা আর একটি খরচ হলে একটি লেখা টিকে, অন্যটি উধাও — কোনো error ছাড়াই; ঘড়ির পার্থক্য আরও খারাপ করে।
বিকল্প: ব্যালান্স আর লেনদেন একটি single-leader database-এ (সংখ্যাগরিষ্ঠের ack বা semi-sync, পর্ব ২২), শর্তযুক্ত লেখা বা transaction দিয়ে (“ব্যালান্স ≥ ৮০ হলে তবেই কমানো”), প্রতিটি অনুরোধে idempotency key (পর্ব ৮), আর একটি double-entry ledger (পর্ব ৬০)। Leaderless store বড় পরিসরে চাইলে অন্তত শর্তযুক্ত লেখায় consensus-ভিত্তিক পথ (Cassandra-ধাঁচের lightweight transaction). যেখানে মানায়: notification-এর ইতিহাস, অ্যাপ খোলার event, ডিভাইসের সর্বশেষ সেটিং — যেখানে সবসময় লেখা নেওয়া জরুরি আর কিছুক্ষণ পুরোনো মান ক্ষতিকর নয়।
-
Output অনুমান করুন: অংশ ২-এর
quorum_overlap.js-এ লাইনটিfor (const N of [3, 5])বদলেfor (const N of [4])করা হলো। চালানোর আগে অনুমান করুন: (ক) ঘরের সংখ্যাগুলো কী হবে, (খ) শেষ লাইনে কোন জোড়াগুলো আসবে, (গ) W = ২, R = ২ কি নিরাপদ? তারপর চালিয়ে মিলিয়ে নিন, আর ভাবুন কেন N সাধারণত বিজোড় রাখা হয়।প্রতিটি ঘর max(০, W + R − ৪); লেখা সহ্য করে ৪ − W, পড়া ৪ − R. আসল আউটপুট:
N = 4: worst-case overlap of write set and read set (0 = stale read possible) R=1 R=2 R=3 R=4 write works while W=1 0 0 0 1 3 replicas down W=2 0 0 1 2 2 replicas down W=3 0 1 2 3 1 replicas down W=4 1 2 3 4 0 replicas down read OK 3 2 1 0 <- read works while this many down smallest always-overlapping (W,R): (1,4) (2,3) (3,2) (4,1)(গ) না —
W=2,R=2-এর ঘরে0: {R1, R2}-তে লেখা আর {R3, R4} থেকে পড়া সম্ভব। Overlap-এর জন্য লাগে (৩, ২), (২, ৩) বা (৩, ৩)। এখন তুলনা: N = ৪-এ (৩, ২)-এ লেখা সহ্য করে মাত্র ১টি বিকল, (৩, ৩)-এ পড়া-লেখা দুটিই ১টি — ঠিক N = ৩-এর (২, ২)-এর মতো, অথচ একটি বাড়তি কপির ডিস্ক আর নেটওয়ার্কের খরচ। এজন্যই N সাধারণত ৩ বা ৫। -
Tail latency আর Merkle tree-র হিসাব: (ক) N = ৩; প্রতিটি replica স্বাধীনভাবে ৫% অনুরোধে ৫০ ms-এর বেশি সময় নেয়। W = ১, ২ আর ৩-এ একটি লেখা ৫০ ms-এর বেশি নেওয়ার সম্ভাবনা কত, আর ১,০০,০০০টি লেখার মধ্যে মোটামুটি কয়টি? (খ) দুটি replica-র Merkle tree-তে ২১৬ = ৬৫,৫৩৬টি পাতা (key range). ৩টি পরিসরে অমিল, আর অমিলগুলো এমনভাবে ছড়ানো যে তুলনা সবচেয়ে বেশি লাগে। Root থেকে শুরু করে সবচেয়ে বেশি কয়টি hash তুলনা লাগবে? সরাসরি সব পাতা মেলালে কয়টি?
(ক) লেখা ধীর মানে দ্রুত replica W টির কম। W = ১: তিনটিই ধীর — ০.০৫³ = ০.০০০১২৫ → ১,০০,০০০-এ প্রায় ১২.৫টি। W = ২: অন্তত দুটি ধীর — ৩ × ০.০৫² × ০.৯৫ + ০.০৫³ = ০.০০৭২৫ → প্রায় ৭২৫টি। W = ৩: অন্তত একটি ধীর — ১ − ০.৯৫³ = ০.১৪২৬২৫ → প্রায় ১৪,২৬৩টি। W = ৩ মানে p৯৯ তো বটেই, p৯০-ও ৫০ ms-এর ওপরে।
(খ) স্তর ০-এ root: ১টি তুলনা। স্তর ১-এ মোটে ২টি node — দুটিই তুলনা (২টি)। স্তর ২-এ অমিল ২টি node-এর ৪টি সন্তান (৪টি); অমিল তিনটি ভিন্ন চতুর্থাংশে হলে এখানে ৩টি অমিল। স্তর ৩ থেকে স্তর ১৬ পর্যন্ত ১৪টি স্তরের প্রতিটিতে ৩টি অমিল node-এর ৬টি সন্তান — ১৪ × ৬ = ৮৪। মোট ১ + ২ + ৪ + ৮৪ = ৯১টি (সরল ঊর্ধ্বসীমা ১ + ২ × ৩ × ১৬ = ৯৭)। সরাসরি পথে ৬৫,৫৩৬টি পাতার hash — প্রায় ৭২০ গুণ বেশি। আর পাঠাতে হবে শুধু ৩টি পরিসরের ডেটা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমLeaderless store-এ একটি নির্ধারিত replica সাময়িক বন্ধ থাকলে hinted handoff-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📤 Coordinator লেখাটি নির্ধারিত replica D-কে পাঠায়, কিন্তু সাড়া আসে না
- 📝 Ring-এ পরের সুস্থ node E লেখাটি নেয়, সাথে “আসল মালিক D” hint
- 🗂️ E hint-টি নিজের সাধারণ ডেটা থেকে আলাদা রাখে আর মাঝে মাঝে দেখে
- 💓 Failure detection থেকে জানা যায় D আবার সচল
- ✅ E লেখাটি D-কে পৌঁছে দেয় আর নিজের hint মুছে ফেলে
একটি leaderless store-এ প্রতিটি key-এর ৫টি কপি (N = ৫), লেখা সফল ৩টি ack-এ (W = ৩), পড়া শেষ ২টি উত্তরে (R = ২)। সফল লেখার পরে শুরু হওয়া পড়া কি সবসময় সেই লেখাটি দেখবে?
- হ্যাঁ, কারণ W সংখ্যাগরিষ্ঠ (৩ > ৫/২)
- না, কারণ W আর R সমান নয়
- না; W + R = N, তাই লেখার দল আর পড়ার দল একটিও replica-তে না মিলতে পারে
- শুধু sloppy quorum বন্ধ থাকলে হ্যাঁ
N = ৩, W = ২, R = ২-এর strict quorum ব্যবস্থায় একটি key-এর কয়টি নির্ধারিত replica বিকল থাকলেও লেখা আর পড়া দুটিই চলবে?
- একটিও না — যেকোনো বিকলতায় লেখা থামে
- সর্বোচ্চ ১টি
- সর্বোচ্চ ২টি
- তিনটিই — hinted handoff সব সামলায়
Sloppy quorum সম্পর্কে কোন কথাটি ঠিক?
- এটি নির্ধারিত replica-র বাইরের node-এর ack-ও গোনে, তাই লেখা বেশি সময় চলে, কিন্তু R + W > N হলেও পড়ায় নতুন মানের নিশ্চয়তা থাকে না
- এটি R + W > N-কে আরও শক্ত করে, তাই linearizability আসে
- এতে hinted handoff বা anti-entropy-র আর দরকার থাকে না
- এতে একসাথে হওয়া দুটি লেখা আর কখনো ঘটে না
দুটি replica-র Merkle tree-তে ১,০২৪টি পাতা (key range), আর ঠিক একটি পরিসরে অমিল। Root থেকে শুরু করে শুধু অমিল শাখায় নামলে মোট কয়টি hash তুলনা লাগবে?
- ১,০২৪
- ১১
- ১০
- ২১
N = ৩; প্রতিটি replica স্বাধীনভাবে ১০% অনুরোধে ধীর। লেখা সবচেয়ে দ্রুত W টি ack-এর অপেক্ষা করে। W = ৩ আর W = ২-এ একটি লেখা ধীর হওয়ার সম্ভাবনা কত?
- ৩০% আর ২০%
- দুটিতেই ১০%, কারণ প্রতিটি replica ১০% ধীর
- প্রায় ২৭.১% আর ২.৮%
- প্রায় ২৭.১% আর ০.১%
একটি leaderless store-এ N = ৩, W = ২, R = ২। একটি লেখা x = ১ একটি replica-তে পৌঁছেছে, বাকি দুটিতে এখনো পথে। এই অবস্থায় পাঠক A দুটি replica থেকে পড়ে x = ১ পেলেন; A শেষ হওয়ার পরে পাঠক B অন্য দুটি থেকে পড়ে x = ০ পেলেন। কোন কথাটি ঠিক?
- এটি অসম্ভব, কারণ R + W > N
- এটি সম্ভব — quorum linearizability দেয় না; A ফেরার আগে মানটি W টি replica-তে লিখে দিলে এটি ঠেকানো যায়
- এটি শুধু ঘড়ির পার্থক্যের কারণে ঘটে; NTP চালালে মিটবে
- এটি সম্ভব, আর একমাত্র প্রতিকার R = ৩ আর W = ৩ করা
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Leaderless নকশায় key-এর preference list-এর N টি replica; যেকোনো node coordinator হয়ে সবাইকে পাঠায়, W টি ack-এ লেখা আর R টি উত্তরে পড়া শেষ।
- R + W > N হলে লেখার দল আর পড়ার দল অন্তত W + R − N টিতে মেলে; লেখা সহ্য করে N − W, পড়া N − R টি বিকল। সবার ack চাওয়া availability আর tail latency দুটিই নষ্ট করে।
- Quorum linearizable নয়: চলমান লেখা, ব্যর্থ আংশিক লেখা, sloppy quorum, LWW আর হারানো কপি; blocking read repair পড়া-লেখা ঠিক রাখে, compare-and-set লাগলে consensus.
- মেরামত তিন স্তরে: hinted handoff ক্ষণিকের বিকলতায়, read repair পড়া key-এ, Merkle tree-ভিত্তিক anti-entropy সব key-এ — শুধু অমিল শাখায় নেমে।
- একসাথে হওয়া লেখা চেনে version vector আর sibling, LWW একটি ফেলে দেয়; delete-এর tombstone grace সময়ের আগেই সব replica-তে মেরামত করে পৌঁছাতে হয়; consistency level প্রতিটি অনুরোধে ডেটার অর্থ দেখে বাছুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪২ · Consensus — Raft-এর leader election আর log replication, Paxos-এর ধারণাপরবর্তী পর্বযেখানে quorum থামে, সেখান থেকে — একটি ক্রমে সবার একমত হওয়া।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ১৬ · Consistent hashing — ring, virtual node আর rendezvous hashingফিরে দেখুনRing, token আর preference list কীভাবে বানানো হয়।
- পর্ব ২২ · Replication — leader-follower, multi-leader, leaderless আর replication lagফিরে দেখুনতিন নকশার তুলনা, replication lag আর conflict-এর মূল ধারণা।
- Distributed database — partitioning, sharding আর CAPপাশের ট্র্যাকDBMS কোর্সে quorum, consensus আর CAP — database-এর চোখে।
- Hash tableগভীরে যানDSA কোর্সে hash function আর collision — Merkle tree-র hash বোঝার ভিত্তি।