পাঠ ০৪ · ৫১-এর মধ্যে · মডিউল ১
Home / Courses / System Design / CAP থিওরেম

CAP থিওরেম ও কনসিস্টেন্সি মডেল

The CAP theorem & consistency models
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • CAP থিওরেম কী বলে এবং কেন বাস্তবে এটি মূলত CP বনাম AP-এর সিদ্ধান্ত
  • CP ও AP সিস্টেমের বাস্তব উদাহরণ (ZooKeeper, Cassandra, DynamoDB, DNS)
  • স্ট্রং, ইভেনচুয়াল ও কজাল কনসিস্টেন্সি মডেলের পার্থক্য
  • PACELC এক্সটেনশন — লেটেন্সি বনাম কনসিস্টেন্সির ট্রেড-অফ, পার্টিশন ছাড়াই
  • Python-এ দুটো রেপ্লিকা সিমুলেট করে স্ট্রং বনাম ইভেনচুয়াল রাইট-রিড আচরণের পার্থক্য দেখা

১ · CAP থিওরেম — তিনটি গ্যারান্টি, একসাথে সব নয়

CAP থিওরেম বলে একটি ডিস্ট্রিবিউটেড সিস্টেম নিচের তিনটি গ্যারান্টির মধ্যে একই সাথে সবগুলো সম্পূর্ণভাবে দিতে পারে না —

Consistency (C)
প্রতিটি রিড সর্বশেষ সফল রাইট দেখে — সব নোড একই মুহূর্তে একই ডেটা দেখায়।
Availability (A)
প্রতিটি রিকোয়েস্ট (রিড বা রাইট) একটি রেসপন্স পায় — এমনকি কিছু নোড ডাউন থাকলেও, কখনো "no response" নয়।
Partition Tolerance (P)
নোডগুলোর মধ্যে নেটওয়ার্ক বার্তা হারিয়ে গেলে বা বিলম্বিত হলেও সিস্টেম কাজ চালিয়ে যায়।
আসল সিদ্ধান্ত — CP বনাম AP

বাস্তব জগতে নেটওয়ার্ক পার্টিশন (P) কখনো কখনো ঘটবেই — কেবল, রাউটার, ডেটাসেন্টার সংযোগ ব্যর্থ হতে পারে। তাই Partition Tolerance ছেড়ে দেওয়ার বিকল্প নেই। ফলে CAP থিওরেমের আসল অর্থ দাঁড়ায় — পার্টিশন ঘটলে, আপনাকে বেছে নিতে হবে Consistency (CP: পার্টিশনের সময় কিছু নোড রিকোয়েস্ট প্রত্যাখ্যান করবে, ডেটা সঠিক রাখতে) অথবা Availability (AP: সব নোড রেসপন্স দেবে, কিন্তু কিছু রেসপন্স পুরনো ডেটা হতে পারে)।

২ · CP বনাম AP — বাস্তব উদাহরণ

CP সিস্টেম
ZooKeeper, HBase, ঐতিহ্যগতভাবে কনফিগার করা অনেক RDBMS ক্লাস্টার — পার্টিশনের সময় কনসিস্টেন্সি রক্ষায় কিছু রিকোয়েস্ট প্রত্যাখ্যান করে।
AP সিস্টেম
Cassandra, DynamoDB (টিউনেবল), DNS — পার্টিশনের সময়ও সবসময় রেসপন্স দেয়, এমনকি পুরনো ডেটা হলেও।

কোনটি সঠিক তা নির্ভর করে ব্যবহারের উপর — একটি ব্যাংকিং লেজারে ভুল ব্যালেন্স দেখানো (AP-এর ঝুঁকি) মেনে নেওয়া যায় না, তাই CP প্রয়োজন। কিন্তু একটি সোশ্যাল মিডিয়া লাইক কাউন্টে সামান্য পুরনো সংখ্যা (কয়েক সেকেন্ড আগের) দেখানো সম্পূর্ণ গ্রহণযোগ্য, তাই AP বেছে নিয়ে সবসময়-উপলব্ধ থাকাটাই বেশি মূল্যবান।

৩ · কনসিস্টেন্সি মডেল

