সরাসরি পর্বে যান
পর্ব ৪০ · ৬৬-এর মধ্যে · মডিউল ৭

CAP, PACELC আর consistency model — linearizable থেকে eventual

CAP, PACELC & Consistency Models — Linearizable, Sequential, Causal, Session Guarantees & Eventual

Taught in Bangla, this lesson defines registers, histories and consistency models, states the CAP theorem precisely with the reasoning of the Gilbert–Lynch proof, corrects five common misreadings, adds PACELC and the latency price of linearizability measured in a partition simulator, climbs the ladder from linearizable, sequential and causal to session guarantees and eventual consistency, checks a history by hand, and contrasts consistency with isolation.

এই পর্বে যা শিখবেন

  • Register, history আর consistency model-এর ভাষা — “সর্বশেষ মান” কথাটির নিখুঁত অর্থ
  • CAP-এর সংজ্ঞা আর Gilbert–Lynch-এর প্রমাণের যুক্তি, আর পাঁচটি প্রচলিত ভুল ব্যাখ্যা
  • PACELC আর linearizability-র latency-দাম, একটি partition সিমুলেটরে availability মেপে
  • Linearizable, sequential, causal, session guarantee আর eventual-এর পার্থক্য; একটি history-কে নিজে পরীক্ষা করা
  • Consistency বনাম isolation, আর একটি অ্যাপের প্রতিটি feature-এর জন্য ঠিক model বাছাই

১ · “সর্বশেষ মান” বলতে আসলে কী বোঝায় — register, history আর একটি চুক্তি

পাড়ার একটি মুদি দোকানের দুটি শাখা, দুই শাখায় দুটি খাতা। সকালে মালিক প্রথম শাখায় চালের দাম ৬৮ থেকে ৭২ টাকা করলেন, তারপর দ্বিতীয় শাখায় ফোন করলেন — ফোন লাগল না। দ্বিতীয় শাখায় একজন ক্রেতা জিজ্ঞেস করলেন, “চাল কত?” দোকানদার কী বলবেন? খাতার পুরোনো ৬৮ বলবেন, নাকি “একটু দাঁড়ান, মালিককে জিজ্ঞেস করে বলছি”? আর যদি ক্রেতা একটু আগেই প্রথম শাখায় ৭২ দেখে এসে থাকেন? এই প্রশ্নগুলোর উত্তর যে নিয়মে ঠিক হয়, distributed system-এ তার নাম consistency model.

পর্ব ২২-এ (Replication) দেখেছেন, একই ডেটার কয়েকটি কপি রাখলে কপিগুলো কিছুক্ষণ আলাদা থাকে। এই পর্বের প্রশ্ন আরও মৌলিক: কপিগুলো আলাদা থাকার সময়ে একটি পড়া কোন কোন মান ফেরত দিলে আমরা সেটিকে “ঠিক” বলব? উত্তর দিতে তিনটি শব্দ লাগবে।

  • Register: সবচেয়ে সরল ডেটা — একটি ঘর, যেখানে একটি মান থাকে। দুটি কাজ: w(v) মানে মান v লেখা, r→v মানে পড়ে v পাওয়া। একটি key-value store-এর প্রতিটি key একেকটি register.
  • কাজের সময়-পরিসর: network-এর কারণে কোনো কাজ এক মুহূর্তে ঘটে না। Client অনুরোধ পাঠায় (শুরু), কিছু পরে উত্তর পায় (শেষ)। মাঝের সময়ে কাজটি “চলছে” — client জানে না ঠিক কখন সার্ভারে প্রয়োগ হলো। দুটি কাজের পরিসর একটি আরেকটির ওপর পড়লে তারা একসাথে চলা (concurrent); একটি শেষ হওয়ার পর আরেকটি শুরু হলে প্রথমটি আগে।
  • History: সব client-এর সব কাজের তালিকা, শুরু আর শেষের সময়সহ। এই পর্বে লিখব A: w(1) [0, 10] — client A মান 1 লিখল, অনুরোধ ০ ms-এ গেল, উত্তর এল ১০ ms-এ।

একটি consistency model আসলে একটি চুক্তি: কোন history বৈধ আর কোনটি নয়। চুক্তি যত কড়া, প্রোগ্রামারের জীবন তত সহজ — “একটিই কপি আছে” ভেবে কোড লেখা যায়। কিন্তু কড়া চুক্তি রাখতে কপিগুলোকে বেশি কথা বলতে হয়: তাতে সময় লাগে, আর network ভাঙলে কিছু অনুরোধ ফেরাতে হয়। CAP theorem এই দর-কষাকষির সবচেয়ে বিখ্যাত রূপ।

একটি শব্দ, তিনটি অর্থ — শুরুতেই আলাদা রাখুন। ACID-এর C মানে অ্যাপের নিয়ম রক্ষা (পর্ব ২-এর requirement আর trade-off, পর্ব ২৪-এর transaction আর isolation দেখুন)। CAP-এর C মানে linearizability. আর “eventual consistency” তৃতীয় এক অর্থ। এই পর্বের প্রতিটি “consistency” কপিগুলোর মিলের অর্থে; ACID-এর অর্থ এখানে নেই।

২ · CAP theorem নিখুঁতভাবে — Brewer-এর অনুমান আর Gilbert–Lynch-এর প্রমাণ

২০০০ সালে PODC সম্মেলনের মূল বক্তৃতায় Eric Brewer অনুমান (conjecture) দেন: ডেটা ভাগ করে রাখা একটি web সেবা একসাথে consistency, availability আর partition tolerance দিতে পারে না। ২০০২ সালে Seth Gilbert আর Nancy Lynch অনুমানটি নির্দিষ্ট সংজ্ঞায় বেঁধে প্রমাণ করেন। প্রমাণটি সত্য কেবল সেই সংজ্ঞাগুলোর ভেতরে, তাই সংজ্ঞাগুলো হুবহু জানা দরকার:

  • C — consistency: প্রমাণে এর নাম atomic consistency, আজকের ভাষায় linearizability. প্রতিটি কাজ যেন তার শুরু আর শেষের মাঝের এক মুহূর্তে ঘটেছে, আর সব কাজ মিলে এমন একটি ক্রম আছে যা বাস্তব সময় মানে। ফল: একটি লেখা শেষ হওয়ার পর শুরু হওয়া প্রতিটি পড়া সেই লেখা (বা নতুনতর কিছু) দেখবে, যে কপি থেকেই পড়ুক।
  • A — availability: চালু থাকা প্রতিটি node-এ আসা প্রতিটি অনুরোধ শেষ পর্যন্ত একটি উত্তর পাবে — error নয়, আসল উত্তর। সংজ্ঞায় সময়সীমা নেই, কিন্তু “অনন্তকাল অপেক্ষা” মানে উত্তর নেই।
  • P — partition tolerance: network যত খুশি বার্তা হারাতে পারে — দুই দলের মাঝের সব বার্তাও। তবুও সিস্টেমকে C আর A-র প্রতিশ্রুতি রাখতে হবে। দলে ভাগ হওয়ার ঘটনাটিই network partition.

প্রমাণের যুক্তি — পাঁচ লাইনে

  1. Node-গুলো দুটি দলে ভাগ হলো: G1 আর G2; মাঝের সব বার্তা হারায়। Register-এর শুরুর মান v0.
  2. একজন client G1-এ w(v1) পাঠালেন। A বলে G1-কে উত্তর দিতেই হবে, G2-র খবর ছাড়াই; লেখা শেষ হলো।
  3. তারপর আরেকজন client G2-তে পড়ার অনুরোধ পাঠালেন। A বলে G2-কেও উত্তর দিতে হবে।
  4. G2 কোনো বার্তা পায়নি। তার চোখে এই ঘটনা আর “কেউ কিছু লেখেনি” ঘটনা হুবহু এক — দুটিকে আলাদা করার কোনো তথ্য তার কাছে নেই। তাই সে v0 ফেরত দেয়।
  5. লেখা শেষ হওয়ার পরে শুরু হওয়া পড়া পুরোনো v0 পেল — linearizability ভাঙল। অতএব তিনটি একসাথে অসম্ভব।

যুক্তির প্রাণ চতুর্থ লাইন: যে তথ্য পৌঁছায়নি, কোনো algorithm তা জানতে পারে না। প্রমাণটি asynchronous network-এর মডেলে — ঘড়ি নেই, বার্তার দেরির সীমা নেই; সেখানে বার্তা হারিয়েছে না শুধু দেরি করছে, node তা বুঝতেই পারে না — পর্ব ৩৮-এর partial failure-এর মূল কথা এটিই।

Network ভাগ হলে প্রতিটি কাজের জন্য একটি বাছাই:
এখনই উত্তর দেবেন (A), না নিশ্চিত সঠিক থাকবেন (C)

DBMS কোর্সের Distributed database পর্বে দুই শহরের দুটি replica দিয়ে এর প্রথম পরিচয় আছে। এখানে বাস্তবের কাছাকাছি একটি ছবি: ৫টি replica, আর CP মোডে নিয়ম “৫টির অন্তত ৩টি (সংখ্যাগরিষ্ঠ) রাজি হলে তবেই কাজ” — Raft-ধাঁচের ব্যবস্থার মতো (পর্ব ৪২)। Network কেটে দুই দলে ভাগ হলো: N1–N3 আর N4–N5.

  1. N1 · N2 · N3সংখ্যাগরিষ্ঠ দিক৩টি ভোট
  2. Networkদুই দলের মাঝেবার্তা হারায়
  3. N4 · N5সংখ্যালঘু দিক২টি ভোট
x-এর মান আর দুই মোডের উত্তর
ঘটনা
N1–N3-এ x
N4–N5-এ x
CP মোডের উত্তর
AP মোডের উত্তর
কী ছাড়তে হলো
  1. স্বাভাবিক সময়। পাঁচটি কপিতেই x = 1. Network ঠিক থাকলে CAP কিছুই নিষেধ করে না — দুই মোডই সঠিক উত্তর আর সেবা দুটিই দেয়।
  2. Partition. লিংক কাটা; দুই দলই চালু, দুই দিকেই client আছেন। কোন দিকে কী হবে, তা এখন নির্ভর করে আগে থেকে বেছে রাখা নিয়মের ওপর।
  3. সংখ্যাগরিষ্ঠ দিকে লেখা। P লিখলেন x = 2. CP মোডে N1, N2, N3 — তিনটি ack, সংখ্যাগরিষ্ঠ — তাই OK. AP মোডেও OK. এই দিকে দুই মোডের আচরণ একই।
  4. সংখ্যালঘু দিকে পড়া। Q পড়লেন N4 থেকে। CP মোডে N4 মাত্র ২টি node-এর নাগাল পায়, তাই ত্রুটি বা অপেক্ষা — ভুল উত্তর নেই, কিন্তু উত্তরও নেই (A গেল)। AP মোডে N4 নিজের কপি থেকে x = 1 দেয় — অথচ x = 2 লেখা আগেই শেষ, তাই C গেল।
  5. সংখ্যালঘু দিকে লেখা। R লিখলেন x = 3. CP প্রত্যাখ্যান করে। AP মেনে নেয় — এখন দুই দলের কাছে x-এর দুটি আলাদা ইতিহাস, কেউ কারও খবর জানে না।
  6. Network জোড়া লাগল। CP-তে মেলানোর কিছু নেই: N4, N5 সংখ্যাগরিষ্ঠের log থেকে x = 2 নিয়ে নেয়। AP-তে একই key-র দুটি একসাথে তৈরি সংস্করণ — 2 আর 3. Last-write-wins একটি ফেলে দেবে; দুটি রেখে মেলানো বা CRDT — পর্ব ৪৬-এ।

লক্ষ করুন, CP মোডেও সংখ্যাগরিষ্ঠের দিক পুরোদমে চলেছে। যেকোনো দুটি সংখ্যাগরিষ্ঠ দল (৫টির ৩টি করে) অন্তত একটি node-এ মেলে, তাই দুই দিকে পরস্পরবিরোধী দুটি সিদ্ধান্ত অসম্ভব — split-brain ঠেকে যায়। এটিই quorum (পর্ব ৪১) আর consensus-এর (পর্ব ৪২) ভিত্তি।

৩ · CAP-এর পাঁচটি ভুল ব্যাখ্যা — “তিনের দুই”, “CA ব্যবস্থা” আর আরও

CAP সম্ভবত distributed systems-এর সবচেয়ে বেশি ভুল বোঝা কথা। পাঁচটি ভুল বারবার দেখা যায়:

  1. “তিনটির যেকোনো দুটি বেছে নিন।” একাধিক যন্ত্র network-এ যুক্ত থাকলে partition ঘটবে কি না, তা আপনার পছন্দ নয় — switch নষ্ট হয়, তার কাটে, ভুল config আসে। P বাদ দেওয়া মানে “partition-এ কী হবে, ঠিক করিনি”। Brewer নিজেই ২০১২ সালের “CAP Twelve Years Later” প্রবন্ধে “তিনের দুই”-কে বিভ্রান্তিকর বলেছেন।
  2. “আমাদের ব্যবস্থা CA.” এক যন্ত্রের database-এ কথাটির মানে আছে, সেখানে partition নেই। কিন্তু তিন node-এর cluster-কে “CA” বললে আসল মানে: partition-এ আচরণ অনির্ধারিত — প্রায়ই দুটিই যায়, যেমন দুই দিকে দুই leader.
  3. “CP মানে availability কম।” CAP-এর A কড়া — প্রতিটি চালু node-এর উত্তর; পর্ব ৫-এর availability (কত ভাগ অনুরোধ সফল) আলাদা মাপ। Client-রা সংখ্যাগরিষ্ঠের দিকে পৌঁছাতে পারলে CP ব্যবস্থার মাপা availability খুব উঁচু হয়; আবার “AP” ব্যবস্থাও bug বা overload-এ পুরো বন্ধ হতে পারে।
  4. “Partition একটি বিরল, স্পষ্ট ঘটনা।” একটি node-এর চোখে partition আর একটি খুব ধীর node দেখতে হুবহু এক — উত্তর আসছে না। তাই CAP-এর বাছাই আসলে ঘটে প্রতিটি timeout-এ: আরও অপেক্ষা করব (C রক্ষা, A-র ঝুঁকি), নাকি হাতে যা আছে তা দিয়েই উত্তর দেব (A রক্ষা, C-র ঝুঁকি)? একটি GC-বিরতি বা ভিড়ে আটকে থাকা লিংকও “partition”. কে মৃত আর কে শুধু ধীর, অনুমানের কাজটি failure detector-এর — পর্ব ৪৪-এর membership আর failure detection পর্বে।
  5. “পুরো সিস্টেম হয় CP, নয় AP.” বাছাই হয় প্রতিটি কাজে — একই অ্যাপে টাকা কাটা CP, like AP; একই database-এ লেখা linearizable কিন্তু follower থেকে পড়া পুরোনো। আর AP মানেই “শুধু eventual” নয় — partition-এও causal পর্যন্ত দেওয়া যায় (অংশ ৫)।

এবার সিমুলেটরে দুই মোডের দাম মাপা যাক। পাঁচটি replica; partition চলাকালীন ১,০০০টি অনুরোধ — অর্ধেক লেখা, অর্ধেক পড়া, ২০০টি key আর পাঁচটি node-এর যেকোনোটিতে সমান সম্ভাবনায়। ধরা যাক প্রতিটি client কেবল নিজের দিকের node-এর নাগাল পান। CP মোডে সংখ্যালঘু দিকের সব অনুরোধ ফেরত যায়; AP মোডে দুই দিকই নিজের মতো লেখা নেয়। বিভাজন দুটি: ৩|২ আর ৪|১; seed একই, তাই দুই মোডে হুবহু একই অনুরোধ।

// 5 replicas; a partition cuts off the last m of them. 1,000 requests arrive during it.
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 KEYS = 200, REQ = 1000;
console.log("split  mode  answered  rejected  avail.   stale reads  conflicting keys");
for (const m of [2, 1]) {                              // minority size: 3|2 split, then 4|1
  for (const mode of ["CP", "AP"]) {
    const rand = rng(41);                              // same workload for both modes
    const ver = [Array(KEYS).fill(0), Array(KEYS).fill(0)]; // [majority, minority] versions
    let ok = 0, rej = 0, stale = 0;
    for (let i = 0; i < REQ; i++) {
      const side = Math.floor(rand() * 5) >= 5 - m ? 1 : 0;  // 1 = minority side
      const key = Math.floor(rand() * KEYS), write = rand() < 0.5;
      if (mode === "CP" && side === 1) { rej++; continue; } // no majority: refuse
      ok++;
      if (write) ver[side][key]++;                     // each side keeps its own history
      else if (ver[side][key] < ver[1 - side][key]) stale++; // a newer write exists elsewhere
    }
    let conflicts = 0;                                 // after heal: both sides changed key
    for (let k = 0; k < KEYS; k++) if (ver[0][k] > 0 && ver[1][k] > 0) conflicts++;
    console.log(`${5 - m}|${m}`.padEnd(7) + mode.padEnd(6) + String(ok).padEnd(10) +
      String(rej).padEnd(10) + (100 * ok / REQ).toFixed(1).padStart(5) + "%   " +
      String(stale).padEnd(13) + conflicts + " of " + KEYS);
  }
}

আউটপুট:

split  mode  answered  rejected  avail.   stale reads  conflicting keys
3|2    CP    584       416        58.4%   0            0 of 200
3|2    AP    1000      0         100.0%   144          105 of 200
4|1    CP    792       208        79.2%   0            0 of 200
4|1    AP    1000      0         100.0%   86           80 of 200

৩|২ বিভাজনে CP মোড উত্তর দিল 58.4% অনুরোধে — প্রত্যাশিত ৩ ÷ ৫ = ৬০%-এর কাছাকাছি; সংখ্যালঘুর ভাগ ছোট হলে (৪|১) তা বেড়ে 79.2%. কিন্তু একটিও পুরোনো পড়া বা conflict নেই। AP মোড উত্তর দিল সবাইকে, দাম দুই রকম। প্রথমত 144টি পুরোনো পড়া বা stale read — পড়ার মুহূর্তে অন্য দিকে আরও নতুন লেখা ছিল, অর্থাৎ linearizability ভাঙা। দ্বিতীয়ত, জোড়া লাগার পর 105টি key-র দুটি করে সংস্করণ। হিসাবেও মেলে: সংখ্যাগরিষ্ঠ দিকে মোটামুটি ৩০০টি, সংখ্যালঘু দিকে ২০০টি লেখা; একটি key দুই দিকেই লেখা হওয়ার সম্ভাবনা ≈ (১ − e−৩০০/২০০) × (১ − e−২০০/২০০) ≈ ০.৭৮ × ০.৬৩ ≈ ০.৪৯, অর্থাৎ প্রায় ৯৮টি key; নমুনায় এসেছে ১০৫। Partition যত দীর্ঘ, AP-র মেলানোর বোঝা তত বড়, আর CP-র ফেরানো অনুরোধও তত বেশি।

৪ · PACELC — network ঠিক থাকলেও latency বনাম consistency

CAP শুধু partition-এর মুহূর্তের কথা বলে, অথচ বছরের বেশিরভাগ সময় network ঠিকই থাকে। তখন কি কোনো বাছাই নেই? Daniel Abadi ২০১২ সালের একটি প্রবন্ধে বিষয়টি একটি নামে গুছিয়ে দেন — PACELC: Partition হলে A না C; Else, অর্থাৎ স্বাভাবিক সময়ে, Latency না Consistency.

কেন স্বাভাবিক সময়েও বাছাই? Linearizable পড়া দিতে হলে store-কে নিশ্চিত হতে হয় যে তার কাছের কপিটিই সর্বশেষ — তার মানে leader বা সংখ্যাগরিষ্ঠের সাথে অন্তত একবার কথা বলা। সেই round trip-এর দাম আলোর গতি ঠিক করে, কোনো optimization নয়। একটি হিসাব — ধরা যাক: তিনটি region X, Y, Z-এ একটি করে replica; RTT X–Y ৩০ ms, Y–Z ৬০ ms, X–Z ৮০ ms; কাছের replica থেকে পড়ায় ১ ms; ব্যবহারকারীদের ৫০% X-এর কাছে, ৩০% Y-এর, ২০% Z-এর। সরল মডেলে linearizable পড়া মানে নিজের replica আর সবচেয়ে কাছের আরেকটি replica-র সম্মতি (৩টির ২টি):

ব্যবহারকারী কোথায়ভাগLocal পড়া (EL)Linearizable পড়া (EC)
X-এর কাছে৫০%১ ms১ + ৩০ = ৩১ ms
Y-এর কাছে৩০%১ ms১ + ৩০ = ৩১ ms
Z-এর কাছে২০%১ ms১ + ৬০ = ৬১ ms
ওজনসহ গড়—১ ms০.৫ × ৩১ + ০.৩ × ৩১ + ০.২ × ৬১ = ৩৭ ms

একটি পাতায় পরপর ৫টি এমন পড়া হলে ৫ ms-এর জায়গায় ১৮৫ ms. সংখ্যাগরিষ্ঠের ধীরতম সদস্যের অপেক্ষায় tail latency (পর্ব ৪) আরও বাড়ে। এক ডেটা সেন্টারেও একই দর-কষাকষি ছোট আকারে আছে — পর্ব ২২-এর উদাহরণে sync commit ৬২ ms, async ১ ms.

শ্রেণিPartition-এস্বাভাবিক সময়েযেমন ধাঁচ
PA/ELলেখা-পড়া চলে, কপি আলাদা হয়কাছের কপি থেকে দ্রুত উত্তরDynamo-ধাঁচের leaderless store-এর default; Abadi Cassandra আর Riak-কে এখানে রেখেছিলেন
PC/ECসংখ্যালঘু দিক থামেপ্রতিটি কাজে সমন্বয়, বাড়তি latencyConsensus-ভিত্তিক store (etcd, ZooKeeper-এর লেখা), পুরো ACID distributed database
PA/ECউত্তর দেয়, কপি আলাদা হতে পারেসাধারণত leader থেকে সর্বশেষ মানকিছু single-leader ব্যবস্থার নির্দিষ্ট setting
PC/ELথামেদ্রুত, কিছুটা পুরোনো মানও চলেবিরল; কিছু geo-replicated নকশা

শ্রেণিগুলো পণ্যের নয়, setting-এর। Abadi-র শ্রেণিবিভাগ ছিল ২০১২ সালের সংস্করণ আর default setting অনুযায়ী; আজ অনেক store প্রতিটি অনুরোধে বেছে নিতে দেয় — একটি পড়া ONE, আরেকটি QUORUM. একে বলে tunable consistency, পুরো হিসাব পর্ব ৪১-এর Quorum আর leaderless replication পর্বে। কোনো ডাটাবেসকে “PA/EL” বা “CP” বলার আগে জিজ্ঞেস করুন: কোন সংস্করণে, কোন setting-এ, কোন কাজে?