Strong Consistency
যেকোনো রাইটের পর, যেকোনো রেপ্লিকা থেকে যেকোনো রিড অবিলম্বে সর্বশেষ মান দেখায়।
Eventual Consistency
রাইটের পরপরই কিছু রেপ্লিকা পুরনো মান দেখাতে পারে, কিন্তু নতুন রাইট না এলে সবাই শেষমেশ (eventually) একই মানে কনভার্জ করে।
Causal Consistency
কার্যকারণসম্পর্কযুক্ত (causally related) অপারেশনগুলো সব নোডে একই ক্রমে দেখা যায় — কিন্তু সম্পূর্ণ অসম্পর্কিত অপারেশনের ক্রম ভিন্ন হতে পারে (M8-এ vector clocks, L30-এ বিস্তারিত)।
স্ট্রং ক্লায়েন্ট রাইট Write রেপ্লিকা ১ রেপ্লিকা ২ উভয়ে আপডেট হওয়ার পরই রেসপন্স যায় ইভেনচুয়াল ক্লায়েন্ট রাইট Write রেপ্লিকা ১ অবিলম্বে রেপ্লিকা ২ sync() পরে
স্ট্রং কনসিস্টেন্সি — রাইট দুই রেপ্লিকাতেই সম্পন্ন হওয়ার পর রেসপন্স যায়। ইভেনচুয়াল — একটি রেপ্লিকা অবিলম্বে আপডেট হয়, বাকিটা পরে (ড্যাশড লাইন) sync হয়।

৪ · কোডে দেখা — স্ট্রং বনাম ইভেনচুয়াল রাইট

নিচের সিমুলেশনে দুটি রেপ্লিকা (dict) আছে। write_strong() দুটো রেপ্লিকাই আপডেট করার পরই রিটার্ন করে — তাই রাইটের পরপরই যেকোনো রেপ্লিকা পড়লে সবসময় নতুন মান পাওয়া যায়। কিন্তু write_eventual() শুধু রেপ্লিকা ১ আপডেট করে সাথে সাথেই রিটার্ন করে — রেপ্লিকা ২ আপডেট হয় শুধুমাত্র পরে আলাদাভাবে sync() কল করলে। ফলে রাইটের ঠিক পরে রেপ্লিকা ২ পড়লে পুরনো মান পাওয়া যায়, যতক্ষণ না sync() চলে।

Python
replica1 = {}
replica2 = {}

def write_strong(key, value):
    # দুটো রেপ্লিকাই আপডেট হওয়ার পরই রিটার্ন করে — ব্লক করে অপেক্ষা করে
    replica1[key] = value
    replica2[key] = value
    return "ok (both replicas updated)"

def write_eventual(key, value):
    # শুধু রেপ্লিকা ১ অবিলম্বে আপডেট হয়, রেপ্লিকা ২ পরে sync() এ আপডেট হবে
    replica1[key] = value
    return "ok (replica1 updated, replica2 pending sync)"

def sync():
    # পরে, ব্যাকগ্রাউন্ডে চলা একটি এক্সপ্লিসিট সিঙ্ক কল
    replica2.update(replica1)

print("--- স্ট্রং কনসিস্টেন্সি ---")
write_strong("balance", 500)
print("রেপ্লিকা ১:", replica1["balance"])
print("রেপ্লিকা ২:", replica2["balance"], "(রাইটের পরপরই — নতুন মান!)")

print()
print("--- ইভেনচুয়াল কনসিস্টেন্সি ---")
replica2["balance"] = 500   # আগের অবস্থা রিসেট (দুই রেপ্লিকাই সমান থেকে শুরু)
write_eventual("balance", 900)
print("রেপ্লিকা ১:", replica1["balance"], "(অবিলম্বে নতুন মান)")
print("রেপ্লিকা ২ (sync-এর আগে):", replica2["balance"], "(এখনো পুরনো মান — stale!)")

sync()
print("রেপ্লিকা ২ (sync-এর পরে):", replica2["balance"], "(এখন কনভার্জড)")

    
লক্ষ্য করুন — ইভেনচুয়াল কনসিস্টেন্সিতে write_eventual() অনেক দ্রুত রিটার্ন করে (শুধু একটি রেপ্লিকা আপডেট করতে হয়), কিন্তু সাময়িকভাবে রেপ্লিকা ২ ভুল/পুরনো ডেটা দেখাতে পারে। এটাই AP সিস্টেমের মূল ট্রেড-অফ — গতি ও সবসময়-উপলব্ধতার বিনিময়ে সাময়িক অসামঞ্জস্যতা মেনে নেওয়া।