নকশার নিয়ম

CAP-এর বাছাই বছরে কয়েক ঘণ্টার জন্য, PACELC-এর E অংশ প্রতিটি অনুরোধের জন্য। তাই নকশার আলোচনায় প্রথম প্রশ্ন হোক: “স্বাভাবিক দিনে এই কাজটির জন্য কত ms বাড়তি দিতে রাজি?” — তারপর “partition হলে এই কাজটি থামবে, না পুরোনো উত্তর দেবে?”

৫ · Consistency model-এর সিঁড়ি — linearizable থেকে eventual

“Strong” আর “eventual” — এই দুই প্রান্তের মাঝে অনেকগুলো ধাপ, আর প্রতিটি ধাপ নির্দিষ্ট কিছু anomaly অনুমোদন করে, কিছু নিষেধ করে। নিচের ছবিতে ওপরেরটি কড়া; তীর মানে “ওপরেরটি মানলে নিচেরটিও আপনিই মানা হয়”।

Consistency model-এর সিঁড়ি

Hierarchy of consistency models, strongest at the top

Consistency model-এর সিঁড়ি: strict serializability থেকে eventual consistency Strict serializability transaction + বাস্তব সময় Serializability transaction, বহু object Linearizability এক object, বাস্তব সময় Sequential সবার এক ক্রম Causal কারণ আগে, ফল পরে RYW নিজের লেখা MR পেছনে নয় MW লেখার ক্রম WFR পড়ার পরে লেখা Eventual শুধু শেষে মিলবে, ক্রমের কথা নেই ↳ isolation level (পর্ব ২৪) ↑ partition হলে সব node উত্তর দিতে পারে না ↓ partition-এও উত্তর দেওয়া যায়* * causal, RYW: client একই replica-তে থাকলে আলাদা প্রতিশ্রুতি
ছবি ৪০.১ — তীর মানে “ওপরেরটি মানলে নিচেরটিও মানা হয়”। লাল ড্যাশ রেখার ওপরের কোনো মডেল partition-এর সময় প্রতিটি node থেকে উত্তর দিতে পারে না; নিচেরগুলো পারে (causal আর read-your-writes-এ client-কে একই replica-তে থাকতে হয়)। Eventual ক্রমের কোনো নিয়ম নয়, শুধু মিলে যাওয়ার প্রতিশ্রুতি — তাই ড্যাশ রেখায় আলাদা।

Linearizability — এক কপির ভ্রম

Maurice Herlihy আর Jeannette Wing-এর ১৯৯০ সালের সংজ্ঞা। Client-এর চোখে মনে হবে ডেটার একটিই কপি, আর প্রতিটি কাজ শুরু ও শেষের মাঝে এক মুহূর্তে ঘটেছে। দুটি শর্ত: (ক) সব কাজ মিলে একটি ক্রম, যেখানে প্রতিটি পড়া ঠিক আগের লেখাটি দেখে; (খ) একটি কাজ আরেকটির শুরুর আগে শেষ হলে ক্রমেও সে আগে। দ্বিতীয় শর্তটিই “বাস্তব সময়” — ফোনে কেউ বললেন “আমি দেখেছি”, আপনি পরে দেখলে আপনিও দেখবেন। পুরোনো লেখায় একে strong consistency বলা হয়, যদিও বিভিন্ন পণ্যের documentation “strong” শব্দটি আলাদা অর্থে ব্যবহার করে।

Sequential consistency — এক ক্রম, কিন্তু ঘড়ি নয়

Leslie Lamport ১৯৭৯ সালে multiprocessor-এর memory-র জন্য সংজ্ঞাটি দেন: সব কাজ একটি একক ক্রমে সাজানো যায়, যেখানে প্রতিটি client-এর নিজের কাজের ক্রম ঠিক থাকে — কিন্তু সেই ক্রম বাস্তব সময়ের আগে-পরে মানবে, এমন কথা নেই। Sequential consistency-তে সবাই একই গল্প দেখেন, শুধু গল্পটি ঘড়ির সাথে না-ও মিলতে পারে। অংশ ৬-এ দেখবেন একটি history যা sequential, অথচ linearizable নয়।

Causal consistency — কারণ আগে, ফল পরে

দুটি লেখা happens-before সম্পর্কে যুক্ত হলে — একজন একটি পোস্ট পড়ে তার উত্তর লিখলেন, বা একই client পরপর দুটি লিখলেন — causal consistency-তে সবাই সেগুলো একই ক্রমে দেখবেন: প্রশ্নের আগে উত্তর কখনো নয়। কিন্তু সম্পর্কহীন দুটি লেখা — দুই শহরে দুজন একই সময়ে দুটি আলাদা পোস্ট — একেকজন একেক ক্রমে দেখতে পারেন। কারণ-ফল ধরে রাখতে প্রতিটি লেখার সাথে “কোনগুলো দেখে লিখেছি” তথ্য যায়, প্রায়ই vector clock বা dependency-তালিকা হিসেবে (পর্ব ৩৯-এর সময় আর ক্রম)। Ahamad ও সহকর্মীরা ১৯৯০-এর দশকে causal memory-র আনুষ্ঠানিক সংজ্ঞা দেন।

Session guarantee আর eventual consistency

আরও নিচে চারটি ছোট প্রতিশ্রুতি, প্রতিটি একজন client-এর নিজের অভিজ্ঞতা ঘিরে — session guarantees, অংশ ৭-এ। আর সবার নিচে eventual consistency: নতুন লেখা থামলে একসময় সব কপি একই মানে পৌঁছাবে। কখন, তার সীমা নেই; মাঝে কোন মান বা কোন ক্রম দেখা যাবে, কিছুই বলা নেই। নামটি জনপ্রিয় করেন Werner Vogels তাঁর ২০০৮ সালের “Eventually Consistent” লেখায়, যদিও ধারণাটি ১৯৯০-এর দশকের replication-গবেষণাতেই ছিল। এটি liveness প্রতিশ্রুতি (“শেষে ভালো কিছু ঘটবে”), safety নয় (“খারাপ কিছু ঘটবে না”) — তাই বাস্তবে এর সাথে প্রায় সবসময় session guarantee জুড়ে দেওয়া হয়।

Eventual-এর একটি কড়া রূপ আছে: strong eventual consistency — যে দুটি কপি একই পরিবর্তনগুলো পেয়েছে, ক্রম যা-ই হোক, তারা তখনই হুবহু এক। “শেষ পর্যন্ত” আর অস্পষ্ট থাকে না, আর conflict মেটানোর জন্য আলাদা কেউ লাগে না। CRDT ঠিক এটি দেয় — পর্ব ৪৬-এর Conflict resolution আর CRDT পর্বে দেখবেন।

৬ · Linearizability কাছ থেকে — linearization point, একটি checker আর তার দাম

একটি history linearizable কি না, যাচাইয়ের একটি সহজ উপায় আছে: প্রতিটি কাজের শুরু আর শেষের মাঝে একটি বিন্দু বেছে নিন — সেই মুহূর্তে কাজটি “একবারে ঘটে গেল” — যাতে বিন্দুগুলোর ক্রমে প্রতিটি পড়া ঠিক আগের লেখাটি দেখে। এই বিন্দুর নাম linearization point. এমন বিন্দু বাছা গেলে history linearizable; কোনোভাবেই না গেলে নয়। একটি কাল্পনিক বোর্ড পরীক্ষার ফল প্রকাশের ঘটনা দিয়ে দেখা যাক।

Register x: 0 = প্রকাশ হয়নি, 1 = প্রকাশিতসময় ms-এ

            
যাচাইয়ের অবস্থা
w(1)-এর বিন্দু কোথায় হতে পারে
Linearizable?
Sequential?
  1. History. অফিস (A) ফল প্রকাশের লেখা পাঠাল ০ ms-এ, “সম্পন্ন” উত্তর এল ১০ ms-এ। এর মধ্যে Rafi (B) ২–৪ ms-এ দেখলেন “প্রকাশিত”, ফোন করে Sumi-কে জানালেন; Sumi (C) ৬–৮ ms-এ দেখলেন “প্রকাশ হয়নি”।
  2. নিয়ম। প্রতিটি কাজ নিজের পরিসরের ভেতরের একটি বিন্দুতে ঘটেছে ধরতে হবে। লেখাটির পরিসর লম্বা (০–১০ ms), তাই তার বিন্দু শুরুতে প্রায় যেকোনো জায়গায় হতে পারে।
  3. Rafi-র পড়া বিন্দুকে বাঁধে। Rafi 1 পেয়েছেন, তাই লেখার বিন্দু Rafi-র বিন্দুর আগে — আর Rafi-র বিন্দু ৪ ms-এর পরে নয়। অতএব লেখার বিন্দু ৪ ms-এর মধ্যে।
  4. Sumi-র পড়ায় দ্বন্দ্ব। Sumi শুরু করেছেন ৬ ms-এ, লেখার বিন্দুর পরে — তাঁকে 1 দেখতেই হবে। দেখলেন 0. কোনো বিন্দু-বাছাই কাজ করে না: history-টি linearizable নয়। এটিই “ফোনে শুনলাম, অথচ নিজে দেখছি না” anomaly.
  5. বাস্তব সময় বাদ দিলে। শুধু প্রতি client-এর নিজের ক্রম মানলে একটি বৈধ ক্রম আছে: Sumi-র পড়া, তারপর লেখা, তারপর Rafi-র পড়া। তাই history-টি sequentially consistent — sequential consistency ফোনের খবরটিকে গোনে না।
  6. উল্টো ঘটনা। Rafi 0 আর Sumi 1 পেলে লেখার বিন্দু ৪ আর ৬ ms-এর মাঝে রাখা যায়: Rafi আগে, Sumi পরে — linearizable. পার্থক্য শুধু কে কী দেখলেন, সময়গুলো একই।

নিচের checker সব সম্ভাব্য ক্রম চেষ্টা করে দুটি প্রশ্ন করে — linearizable (বাস্তব সময় মেনে) আর sequential (শুধু প্রতি client-এর ক্রম মেনে) — আর পেলে একটি সাক্ষী-ক্রম (witness) ছাপে। H1 আর H2 উপরের দুটি ঘটনা; H3-এ একজন client নিজে 1 লিখে তারপর 0 পড়লেন।

// Brute-force checker for one register (initial value 0).
// Each operation: [client, "w" or "r", value, start, end] in ms.
const H = {
  H1: [["A", "w", 1, 0, 10], ["B", "r", 0, 2, 4], ["C", "r", 1, 6, 8]],
  H2: [["A", "w", 1, 0, 10], ["B", "r", 1, 2, 4], ["C", "r", 0, 6, 8]],
  H3: [["A", "w", 1, 0, 2], ["A", "r", 0, 3, 5]],
};
const perms = a => a.length < 2 ? [a] :
  a.flatMap((x, i) => perms([...a.slice(0, i), ...a.slice(i + 1)]).map(p => [x, ...p]));
function legal(order) {                    // every read returns the latest write before it
  let v = 0;
  for (const [, kind, val] of order) {
    if (kind === "w") v = val; else if (val !== v) return false;
  }
  return true;
}
function keeps(order, mustPrecede) {       // no pair appears in a forbidden order
  for (let i = 0; i < order.length; i++)
    for (let j = i + 1; j < order.length; j++)
      if (mustPrecede(order[j], order[i])) return false;
  return true;
}
const realTime = (a, b) => a[4] < b[3];                     // a ended before b started
const show = o => o.map(([c, k, v]) => `${c}:${k}(${v})`).join(" -> ");
for (const [name, h] of Object.entries(H)) {
  const sameClient = (a, b) => a[0] === b[0] && h.indexOf(a) < h.indexOf(b);
  const lin = perms(h).find(p => legal(p) && keeps(p, realTime));
  const seq = perms(h).find(p => legal(p) && keeps(p, sameClient));
  console.log(name + ": " + (lin ? "LINEARIZABLE      " + show(lin) : "NOT LINEARIZABLE"));
  console.log("    " + (seq ? "SEQUENTIAL        " + show(seq) : "NOT SEQUENTIAL"));
}

আউটপুট:

H1: LINEARIZABLE      B:r(0) -> A:w(1) -> C:r(1)
    SEQUENTIAL        B:r(0) -> A:w(1) -> C:r(1)
H2: NOT LINEARIZABLE
    SEQUENTIAL        C:r(0) -> A:w(1) -> B:r(1)
H3: NOT LINEARIZABLE
    NOT SEQUENTIAL

H1-এর সাক্ষী-ক্রমই stepper-এর শেষ ধাপ — Rafi আগে, লেখা মাঝে, Sumi পরে। H2 linearizable নয়, কিন্তু sequential: সাক্ষী-ক্রমে Sumi-র পড়া লেখার আগে বসেছে, যদিও বাস্তবে Sumi পরে দেখেছেন। H3 কোনোটিই নয় — একজন client-এর নিজের ক্রম (আগে লেখা, পরে পড়া) কোনো মডেলেই ভাঙা চলে না; এটি read-your-writes-এরও লঙ্ঘন। Brute force চলে কেবল ছোট history-তে: ১০টি কাজে ক্রম ১০! = ৩৬,২৮,৮০০টি। সাধারণভাবে linearizability যাচাই NP-complete (Gibbons ও Korach, ১৯৯৭), তাই বাস্তবের tester-রা চতুর ছাঁটাই আর প্রতিটি key আলাদা করে দেখার কৌশল ব্যবহার করে।

Linearizability-র দাম

এই নিশ্চয়তা বিনা মূল্যে আসে না। তিনটি দাম:

  • Partition-এ সংখ্যালঘু দিক থামে — অংশ ২-এর প্রমাণ।
  • প্রতিটি কাজে সমন্বয়ের latency — অংশ ৪-এর হিসাব। Leader-ভিত্তিক ব্যবস্থায় লেখা সংখ্যাগরিষ্ঠের log-এ না যাওয়া পর্যন্ত “OK” নয়, আর পড়ার আগেও leader-কে নিশ্চিত হতে হয় সে এখনো leader (Raft-এর read index বা lease, পর্ব ৪২ আর পর্ব ৪৩)। নইলে network-এ আলাদা হয়ে পড়া পুরোনো leader পুরোনো মান দেবে। Hagit Attiya আর Jennifer Welch ১৯৯৪ সালে দেখান, linearizable পড়া আর লেখাকে একসাথে network-এর বার্তা-দেরির চেয়ে দ্রুত করা যায় না, অথচ sequential consistency-তে অন্তত একটি ধরন, যেমন পড়া, স্থানীয়ভাবে করা যায়।
  • “R + W > N” নিজে যথেষ্ট নয়। Quorum-এর ওভারল্যাপ নতুন মান খুঁজে পেতে সাহায্য করে, কিন্তু একসাথে চলা লেখা, last-write-wins আর ঘড়ির পার্থক্য থাকলে history তবুও linearizable না-ও হতে পারে — পর্ব ৪১-এ দেখবেন কী বাড়তি লাগে।

বিনিময়ে একটি দুর্লভ সুবিধা: Herlihy ও Wing দেখান linearizability “local” — প্রতিটি object আলাদাভাবে linearizable হলে পুরো ব্যবস্থাও linearizable. Sequential consistency-র এই গুণ নেই, তাই টুকরো জুড়ে বড় সিস্টেম বানাতে linearizability নিরাপদ ভিত্তি।

৭ · Session guarantee — একজন ব্যবহারকারীর চোখে চারটি প্রতিশ্রুতি

পর্ব ২২-এ (Replication, অংশ ৪) replication lag-এর তিনটি anomaly আর প্রতিকার দেখেছেন: read-your-writes (সময়-জানালা, LSN মিলিয়ে route), monotonic reads (এক replica-তে বাঁধা) আর consistent prefix. সেখানকার কৌশল এখানে আবার বলব না। এখানে প্রশ্ন: এগুলো সিঁড়ির কোথায় বসে, আর পুরো পরিবারটি কী?

Xerox PARC-এর Bayou প্রকল্পে Douglas Terry ও সহকর্মীরা ১৯৯৪ সালে চারটি session guarantee-র নাম দেন। প্রতিটি একটি session-এর (একজন ব্যবহারকারী বা একটি অ্যাপ-instance) পড়া-লেখার ক্রম ঘিরে; অন্য client-দের ব্যাপারে কোনো কথা নেই:

নিশ্চয়তাপ্রতিশ্রুতিভাঙলে ব্যবহারকারী যা দেখেন
Read-your-writesনিজের লেখা পরের পড়ায় অন্তত দেখবেনসদ্য বদলানো ঠিকানা আবার পুরোনো
Monotonic readsএকবার নতুন অবস্থা দেখলে আর পুরোনো নয়refresh-এ স্কোর কমে গেল, উইকেট “ফিরে” এল
Monotonic writesনিজের লেখাগুলো প্রতিটি কপিতে নিজের ক্রমেই প্রয়োগ“ফোন নম্বর বদল” আর “নম্বর যাচাই হয়েছে” উল্টো ক্রমে বসল — যাচাই-চিহ্ন পুরোনো নম্বরে
Writes-follow-readsযা পড়ে লিখেছেন, আপনার লেখা কোথাও তার আগে বসবে নামন্তব্য দেখা যাচ্ছে, মূল পোস্টটিই নেই

চারটি একসাথে, আর session-গুলোর মাঝে কারণ-ফল ছড়ালে, পাওয়া যায় causal-এর কাছাকাছি — তাই ছবি ৪০.১-এ এরা causal-এর নিচে। বাস্তবায়নের মূল যন্ত্র session token: client নিজের দেখা ও লেখা সর্বোচ্চ অবস্থান (version, LSN বা version vector) প্রতিটি অনুরোধে পাঠায়; replica সেখানে না পৌঁছালে অপেক্ষা করে বা এগিয়ে থাকা কপিতে পাঠায়। একই replica-তে বেঁধে রাখা (sticky session) সহজতর, কিন্তু replica মরলে বা ব্যবহারকারী অন্য ডিভাইসে গেলে নিশ্চয়তা হারায়।

একটি কাল্পনিক ক্রিকেট-স্কোর অ্যাপ · v3 = ১২০/২, v4 = ১২৬/২, v5 = ১৩১/৩, v6 = ১৩৫/৩
পড়া কোথায় গেল
R1-এর অবস্থান
R2-এর অবস্থান
Sumi যা দেখলেন
Sumi-র token
ভাঙা নিশ্চয়তা
  1. শুরুর অবস্থা। স্কোর লেখা হয় leader-এ; R1 v5 পর্যন্ত প্রয়োগ করেছে, R2 পিছিয়ে v3-এ। Sumi অ্যাপ খুললেন — এখনো কিছু দেখেননি।
  2. প্রথম পড়া R1-এ। Sumi দেখলেন v5-এর স্কোর ১৩১/৩। অ্যাপ মনে রাখল: “আমি v5 পর্যন্ত দেখেছি” — seen = 5.
  3. Token ছাড়া refresh গেল R2-তে। ১২০/২ — রান কমেছে, উইকেট “ফিরে” এসেছে! Sumi নিজে কিছু লেখেননি, তাই read-your-writes প্রশ্নই নয়; ভেঙেছে monotonic reads.
  4. Token সহ refresh করা হলো। অনুরোধে seen = 5. R2 নিজের অবস্থান v3 দেখে বুঝল সে এই client-কে উত্তর দিতে পারবে না — তাই অপেক্ষা করবে, নয়তো অনুরোধটি এগিয়ে থাকা কপিতে পাঠাবে।
  5. এগিয়ে থাকা কপি থেকে উত্তর। R1 এখন v6-এ: ১৩৫/৩। Token হলো seen = 6. Sumi-র সময় আর কখনো পেছনে যাবে না — কোন replica থেকে পড়ছেন, তাতে কিছু আসে যায় না।
  6. যা এখনো অনুমোদিত। একই মুহূর্তে Rafi নিজের ফোনে R2 থেকে ১২৬/২ (v4) দেখছেন। Session guarantee শুধু এক session-এর ভেতরের কথা; v6 লেখা শেষ হওয়ার পর Rafi পুরোনো দেখলেন বলে এটি linearizable নয় — কিন্তু স্কোরের অ্যাপে তা মেনে নেওয়া যায়।

কতবার কোন নিশ্চয়তা ভাঙে, একটি সিমুলেটরে গোনা যাক। একটি register, দুটি replica: লেখা যায় A-তে (leader), B সেই লেখা ২০–৯৯ ms পরে প্রয়োগ করে। তিনজন client (c1, c2, c3) প্রতি ১০ ms-এ একটি করে কাজ করেন — ৪০% লেখা, বাকিটা পড়া, A বা B থেকে সমান সম্ভাবনায়। প্রতিটি পড়ায় তিনটি প্রশ্ন: linearizable store হলে আরও নতুন মান আসত কি (LIN)? নিজের শেষ লেখার চেয়ে পুরোনো কি (RYW)? নিজের আগের পড়ার চেয়ে পুরোনো কি (MR)? এখানে লেখা A-তে তাৎক্ষণিক, তাই “পড়া শুরুর আগে শেষ হওয়া সর্বশেষ লেখা” ঠিক করে বলা যায়।

// One register, two replicas. Writes go to A; B applies each write 20-99 ms later.
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 rand = rng(40);
const W = [{ v: 0, tA: -1, tB: -1 }];                  // version 0 = initial value
const lastW = {}, lastR = {}, bad = { LIN: 0, RYW: 0, MR: 0 };
let reads = 0;
console.log("t    client  op       replica  got  newest  broken");
for (let t = 0; t < 400; t += 10) {                    // one operation every 10 ms
  const c = "c" + (1 + Math.floor(rand() * 3));
  if (rand() < 0.4) {                                   // write: new version on A at time t
    const v = W.length;
    W.push({ v, tA: t, tB: t + 20 + Math.floor(rand() * 80) });
    lastW[c] = v; continue;
  }
  const rep = rand() < 0.5 ? "A" : "B";
  const seen = w => (rep === "A" ? w.tA : w.tB) <= t;  // has this replica applied w?
  const got = W.filter(seen).pop().v;                  // value the replica returns
  const newest = W.filter(w => w.tA < t).pop().v;     // latest write finished before read
  const broken = [];
  if (got < newest) broken.push("LIN");                // a linearizable store returns newest
  if (got < (lastW[c] ?? 0)) broken.push("RYW");       // older than my own write
  if (got < (lastR[c] ?? 0)) broken.push("MR");        // older than what I read before
  lastR[c] = Math.max(lastR[c] ?? 0, got); reads++;
  broken.forEach(b => bad[b]++);
  if (broken.length) console.log(String(t).padEnd(5) + c.padEnd(8) + "read".padEnd(9) +
    rep.padEnd(9) + ("v" + got).padEnd(5) + ("v" + newest).padEnd(8) + broken.join(","));
}
console.log(`reads=${reads}  LIN broken=${bad.LIN}  RYW broken=${bad.RYW}  MR broken=${bad.MR}`);