৫ · PACELC — CAP-এর সম্প্রসারণ

CAP শুধু পার্টিশনের সময়কার সিদ্ধান্ত নিয়ে কথা বলে। কিন্তু PACELC যোগ করে — পার্টিশন (P) থাকলে Availability (A) বনাম Consistency (C) বেছে নিতে হয়, নাহলে (Else, E) — এমনকি স্বাভাবিক অবস্থায়ও — Latency (L) বনাম Consistency (C)-এর মধ্যে একটি ট্রেড-অফ থেকে যায়। কারণ স্ট্রং কনসিস্টেন্সি নিশ্চিত করতে (সব রেপ্লিকা আপডেট নিশ্চিত করা) সবসময় কিছুটা বাড়তি সময় (নেটওয়ার্ক রাউন্ড ট্রিপ) লাগে — এমনকি পার্টিশন না থাকলেও।

মূল কথা · Key takeaway

CAP থিওরেম কোনো "নিয়ম ভাঙা" নয় — এটি একটি বাস্তবতা যে ডিস্ট্রিবিউটেড সিস্টেমে ট্রেড-অফ অনিবার্য। System Design-এ প্রতিটি ডেটাবেস/সার্ভিস বেছে নেওয়ার সময় প্রশ্ন করতে হবে — এই ডেটার জন্য সাময়িক অসামঞ্জস্যতা মেনে নেওয়া যায় (AP), নাকি সবসময় সঠিক থাকা আবশ্যক (CP)? M4 (ডেটাবেস), M8 (ডিস্ট্রিবিউটেড থিওরি) — কোর্সের বাকি অংশজুড়ে এই সিদ্ধান্তই বারবার ফিরে আসবে।

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

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কেন বলা হয় বাস্তবে CAP আসলে "CP বনাম AP"-এর সিদ্ধান্ত, তিনটির মধ্যে যেকোনো দুটো বেছে নেওয়া নয়?

কারণ Partition Tolerance (P) ছেড়ে দেওয়ার বাস্তবসম্মত উপায় নেই — যেকোনো মাল্টি-নোড সিস্টেমে নেটওয়ার্ক বিচ্ছিন্নতা কখনো না কখনো ঘটবেই (কেবল কাটা, রাউটার ব্যর্থতা, ডেটাসেন্টার আউটেজ)। "CA" সিস্টেম (P ছাড়া C ও A) শুধু তখনই সম্ভব যখন পুরো সিস্টেম একটি একক নোড বা নেটওয়ার্ক-নির্ভরতাহীন পরিবেশে চলে — যা বড় স্কেলের ডিস্ট্রিবিউটেড সিস্টেমের জন্য বাস্তবসম্মত নয়। তাই বাস্তব প্রশ্নটা সবসময় হয় — পার্টিশন ঘটলে, C নাকি A?

প্র ০২ একটি ই-কমার্স সাইটে "প্রোডাক্ট ভিউ কাউন্ট" এবং "চেকআউট/পেমেন্ট" — এই দুটো ভিন্ন ফিচারের জন্য ভিন্ন কনসিস্টেন্সি মডেল বেছে নেওয়া কেন যুক্তিসঙ্গত?

প্রোডাক্ট ভিউ কাউন্ট সামান্য পুরনো (কয়েক সেকেন্ডের ব্যবধানে) দেখালে ইউজার অভিজ্ঞতায় কোনো বাস্তব ক্ষতি হয় না — তাই এখানে ইভেনচুয়াল কনসিস্টেন্সি (AP) বেছে নিয়ে দ্রুত, সবসময়-উপলব্ধ সিস্টেম পাওয়া লাভজনক। কিন্তু চেকআউট/পেমেন্টে ভুল ইনভেন্টরি কাউন্ট বা দুবার চার্জ হওয়া গুরুতর সমস্যা (আর্থিক ক্ষতি, গ্রাহকের অবিশ্বাস) — তাই এখানে স্ট্রং কনসিস্টেন্সি (CP) প্রয়োজন, এমনকি সাময়িক ধীরগতির বিনিময়েও। একই সিস্টেমে ভিন্ন ডেটার জন্য ভিন্ন কনসিস্টেন্সি মডেল বেছে নেওয়া সম্পূর্ণ স্বাভাবিক ও প্রায়োগিক।