আউটপুট (শুধু কোনো নিশ্চয়তা ভাঙা পড়াগুলো):

t    client  op       replica  got  newest  broken
60   c3      read     B        v2   v4      LIN,RYW,MR
100  c3      read     B        v4   v5      LIN
110  c1      read     B        v4   v5      LIN,MR
130  c3      read     B        v4   v5      LIN,MR
200  c2      read     B        v7   v9      LIN
210  c2      read     B        v7   v9      LIN
230  c3      read     B        v7   v10     LIN,RYW
250  c3      read     B        v9   v11     LIN,RYW
280  c1      read     B        v11  v12     LIN,RYW
300  c2      read     B        v11  v13     LIN,RYW
320  c1      read     B        v12  v13     LIN
360  c1      read     B        v12  v14     LIN
reads=23  LIN broken=12  RYW broken=5  MR broken=3

২৩টি পড়ার 12টি linearizability ভেঙেছে, সবগুলো B থেকে — leader A থেকে পড়া কখনো পুরোনো নয়। লক্ষ করুন, প্রতিটি RYW বা MR লঙ্ঘনের পাশে LIN-ও আছে: কড়া মডেল ভাঙা ছাড়া দুর্বলটি ভাঙে না। উল্টোটা সত্য নয় — t = 100-এ c3 পেলেন v4, অথচ v5 লিখেছেন অন্য কেউ: linearizability গেল, কিন্তু c3-এর নিজের দুটি প্রতিশ্রুতি অটুট। ১২টির মাত্র ৭টিতে ব্যবহারকারী নিজের চোখে অসংগতি টের পেতেন — session token দিয়ে সেগুলো সারানো যায়, B-কে না থামিয়েই। বাকিগুলো সারাতে, অর্থাৎ linearizable হতে, প্রতিটি পড়ায় B-কে leader-এর সাথে মিলিয়ে নিতে হতো।

৮ · Consistency বনাম isolation, আর কোন feature-এ কোন model

পর্ব ২৪-এ (Transaction আর isolation level) isolation level-এর সিঁড়ি দেখেছেন। দুটি সিঁড়ি দেখতে একরকম, কিন্তু প্রশ্ন আলাদা। Isolation জিজ্ঞেস করে: একসাথে চলা কয়েকটি transaction, যারা কয়েকটি সারি ছোঁয়, একে অপরের মাঝপথের কাজ কতটা দেখবে? Consistency model জিজ্ঞেস করে: একটি object-এর কয়েকটি কপি থেকে পড়া কতটা হালনাগাদ আর কোন ক্রমে?

নিশ্চয়তাকীসের ওপরবাস্তব সময় মানে?যা দেয় না
Serializabilityকয়েকটি object ছোঁয়া transactionনা — ফল কোনো এক serial ক্রমের সমানএকটি read-only transaction-কে অতীতে বসানো যায়, তাই সদ্য শেষ হওয়া লেখা সে না-ও দেখতে পারে
Linearizabilityএকটি object-এর প্রতিটি কাজহ্যাঁকয়েকটি key একসাথে বদলানোর নিশ্চয়তা
Strict serializabilitytransactionহ্যাঁ— দুটিই দেয়; দামও দুটিরই

দুটির পার্থক্যের বিস্তারিত উদাহরণ পর্ব ২৪-এর ভাবনার প্রশ্ন ৩-এ; Spanner (২০১২) দুটি একসাথে দেয় আর একে external consistency বলে।

কোন feature-এ কোন model — একটি কাল্পনিক super-app ধরে

টাকা পাঠানো, কেনাকাটা, চ্যাট আর feed — সব একটি অ্যাপে। সবখানে এক model মানে হয় সবখানে বেশি দাম, নয়তো টাকার জায়গায় ঝুঁকি। প্রতিটি feature-এর জন্য তিনটি প্রশ্ন: কোন ভুলে ক্ষতি (টাকা, আইন, আস্থা)? Partition-এ “পরে চেষ্টা করুন” ভালো, না “হয়তো পুরোনো”? স্বাভাবিক দিনে latency-র বাজেট কত?

Featureযে ভুল সহ্য করা যায় নাModelPartition হলে
টাকা পাঠানো, ব্যালান্স থেকে কাটাএকই টাকা দুবার খরচ, ঋণাত্মক ব্যালান্সLinearizable বা strict serializable (consensus-ভিত্তিক leader)CP: “পরে চেষ্টা করুন”; idempotency key দিয়ে retry (পর্ব ৮, ৬০)
ব্যালান্স শুধু দেখানোনিজের সদ্য-পাঠানো টাকার পর পুরোনো ব্যালান্সকয়েক সেকেন্ড পুরোনো চলে + read-your-writesপুরোনো মান “শেষ হালনাগাদ: …” লিখে
Username বা ফোন নম্বর অনন্য রাখা; শেষ সিটদুজনের একই নাম, একই সিটLinearizable compare-and-setCP: নিবন্ধন বা বুকিং সাময়িক বন্ধ
Like আর view counterপ্রায় কিছুই না; নির্ভুল সংখ্যা পরে মিললেই চলেEventual + CRDT counter (পর্ব ৪৬)AP: দুই দিকেই গোনা, পরে যোগ
প্রোফাইল ছবিমালিক নিজে নতুন ছবি না দেখাEventual + read-your-writes; cache ও CDN (পর্ব ১৭, ২৯)AP: অন্যরা কিছুক্ষণ পুরোনো ছবি দেখেন
চ্যাটের বার্তাপ্রশ্নের আগে উত্তরপ্রতিটি কথোপকথনে causal বা এক partition-এ ক্রম (পর্ব ৫৫)AP: বার্তা জমে, পরে ক্রম মেনে পৌঁছায়
কেনাকাটার cartযোগ করা জিনিস হারানোEventual + মেলানোর নিয়ম বা CRDT setAP: দুই দিকে যোগ, পরে union
নকশার নিয়ম

প্রতিটি “consistent চাই” কথার পাশে লিখুন: কোন model, কোন কাজে, আর partition-এ কী হবে। অ্যাপের বেশিরভাগ পড়া eventual + session guarantee-তে চলে; linearizability রাখুন অল্প কয়েকটি জায়গায় — যেখানে একটি invariant (অনন্যতা, ব্যালান্স, সিট) একটি জায়গায় যাচাই হতেই হবে।

আরও গভীরে: Kyle Kingsbury-র Jepsen প্রকল্প (২০১৩ থেকে) চালু database-এ ইচ্ছাকৃত partition ঘটিয়ে client-দের history রেকর্ড করে, তারপর Knossos-এর মতো checker দিয়ে দেখে history-টি linearizable কি না — অংশ ৬-এর checker-এর শিল্পমানের রূপ; পরে transaction-এর isolation যাচাইয়ে আসে Elle. তত্ত্বের দিকে, Mahajan, Alvisi ও Dahlin-এর ২০১১ সালের কাজ দেখায় যে সবসময় available একটি ব্যবস্থা causal-এর একটি রূপের চেয়ে কড়া কিছু দিতে পারে না; একই বছর COPS (Lloyd ও সহকর্মীরা, SOSP ২০১১) বহু region-এ causal+ consistency বাস্তবে দেখায়।

পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এমন প্রশ্ন আসতে পারে:

  • CAP theorem ঠিক কী বলে? — Network partition হলে একটি সিস্টেম linearizability আর প্রতিটি চালু node থেকে উত্তর একসাথে দিতে পারে না; বাছাই শুধু partition-এর সময় C না A.
  • PACELC কী যোগ করে? — Partition না থাকলেও consistency-র দাম latency: প্রতিটি কাজে অন্য replica-র সাথে round trip.
  • Linearizable আর sequential-এর পার্থক্য? — দুটিতেই সবার একটি ক্রম; linearizable সেই ক্রমকে বাস্তব সময়ের আগে-পরের সাথে মেলায়, sequential মেলায় না।
  • Serializability আর linearizability কি এক? — না; প্রথমটি বহু object-এর transaction, ক্রম যেকোনো; দ্বিতীয়টি এক object, বাস্তব সময়। দুটি একসাথে strict serializability.

৯ · AI-এর যুগে consistency-র নকশা যাচাই

“highly available আর consistent একটি ব্যবস্থার নকশা চাই” লিখলে AI প্রায়ই উত্তর দেয়: “CAP-এর তিনটির দুটি বাছতে হয়, আমরা AP নেব, Cassandra আর Kafka দিয়ে।” কথাগুলো পেশাদার শোনায়, কিন্তু এই বিষয়ে AI-এর ভুল বেশ নিয়মিত:

AI-এর ভুলকেন ভুলকীভাবে ধরবেন
“তিনের দুই বাছুন”, “আমরা CA নেব”Partition বাছাইয়ের বিষয় নয়; বাছাই হয় partition-এর সময়, প্রতিটি কাজেজিজ্ঞেস করুন: network ভাগ হলে এই কাজটি ঠিক কী করবে?
ACID-এর C আর CAP-এর C গুলিয়ে ফেলা; “strong consistency” অর্থ না বলাএকটি অ্যাপের নিয়ম রক্ষা, অন্যটি কপিদের মিল; “strong” পণ্যভেদে আলাদাপ্রতিটি “consistent”-এর পাশে model-এর নাম লিখিয়ে নিন
পুরো সিস্টেমের জন্য এক model, বা সবখানে consensus আর বহু regionটাকা আর like-এর দাম এক নয়; অকারণ latency আর চালানোর জটিলতাঅংশ ৮-এর মতো feature ধরে টেবিল চান
Latency-র অঙ্কে ভুলRegion পেরোনো সমন্বয় প্রতিটি কাজে বাড়তি কয়েক দশ msRTT দিয়ে অংশ ৪-এর হিসাব নিজে করুন
Failure mode উপেক্ষা: পুরোনো leader, ঘড়ির পার্থক্য, LWW-এ হারানো লেখা“Quorum আছে, তাই linearizable” — সবসময় সত্য নয়অংশ ৩-এর সিমুলেটর চালান; failure-mode পর্যালোচনা করুন
পণ্যের setting, default বা নিশ্চয়তা বানিয়ে বলাসংস্করণ আর setting-ভেদে আচরণ আলাদাOfficial documentation, তারিখসহ; সম্ভব হলে Jepsen-ধাঁচের প্রকাশিত পরীক্ষা

AI-কে feature, শর্ত আর সংখ্যা দিন, আর প্রতিটি সিদ্ধান্তের যুক্তি চান — যাতে আপনি যাচাই করতে পারেন:

তুমি একজন অভিজ্ঞ distributed systems ইঞ্জিনিয়ার। একটি কাল্পনিক অনলাইন বাস-টিকিট সেবার consistency নকশা করো। তথ্য: তিনটি region-এ replica; RTT X–Y ৩০ ms, Y–Z ৬০ ms, X–Z ৮০ ms. Feature: সিট বুকিং, পেমেন্ট, বাসের অবস্থান দেখা, রিভিউ আর রেটিং, ব্যবহারকারীর প্রোফাইল। ১. প্রতিটি feature-এর জন্য consistency model (linearizable, causal, read-your-writes, eventual ইত্যাদি) বাছো, কারণসহ। "consistency" বললে ACID না CAP অর্থে, স্পষ্ট লেখো। ২. Partition হলে প্রতিটি feature কী করবে — প্রত্যাখ্যান, না পুরোনো উত্তর — আর ব্যবহারকারী কী দেখবেন। ৩. স্বাভাবিক সময়ে প্রতিটি feature-এর বাড়তি latency, RTT থেকে ধাপে ধাপে হিসাব দেখাও। ৪. কোথায় quorum বা consensus লাগবে, আর শুধু R + W > N কেন যথেষ্ট না-ও হতে পারে। ৫. কোনো পণ্যের setting, default বা নিশ্চয়তা নিজে থেকে লিখবে না; দরকার হলে লিখবে "documentation-এ যাচাই করতে হবে"। সব অনুমান আলাদা তালিকায় দাও।

AI-এর দেওয়া consistency-নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট

AI দেয় দ্রুত খসড়া, যাচাইয়ের দায় আপনার।
প্রতিটি কাজের জন্য জিজ্ঞেস করুন: কোন model, কত ms, আর network ভাঙলে কী।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। সূত্র মুখস্থ নয়, কোন নকশা কেন বেছে নেওয়া হয় সেটাই আসল।

প্র ০১Brewer ২০০০ সালে যা বলেছিলেন আর Gilbert–Lynch ২০০২ সালে যা প্রমাণ করেছিলেন — দুটি কি একই কথা? প্রমাণের সংকীর্ণ সংজ্ঞা কেন গুরুত্বপূর্ণ?

Brewer-এর বক্তৃতার প্রেক্ষাপট ছিল ১৯৯০-এর দশকের শেষের বড় web সেবা — সার্চ ইঞ্জিন, ই-মেইল, ক্যাশ — যেগুলো শত শত সস্তা যন্ত্রে চলত। তার আগে ১৯৯৭ সালে Armando Fox, Brewer ও সহকর্মীদের একটি প্রবন্ধে ACID-এর বিপরীতে “BASE” (basically available, soft state, eventually consistent) ধারণাটি আসে: এসব সেবায় সামান্য পুরোনো উত্তর দেওয়া সেবা বন্ধ রাখার চেয়ে ভালো। Brewer-এর অনুমান ছিল একটি নকশা-অভিজ্ঞতার সারকথা, শব্দগুলো ছিল খোলামেলা।

Gilbert আর Lynch সেই খোলা কথাকে একটি নির্দিষ্ট গাণিতিক মডেলে বসান। C মানে একটি read/write register-এর atomic (linearizable) আচরণ; A মানে প্রতিটি চালু node-এর প্রতিটি অনুরোধের উত্তর; P মানে যেকোনো সংখ্যক বার্তা হারানো। আর network asynchronous — ঘড়ি নেই, বার্তার দেরির সীমা নেই। এই মডেলে প্রমাণটি প্রায় তুচ্ছ, যেমন অংশ ২-এ দেখেছেন। তাঁরা partially synchronous মডেলও দেখেন, যেখানে node-এর ঘড়ি আছে: সেখানে partition-এর সময় দুর্বল একটি নিশ্চয়তা আর partition মিটলে আবার পুরো consistency-র মতো মাঝামাঝি পথ সম্ভব।

সংকীর্ণ সংজ্ঞার দুটি ফল। এক, প্রমাণটি অকাট্য, কিন্তু সীমিত — এটি একটি register, একটি ধরনের ব্যর্থতা আর দুটি চূড়ান্ত প্রান্তের কথা বলে। “আমাদের ব্যবস্থা ৯৯% সময় সামান্য পুরোনো মানে উত্তর দেয়” — এমন বাস্তব নকশা নিয়ে CAP কিছুই বলে না। Latency, crash, একাধিক object-এর transaction — এগুলোও CAP-এর বাইরে। দুই, বাস্তবের অনেক আলোচনা CAP-এর নামে এমন দাবি করে, যা প্রমাণের ভেতরে নেই — যেমন “CP ব্যবস্থার availability কম”। Brewer নিজেই ২০১২ সালের লেখায় বলেছেন: partition বিরল, তাই স্বাভাবিক সময়ে C আর A দুটিই রাখা উচিত, আর partition-এর জন্য দরকার তিনটি পরিকল্পনা — partition চেনা, partition চলাকালীন কোন কাজ সীমিত হবে, আর জোড়া লাগার পর কীভাবে মেলানো হবে।

মূল উপলব্ধি: একটি theorem তার সংজ্ঞার সমান শক্তিশালী; CAP থেকে নকশার সিদ্ধান্ত নিতে হলে আগে দেখুন আপনার প্রশ্নটি সত্যিই সেই সংজ্ঞার ভেতরে পড়ে কি না।

প্র ০২Martin Kleppmann কেন database-কে “CP” বা “AP” বলা বন্ধ করতে বলেছিলেন? একটি সাধারণ single-leader database কোন ঘরে পড়ে?

২০১৫ সালে Kleppmann একটি জনপ্রিয় blog লেখায় আর পরে “A Critique of the CAP Theorem” নামের একটি প্রবন্ধে যুক্তি দেন: বাস্তবের বেশিরভাগ database CAP-এর কড়া সংজ্ঞায় না CP, না AP — তাই তকমাগুলো তথ্য না দিয়ে বিভ্রান্তি ছড়ায়।

একটি সাধারণ উদাহরণ নিন: একটি leader, দুটি asynchronous follower, পড়া follower থেকেও। Partition হলে follower নিজের পুরোনো কপি থেকে পড়ার উত্তর দেয় — linearizable নয়, তাই C নেই। আবার লেখা শুধু leader নেয়; follower-এর দিকের client লিখতে পারেন না — সব চালু node উত্তর দিচ্ছে না, তাই CAP-এর A-ও নেই। ব্যবস্থাটি না CP, না AP; অথচ বাস্তবে এটি সবচেয়ে প্রচলিত নকশা আর বেশিরভাগ অ্যাপের জন্য যথেষ্ট ভালো।

দ্বিতীয় উদাহরণ ZooKeeper — প্রায়ই “CP”-র উদাহরণ হিসেবে বলা হয়। এর প্রকাশিত নকশা (Hunt ও সহকর্মীরা, ২০১০) অনুযায়ী লেখাগুলো consensus দিয়ে linearizable, কিন্তু একটি client যে server-এর সাথে যুক্ত, পড়া সেখান থেকেই আসে, আর সেই server কিছুটা পিছিয়ে থাকতে পারে। প্রতিটি client নিজের ক্রম ঠিক দেখে, কিন্তু সর্বশেষ মান পড়তে চাইলে আগে sync চালাতে হয়। তাহলে “CP” তকমা কোন কাজের জন্য সত্যি? লেখার জন্য হ্যাঁ, default পড়ার জন্য নয়।

Kleppmann-এর প্রস্তাব: তকমার বদলে প্রতিটি কাজের নিশ্চয়তা বলুন — “এই store-এ লেখা linearizable, পড়া follower থেকে হলে কয়েক সেকেন্ড পুরোনো হতে পারে, read-your-writes পেতে এই setting চালু করুন”। পাশাপাশি latency-কে প্রথম শ্রেণির বিবেচনা ধরুন, কারণ partition আর ধীর network ভেতর থেকে আলাদা করা যায় না। Jepsen-এর মতো স্বাধীন পরীক্ষাতেও বারবার দেখা গেছে, documentation-এর দাবি আর partition-এর মুখে আসল আচরণ সবসময় মেলে না — তাই দাবি যাচাইযোগ্য ভাষায় থাকা দরকার।

মূল উপলব্ধি: “CP/AP” একটি সিস্টেমকে দুই ঘরে ঠেলে দেয়, অথচ নিশ্চয়তা থাকে প্রতিটি কাজে আর প্রতিটি setting-এ; নকশার নথিতে তকমা নয়, কাজভিত্তিক প্রতিশ্রুতি লিখুন।

প্র ০৩আপনার ল্যাপটপের CPU-ও sequentially consistent নয় — Lamport-এর ১৯৭৯ সালের সংজ্ঞা আর আধুনিক processor-এর memory model থেকে distributed system কী শেখে?

Sequential consistency-র জন্ম distributed database-এ নয়, multiprocessor কম্পিউটারে। Lamport ১৯৭৯ সালের প্রবন্ধে প্রশ্ন তোলেন: কয়েকটি processor একই memory ভাগ করলে প্রোগ্রামার কী আশা করতে পারেন? তাঁর উত্তর — সব processor-এর কাজ মিলে যেন একটি ক্রম, যেখানে প্রতিটি processor-এর নিজের প্রোগ্রামের ক্রম ঠিক থাকে।

আধুনিক CPU এটিও দেয় না, গতির জন্য। x86 processor-এ প্রতিটি core-এর একটি store buffer থাকে: লেখা আগে সেখানে জমে, পরে memory-তে যায়। একটি পরিচিত পরীক্ষা: শুরুতে x = y = 0; thread 1 লেখে x = 1, তারপর পড়ে y; thread 2 লেখে y = 1, তারপর পড়ে x. Sequential consistency-তে অন্তত একজন 1 দেখবেই। অথচ x86-এ দুজনেই 0 পড়তে পারে, কারণ দুজনের লেখাই তখনো নিজের buffer-এ। Sewell ও সহকর্মীদের ২০১০ সালের x86-TSO কাজে এই আচরণের আনুষ্ঠানিক মডেল আছে; ARM-এর মতো processor-এর memory model আরও ঢিলা।

প্রোগ্রামার তাহলে কী করেন? দরকারি জায়গায় memory fence বা বিশেষ atomic নির্দেশনা, যা buffer খালি না হওয়া পর্যন্ত থামায় — দাম গতি। Java-র volatile বা C++-এর seq_cst atomic ঠিক এই চুক্তি দেয়: সাধারণ variable-এ দ্রুত কিন্তু ঢিলা, চিহ্নিত variable-এ ধীর কিন্তু কড়া। Mutex আর semaphore-এর ভেতরেও এই fence (অপারেটিং সিস্টেম কোর্সের mutex আর semaphore; cache coherence-এর ভিত্তি কম্পিউটার আর্কিটেকচার কোর্সের multicore আর coherence পর্বে)।

শিক্ষাটি পুরো এই পর্বের প্রতিচ্ছবি। একটি chip-এর ভেতরের দূরত্ব কয়েক সেন্টিমিটার, তবুও কড়া consistency-র দাম এত বেশি যে hardware ডিজাইনাররা তা default রাখেননি। Region পেরোনো network-এ দূরত্ব হাজার কিলোমিটার — সেখানে সবকিছু linearizable রাখার দাম আরও বড়। দুই জগতেই একই সমাধান: default-এ দুর্বল আর দ্রুত, আর যেখানে ঠিকতা জরুরি সেখানে স্পষ্টভাবে কড়া নিশ্চয়তা চাওয়া।

মূল উপলব্ধি: Consistency model একটি সাধারণ ধারণা — CPU-র memory থেকে বহু region-এর database পর্যন্ত; সর্বত্র দর-কষাকষি একই: কড়া চুক্তি বোঝা সহজ, দুর্বল চুক্তি দ্রুত।

প্র ০৪Partition-এর সময়েও প্রতিটি node উত্তর দেবে — এই শর্তে সবচেয়ে কড়া কোন consistency পাওয়া সম্ভব? তবুও শিল্পে বেশিরভাগ ব্যবস্থা কেন “eventual”-এ থেমে যায়?