প্র ০৩ PACELC কীভাবে দেখায় যে স্ট্রং কনসিস্টেন্সি বেছে নেওয়ার একটি খরচ আছে, এমনকি নেটওয়ার্ক পার্টিশন না থাকলেও?

স্ট্রং কনসিস্টেন্সি নিশ্চিত করতে একটি রাইট অপারেশনকে অবশ্যই একাধিক রেপ্লিকার (বা কোরাম, L31) কাছ থেকে নিশ্চিতকরণ পেতে হবে, তারপরই ক্লায়েন্টকে রেসপন্স দেওয়া যায় — এই অতিরিক্ত নেটওয়ার্ক রাউন্ড ট্রিপগুলোই বাড়তি লেটেন্সি যোগ করে, পার্টিশন থাকুক বা না থাকুক। বিপরীতে, ইভেনচুয়াল কনসিস্টেন্সিতে একটি রেপ্লিকা আপডেট করেই রেসপন্স দেওয়া যায় (দ্রুত), বাকি রেপ্লিকা ব্যাকগ্রাউন্ডে সিঙ্ক হয়। তাই "স্বাভাবিক" (Else) সময়েও Latency বনাম Consistency-এর একই মৌলিক ট্রেড-অফ থেকেই যায়।

অনুশীলন

  1. শ্রেণিবদ্ধ করুন: নিচের প্রতিটি সিস্টেমকে CP নাকি AP হওয়া উচিত তা যুক্তিসহ লিখুন — (ক) একটি ব্যাংক ট্রান্সফার সিস্টেম, (খ) একটি লাইভ স্পোর্টস স্কোর আপডেট সার্ভিস, (গ) একটি DNS রেজলিউশন সিস্টেম।

    (ক) ব্যাংক ট্রান্সফার — CP। ভুল ব্যালেন্স বা ডুপ্লিকেট ট্রান্সফার গ্রহণযোগ্য নয়; পার্টিশনের সময় বরং রিকোয়েস্ট প্রত্যাখ্যান করা ভালো। (খ) লাইভ স্কোর — AP। কয়েক সেকেন্ড পুরনো স্কোর দেখানো সমস্যা নয়, কিন্তু সার্ভিস ডাউন হয়ে যাওয়া (কোনো স্কোরই না দেখানো) খারাপ অভিজ্ঞতা। (গ) DNS — AP। DNS রেকর্ড আপডেট প্রোপাগেট হতে সময় লাগা (TTL, L05) স্বাভাবিকভাবে মেনে নেওয়া হয়, কিন্তু DNS রেজলিউশন ব্যর্থ হওয়া মানে পুরো ইন্টারনেট অ্যাক্সেস ভেঙে পড়া — তাই availability অগ্রাধিকার পায়।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে একটি তৃতীয় ফাংশন read(replica, key) লিখুন যা একটি নির্দিষ্ট রেপ্লিকা থেকে মান পড়ে। তারপর write_eventual() কলের ঠিক পরে (sync-এর আগে) রেপ্লিকা ১ ও রেপ্লিকা ২ দুটো থেকেই পড়ে দেখান তারা ভিন্ন মান দেখাচ্ছে কি না।

    def read(replica, key): return replica.get(key) — write_eventual("balance", 900) চালানোর পর read(replica1, "balance") রিটার্ন করবে ৯০০ (নতুন মান, অবিলম্বে আপডেট হয়েছে), কিন্তু read(replica2, "balance") রিটার্ন করবে ৫০০ (পুরনো মান, sync() এখনো চলেনি) — এটাই ইভেনচুয়াল কনসিস্টেন্সির "সাময়িক অসামঞ্জস্যতা জানালা" (inconsistency window), যা sync() চালানোর পর বন্ধ হয়ে যায়।

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

আগের পাঠ
Latency ও Throughput — পারফরম্যান্স মেট্রিক্স