CAP বলে linearizability আর সবসময়-available একসাথে নয়। কিন্তু তার নিচে কতটা পর্যন্ত যাওয়া যায়? ছবি ৪০.১-এর লাল রেখার যুক্তি এখান থেকেই। Sequential consistency-ও partition-এ রাখা যায় না — দুই দল দুটি আলাদা লেখা নিলে সবার জন্য একটি ক্রম ঠিক করার উপায় নেই, যদি না একদিক থামে। Causal consistency কিন্তু রাখা যায়: প্রতিটি দল নিজের দেখা কারণ-ফল মেনে চলে; দুই দলের একসাথে হওয়া লেখা causal-এর চোখে “সম্পর্কহীন”, তাই ভিন্ন ক্রমে দেখা অনুমোদিত।

Mahajan, Alvisi আর Dahlin ২০১১ সালের একটি প্রযুক্তিগত প্রতিবেদনে এর একটি আনুষ্ঠানিক রূপ দেখান: সবসময় available আর শেষে মিলে যায় এমন ব্যবস্থায় real-time causal consistency-র চেয়ে কড়া কিছু সম্ভব নয় (তাঁদের মডেলের শর্তে)। একই বছর COPS (Lloyd ও সহকর্মীরা, SOSP ২০১১) দেখায় বাস্তবে কীভাবে: প্রতিটি লেখার সাথে তার নির্ভরতা — “আমি কোন লেখাগুলো দেখে লিখেছি” — যায়, আর অন্য region সেই নির্ভরতা পৌঁছানোর আগে লেখাটি দৃশ্যমান করে না। তারা একে causal+ বলে: causal, আর conflict-এ সবাই একই ফলে পৌঁছায়।

তবুও শিল্পে বেশিরভাগ AP ব্যবস্থা eventual + কয়েকটি session guarantee দেয়, causal নয়। কারণ কয়েকটি। নির্ভরতার metadata বাড়তে থাকে, আর সেটি বয়ে নেওয়া, যাচাই করা খরচসাপেক্ষ। Causal নিশ্চয়তা কেবল store-এর ভেতরের কারণ-ফল দেখে — ব্যবহারকারী ফোনে বন্ধুকে খবর দিলে (“side channel”) সেটি store জানে না। Client-কে একই replica-তে থাকতে হয়, নইলে নিজের অতীতই হারায়। আর অনেক অ্যাপে দরকারি কারণ-ফল সংকীর্ণ — একটি চ্যাটের বার্তা — যা একটি partition-এ সব রেখে আরও সস্তায় পাওয়া যায় (পর্ব ২২-এর consistent prefix-এর আলোচনা)।

মূল উপলব্ধি: Partition-এও available থাকার সীমা causal পর্যন্ত, eventual পর্যন্ত নয় — কিন্তু সেই সীমা ছুঁতে metadata আর জটিলতার দাম লাগে, তাই বেশিরভাগ নকশা শুধু যেখানে কারণ-ফল চোখে পড়ে সেখানেই তা কেনে।

প্র ০৫একটি database সত্যিই linearizable কি না, বাইরে থেকে কীভাবে যাচাই করবেন? Jepsen-ধাঁচের পরীক্ষা কী শিখিয়েছে, আর এই যাচাই কেন কম্পিউটেশনালি কঠিন?

পদ্ধতিটি অংশ ৬-এর checker-এর বড় রূপ। কয়েকটি client একসাথে একটি চালু cluster-এ পড়া, লেখা আর compare-and-set পাঠায়, আর প্রতিটির শুরু, শেষ আর ফল রেকর্ড করে — একটি history. সেই সময়ে পরীক্ষক ইচ্ছাকৃতভাবে ব্যর্থতা ঘটান: network ভাগ করা, node থামানো, ঘড়ি সরানো। তারপর checker খোঁজে: এই history-র জন্য কোনো বৈধ linearization আছে কি?

Kyle Kingsbury ২০১৩ সাল থেকে Jepsen নামে এই পদ্ধতি জনপ্রিয় করেন, আর তাঁর প্রকাশিত বিশ্লেষণে বহু জনপ্রিয় database আর coordination সেবার নির্দিষ্ট সংস্করণে partition-এর মুখে হারানো লেখা বা পুরোনো পড়া ধরা পড়েছে, যা documentation-এর দাবির সাথে মেলেনি; অনেক ক্ষেত্রে পরে সংশোধন হয়েছে। Checker হিসেবে প্রথমে Knossos (linearizability), পরে Elle (transaction-এর isolation, Kingsbury ও Alvaro-র ২০২০ সালের প্রবন্ধ)।

কঠিনতা কোথায়? অংশ ৬-এর brute force সব ক্রম চেষ্টা করে — n!টি। Gibbons আর Korach ১৯৯৭ সালে দেখান, সাধারণ ক্ষেত্রে একটি history linearizable কি না যাচাই NP-complete. বাস্তবের checker তাই কৌশল খাটায়: প্রতিটি key আলাদা করে দেখা (linearizability local — অংশ ৬), বাস্তব সময়ের ক্রম দিয়ে অসম্ভব শাখা আগেই ছাঁটা, আগে দেখা অবস্থা মনে রাখা (Wing ও Gong-এর ১৯৯৩ সালের algorithm-এর পরের উন্নত রূপ)। তবুও লম্বা, বহু-concurrent history-তে checker-এর সময় বা memory ফুরিয়ে যেতে পারে, তাই পরীক্ষা ছোট টুকরোয় সাজানো হয়।

নিজের দলের জন্য শিক্ষা: consistency-র দাবি একটি পরীক্ষাযোগ্য বক্তব্য। Staging-এ fault injection (partition, process থামানো), history রেকর্ড আর একটি checker — বড় যন্ত্রপাতি ছাড়াও শুরু করা যায়। আর কোনো পণ্যের “strongly consistent” দাবি পড়লে দেখুন, কোন setting আর কোন সংস্করণে, কোন স্বাধীন পরীক্ষায় তা টিকেছে।

মূল উপলব্ধি: Consistency শুধু নকশার নথির শব্দ নয়, history-র ওপর একটি যাচাইযোগ্য দাবি; ব্যর্থতা ঘটিয়ে history পরীক্ষা না করা পর্যন্ত দাবিটি আশামাত্র।

অনুশীলন

খাতা-কলম আর উপরের সিমুলেটর পাশে রাখুন। আগে নিজে করুন, তারপর উত্তর মিলিয়ে নিন।

  1. PACELC আর CAP-এর হিসাব: একটি কাল্পনিক সেবার তিনটি replica তিনটি region X, Y, Z-এ। RTT: X–Y ৩০ ms, Y–Z ৬০ ms, X–Z ৮০ ms. ব্যবহারকারীদের ৫০% X-এর কাছে, ৩০% Y-এর, ২০% Z-এর; প্রত্যেকে নিজের region-এর replica-তে যান, local পড়া ১ ms. Linearizable পড়া = নিজের replica + সবচেয়ে কাছের আরেকটির সম্মতি (৩টির ২টি)। (ক) গড় linearizable পড়ার latency কত, local পড়ার কত গুণ? (খ) একটি পাতায় পরপর ৫টি পড়া হলে কত? (গ) Partition-এ Z একা আলাদা হলে, CP নিয়মে (সংখ্যাগরিষ্ঠ লাগবে) কত ভাগ ব্যবহারকারী সেবা পাবেন? X একা আলাদা হলে? (ঘ) দ্বিতীয় ক্ষেত্রটি কীভাবে উন্নত করবেন?

    (ক) X: ১ + ৩০ = ৩১ ms, কাছের Y; Y: ১ + ৩০ = ৩১ ms, কাছের X; Z: ১ + ৬০ = ৬১ ms, কাছের Y. গড় = ০.৫ × ৩১ + ০.৩ × ৩১ + ০.২ × ৬১ = ১৫.৫ + ৯.৩ + ১২.২ = ৩৭ ms — local-এর ৩৭ গুণ।

    (খ) ৫ × ৩৭ = ১৮৫ ms, local-এ ৫ ms. পড়াগুলো একসাথে (parallel) পাঠালে প্রায় একটির সমান, তাই API নকশায় ক্রমিক পড়া এড়ানোও একটি সমাধান।

    (গ) Z আলাদা: X আর Y-এর দুটি replica সংখ্যাগরিষ্ঠ, তাই X আর Y-এর ব্যবহারকারী চলেন — ৮০%; Z-এর ২০% প্রত্যাখ্যান পান। X আলাদা: Y + Z সংখ্যাগরিষ্ঠ, X-এর ৫০% ব্যবহারকারী বাদ — অর্ধেক সেবা বন্ধ।

    (ঘ) X আলাদা হলে দুই পাশেই ৫০% ব্যবহারকারী, তাই ভোট যেভাবেই সাজান সর্বোচ্চ ৫০%. আসল উন্নতি দুটি: X-এর ব্যবহারকারীকে অন্য পথে সংখ্যাগরিষ্ঠের region-এ পৌঁছানোর ব্যবস্থা (DNS বা anycast-এ সরানো, পর্ব ৫১; দাম বাড়তি latency), আর যেসব পড়ায় পুরোনো উত্তর চলে সেগুলো partition-এ local replica থেকে AP মোডে চালু রাখা। CAP-এর A আর মাপা availability আলাদা — client সংখ্যাগরিষ্ঠে পৌঁছাতে পারলে CP ব্যবস্থাও প্রায় সবাইকে সেবা দেয়।

  2. একটি spec সমালোচনা করুন: একটি কাল্পনিক mobile banking দলের নকশা-নথিতে লেখা: “সব ডেটা strongly consistent হবে; পাঁচটি region-এ active-active; network partition-এও ১০০% available; প্রতিটি অনুরোধ ৫০ ms-এর মধ্যে; consistency-র জন্য database ACID, তাই চিন্তা নেই।” ভুল আর পরস্পরবিরোধী দাবিগুলো চিহ্নিত করুন, আর feature ধরে একটি সংশোধিত নকশা প্রস্তাব করুন।

    পরস্পরবিরোধী দাবি: (১) “Strongly consistent” (linearizable ধরলে) আর “partition-এও ১০০% available” — CAP সরাসরি নিষেধ করে। (২) পাঁচ region-এ linearizable লেখা মানে প্রতিটি লেখায় সংখ্যাগরিষ্ঠের round trip; region-গুলো দূরে হলে ৫০ ms-এর বাজেট কেবল network-এই শেষ (PACELC-এর E অংশ)। (৩) “ACID, তাই চিন্তা নেই” — ACID-এর C অ্যাপের নিয়ম রক্ষা; replica-দের মিলের সাথে সম্পর্ক নেই, আর active-active-এ দুই region-এ একই অ্যাকাউন্ট থেকে একসাথে খরচ ঠেকানো আলাদা প্রশ্ন। (৪) “Strongly consistent” শব্দটির model-ই বলা নেই।

    সংশোধিত নকশা: টাকা কাটা আর লেনদেনের খাতা — প্রতিটি অ্যাকাউন্টের একটি “home” region, সেখানে consensus-ভিত্তিক leader; লেখা linearizable, partition-এ সংখ্যালঘু দিকে “পরে চেষ্টা করুন” আর idempotency key-সহ retry. ব্যালান্স দেখানো — কাছের replica থেকে, “শেষ হালনাগাদ” সময়সহ, নিজের লেনদেনের পর read-your-writes token দিয়ে। লেনদেনের ইতিহাস আর নোটিফিকেশন — eventual, কিন্তু প্রতি অ্যাকাউন্টে ক্রম রক্ষা। প্রোফাইল ছবি, অফার, পুরস্কার পয়েন্টের প্রদর্শন — eventual. SLO লিখুন feature ধরে: “টাকা পাঠানো p99 ৩০০ ms, partition-এ সংখ্যালঘু region-এ প্রত্যাখ্যান অনুমোদিত”; “ব্যালান্স দেখা p99 ৫০ ms, ৫ সেকেন্ড পর্যন্ত পুরোনো চলে”।

  3. History শ্রেণিবিভাগ করুন: একটি register, শুরুর মান 0; প্রতিটি কাজ client: কাজ [শুরু, শেষ] ms-এ। প্রতিটির জন্য বলুন: linearizable? sequential? আর কোনো session guarantee (RYW, MR) ভেঙেছে কি?
    (ক) A: w(1) [0,2], B: r→1 [3,4], B: r→0 [5,6]
    (খ) A: w(1) [0,2], A: r→1 [3,4], B: r→0 [5,6]
    (গ) A: w(1) [0,6], B: r→0 [1,2], C: r→1 [3,4], B: r→1 [5,7]
    (ঘ) A: w(1) [0,2], A: r→0 [3,4]

    (ক) linearizable নয়, sequential নয়; monotonic reads ভাঙা। B আগে 1, পরে 0 দেখলেন। B-র নিজের ক্রম মানলে দ্বিতীয় পড়া প্রথমটির পরে, আর প্রথম পড়ার আগেই 1 লেখা হয়ে গেছে — 0 ফেরার মতো কোনো লেখা নেই। RYW প্রশ্ন নয়, B কিছু লেখেননি।

    (খ) linearizable নয়, কিন্তু sequential; RYW আর MR ঠিক আছে। B-র পড়া শুরু ৫ ms-এ, লেখা শেষ ২ ms-এ — বাস্তব সময় মানলে 1 দেখতেই হতো। ক্রম B:r(0) → A:w(1) → A:r(1) প্রতি client-এর ক্রম মানে। অন্য ব্যবহারকারী পুরোনো দেখলেন — এটি শুধু linearizability-র লঙ্ঘন।

    (গ) linearizable (তাই sequential-ও); কোনো লঙ্ঘন নেই। লেখার বিন্দু রাখুন যেমন ২ আর ৩ ms-এর মাঝে: B-র প্রথম পড়া আগে, তাই 0; C আর B-র দ্বিতীয় পড়া পরে, তাই 1. লেখার পরিসর লম্বা হলে পড়াগুলো ভিন্ন মান দেখতে পারে — সেটি বৈধ।

    (ঘ) কোনোটিই নয়; read-your-writes ভাঙা। A নিজে 1 লিখে উত্তর পাওয়ার পর 0 পড়লেন — কোনো মডেলেই একজন client-এর নিজের ক্রম ভাঙা চলে না।

    অংশ ৬-এর checker-এ এগুলো বসিয়ে মিলিয়ে নিতে পারেন: (ক) NOT LINEARIZABLE / NOT SEQUENTIAL; (খ) NOT LINEARIZABLE / SEQUENTIAL B:r(0) -> A:w(1) -> A:r(1); (গ) LINEARIZABLE B:r(0) -> A:w(1) -> C:r(1) -> B:r(1); (ঘ) NOT LINEARIZABLE / NOT SEQUENTIAL.

  4. Output অনুমান করুন: অংশ ৬-এর checker-এর H-এ চতুর্থ একটি history যোগ করা হলো — দুজন লেখক, একজন পাঠক:
    H4: [["A","w",1,0,3], ["B","w",2,1,8], ["C","r",2,4,5], ["C","r",1,6,7]]
    চালানোর আগে অনুমান করুন H4-এর দুটি লাইন কী ছাপবে — linearizable কি না, sequential কি না, আর sequential হলে সাক্ষী-ক্রমটি কী (checker সব ক্রম H4-এর লেখার ক্রম ধরে ধাপে ধাপে চেষ্টা করে, প্রথম বৈধটি ছাপে)। তারপর কোডে যোগ করে চালিয়ে মিলিয়ে নিন।

    Linearizable নয়। A-র w(1) শেষ ৩ ms-এ, C-র দুটি পড়াই তার পরে শুরু — তাই w(1) দুটি পড়ার আগে। C প্রথমে 2 পেলেন, অর্থাৎ w(2) তাঁর প্রথম পড়ার আগে আর w(1)-এর পরে। তাহলে C-র দ্বিতীয় পড়ার সময় শেষ লেখা w(2) — 1 পাওয়া অসম্ভব।

    Sequential consistency আছে। বাস্তব সময় বাদ দিলে শর্ত শুধু C-র নিজের ক্রম — আগে r→2, পরে r→1. ক্রম: w(2) → C-র r(2) → w(1) → C-র r(1) — বৈধ। A-র লেখা বাস্তবে ৩ ms-এ শেষ হলেও ক্রমে সে C-র প্রথম পড়ার পরে বসেছে — sequential consistency এটি অনুমোদন করে। Checker প্রথমে A দিয়ে শুরু হওয়া সব ক্রম চেষ্টা করে (কোনোটি বৈধ নয়), তারপর B দিয়ে শুরু করে এই ক্রমটি পায়।

    চালানোর পর আসল আউটপুট:

    H1: LINEARIZABLE      B:r(0) -> A:w(1) -> C:r(1)
        SEQUENTIAL        B:r(0) -> A:w(1) -> C:r(1)
    H2: NOT LINEARIZABLE
        SEQUENTIAL        C:r(0) -> A:w(1) -> B:r(1)
    H3: NOT LINEARIZABLE
        NOT SEQUENTIAL
    H4: NOT LINEARIZABLE
        SEQUENTIAL        B:w(2) -> C:r(2) -> A:w(1) -> C:r(1)

    বাস্তবে এমন history আসে যখন দুই region-এ দুটি লেখা একসাথে ঘটে আর একটি region পরে পৌঁছানো পুরোনো লেখাটিকে “নতুন” ভেবে প্রয়োগ করে — ব্যবহারকারীর চোখে মান 2 থেকে ফিরে 1.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমGilbert–Lynch-এর CAP প্রমাণের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ✂️ Network দুই দলে ভাগ হলো (G1, G2); মাঝের সব বার্তা হারায়
  2. ✍️ G1-এ একটি লেখা এল; availability-র জন্য G1 উত্তর দিল, লেখা শেষ
  3. 📖 এরপর G2-তে একটি পড়ার অনুরোধ এল; G2-কেও উত্তর দিতে হবে
  4. 🙈 G2 কোনো বার্তা পায়নি, তাই পুরোনো মান ফেরত দিল
  5. ❌ লেখা শেষ হওয়ার পরে শুরু হওয়া পড়া পুরোনো মান পেল — linearizability ভাঙল

Gilbert ও Lynch-এর CAP প্রমাণে “availability” বলতে কী বোঝানো হয়েছে?

  • বছরে অন্তত ৯৯.৯৯% সময় সেবা চালু থাকা
  • সংখ্যাগরিষ্ঠ node চালু থাকলে অনুরোধের উত্তর পাওয়া
  • চালু থাকা প্রতিটি node-এ আসা প্রতিটি অনুরোধ শেষ পর্যন্ত একটি error নয় এমন উত্তর পাবে
  • প্রতিটি অনুরোধের উত্তর ১০০ ms-এর মধ্যে আসা

একটি দলের দাবি: “আমাদের তিন node-এর cluster CA — partition tolerance আমরা বাদ দিয়েছি।” এর সবচেয়ে সঠিক মূল্যায়ন কোনটি?

  • ঠিক আছে, এক ডেটা সেন্টারে partition কখনো হয় না
  • কথাটির মানে প্রায় নেই: partition ঘটতে পারেই, তখন C না A — একটি ছাড়তেই হবে, আর আগে থেকে না বাছলে প্রায়ই দুটিই ঝুঁকিতে
  • ঠিক আছে, যদি cluster Raft বা Paxos ব্যবহার করে
  • ঠিক আছে, যদি database ACID transaction সমর্থন করে

একটি store স্বাভাবিক সময়ে সবচেয়ে কাছের replica থেকে কোনো সমন্বয় ছাড়াই পড়া দেয়, আর partition-এর সময়েও দুই দিকেই লেখা নিতে থাকে। PACELC অনুযায়ী এটি কোন শ্রেণির?

  • PA/EL
  • PC/EC
  • PA/EC
  • PC/EL

একটি register-এর শুরুর মান 0. History: A লিখলেন 1, সময় [0, 10] ms; B পড়ে পেলেন 1, সময় [2, 4]; C পড়ে পেলেন 0, সময় [6, 8]. এই history সম্পর্কে কোনটি সঠিক?

  • Linearizable, কারণ A-র লেখা তখনো চলছিল
  • Linearizable নয়, sequential-ও নয়
  • শুধু read-your-writes ভেঙেছে
  • Linearizable নয়, কিন্তু sequentially consistent

Mitu প্রথমে প্রোফাইলে নতুন ফোন নম্বর দিলেন, তারপর OTP দিয়ে “নম্বর যাচাই হয়েছে” চিহ্ন বসালেন। একটি replica দ্বিতীয় লেখাটি আগে আর প্রথমটি পরে প্রয়োগ করল, ফলে যাচাই-চিহ্ন কিছুক্ষণ পুরোনো নম্বরের পাশে দেখা গেল। কোন session guarantee ভেঙেছে?

  • Read-your-writes
  • Monotonic writes
  • Monotonic reads
  • Writes-follow-reads

Serializability আর linearizability সম্পর্কে কোন বক্তব্যটি সঠিক?

  • দুটি একই জিনিসের দুটি নাম
  • Linearizable store হলে কয়েকটি key জুড়ে transaction আপনিই serializable হয়
  • Serializability বাস্তব সময় মানার শর্ত দেয় না; linearizability দেয়, কিন্তু একটি object-এর জন্য — দুটি একসাথে হলে strict serializability
  • SERIALIZABLE isolation চালু করলে সব replica থেকে পড়াও linearizable হয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. CAP (Brewer ২০০০, Gilbert–Lynch ২০০২): C = linearizability, A = প্রতিটি চালু node-এর উত্তর, P = বার্তা হারানো। Partition ঠেকানো যায় না; বাছাই শুধু তখন — C না A, আর প্রতিটি কাজে আলাদা।
  2. “তিনের দুই” আর “CA” ভুল ব্যাখ্যা; CAP-এর A মাপা uptime নয়; partition দেখতে ধীর network-এর মতো, তাই বাছাই ঘটে প্রতিটি timeout-এ।
  3. PACELC: স্বাভাবিক সময়েও consistency-র দাম latency — region পেরোনো সমন্বয় প্রতিটি কাজে কয়েক দশ ms; শ্রেণি পণ্যের নয়, setting-এর।
  4. সিঁড়ি: linearizable (বাস্তব সময়) → sequential (এক ক্রম) → causal (কারণ-ফল) → চার session guarantee → eventual (শুধু মিলবে); causal পর্যন্ত partition-এও available থাকা যায়।
  5. Serializability ≠ linearizability; দুটি একসাথে strict serializability. Model বাছুন feature ধরে — invariant-এর জায়গায় linearizable, বাকিটা eventual + session guarantee.

পরের পর্বে: Leader ছাড়াই কপিগুলো কীভাবে লেখা নেয় আর পড়ায় নতুন মান খুঁজে পায়? R + W > N-এর ওভারল্যাপ, sloppy quorum, read repair আর hinted handoff — আর কেন শুধু quorum linearizability-র নিশ্চয়তা দেয় না।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান