পাঠ ১৪ · ৫১-এর মধ্যে · মডিউল ৪
Home / Courses / System Design / SQL বনাম NoSQL

SQL বনাম NoSQL — কখন কোনটা বেছে নেবেন

SQL vs NoSQL — when to choose which
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • SQL (রিলেশনাল) ডেটাবেসের মূল বৈশিষ্ট্য — ফিক্সড স্কিমা, ACID, জয়েন
  • NoSQL-এর চারটি ক্যাটাগরি এবং প্রতিটির আদর্শ ইউজ-কেস
  • SQL বনাম NoSQL ট্রেড-অফ কীভাবে CAP থিওরেমের (L04) সাথে যুক্ত
  • Python দিয়ে একই ডেটা নরমালাইজড (SQL-স্টাইল) ও ডিনরমালাইজড (NoSQL-স্টাইল) — দুইভাবে মডেল করা

১ · SQL (রিলেশনাল) ডেটাবেস

SQL / রিলেশনাল ডেটাবেসRelational Databaseফিক্সড, আগে থেকে সংজ্ঞায়িত স্কিমাসহ টেবিলে ডেটা সংরক্ষণকারী ডেটাবেস — টেবিলগুলো ফরেন কী দিয়ে একে অপরের সাথে যুক্ত (relation) থাকে, এবং জয়েন দিয়ে একাধিক টেবিল একসাথে কোয়েরি করা যায়। একটি ফিক্সড স্কিমা মেনে চলে — প্রতিটি টেবিলের কলাম ও টাইপ আগে থেকেই নির্ধারিত থাকে। এটি ACIDACIDAtomicity, Consistency, Isolation, Durability — একটি ট্রানজেকশনের সবগুলো ধাপ সম্পূর্ণ হবে অথবা কিছুই হবে না, এবং ফলাফল সবসময় সঠিক ও স্থায়ী থাকবে তার গ্যারান্টি (DBMS কোর্সে বিস্তারিত)। গ্যারান্টি দেয় — প্রতিটি ট্রানজেকশন হয় সম্পূর্ণভাবে হবে, নাহলে কিছুই হবে না। MySQL, PostgreSQL এই ধরনের ডেটাবেসের উদাহরণ। ব্যাংকিং, ইনভেন্টরি, বা যেকোনো জায়গায় যেখানে ডেটার সঠিকতা ও সম্পর্ক গুরুত্বপূর্ণ, সেখানে SQL সবচেয়ে নির্ভরযোগ্য পছন্দ।

২ · NoSQL-এর চারটি প্রধান ক্যাটাগরি

NoSQLNoSQL"Not Only SQL" — ফিক্সড রিলেশনাল স্কিমা ছাড়া ডেটাবেস, যা সাধারণত হরাইজন্টাল স্কেলেবিলিটি ও স্কিমা ফ্লেক্সিবিলিটির জন্য স্ট্রং কনসিস্টেন্সি/জয়েন সুবিধা কিছুটা ছেড়ে দেয়। একটি একক প্রযুক্তি নয় — বরং চারটি ভিন্ন ধরনের ডেটাবেসের ছাতা-নাম, প্রতিটি ভিন্ন সমস্যার জন্য অপ্টিমাইজড।

Document (MongoDB)
JSON-এর মতো নেস্টেড ডকুমেন্ট সংরক্ষণ করে — যেখানে প্রতিটি রেকর্ডের শেপ ভিন্ন হতে পারে। ইউজার প্রোফাইল, কনটেন্ট ম্যানেজমেন্টের জন্য ভালো।
Key-Value (Redis, DynamoDB)
শুধু কী দিয়ে ভ্যালু লুকআপ — সবচেয়ে সরল ও দ্রুততম। সেশন স্টোর (L12), ক্যাশ (L19)-এর জন্য আদর্শ।
Column-family (Cassandra, HBase)
বিশাল পরিমাণ রাইট-হেভি, টাইম-সিরিজ-স্টাইল ডেটার জন্য অপ্টিমাইজড — একাধিক ডেটা সেন্টার জুড়ে হাই এভেইলেবিলিটি।
Graph (Neo4j)
নোড ও সম্পর্ক (edges) নিজেই প্রথম-শ্রেণির ডেটা — সোশ্যাল নেটওয়ার্ক, রেকমেন্ডেশন ইঞ্জিনের জন্য আদর্শ।

৩ · মূল ট্রেড-অফ — নরমালাইজেশন বনাম ডিনরমালাইজেশন

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

SQL — নরমালাইজড users টেবিল orders টেবিল JOIN via user_id NoSQL — ডিনরমালাইজড user ডকুমেন্ট { name, orders: [ ... ] } সব একই ডকুমেন্টে embedded — জয়েন লাগে না
SQL সম্পর্ক আলাদা টেবিলে রেখে জয়েন করে; NoSQL (document) প্রায়ই সম্পর্কিত ডেটা একই ডকুমেন্টে embed করে ফেলে।
CAP থিওরেমের সাথে সংযোগ (L04)

এই ট্রেড-অফ কাকতালীয় নয়। বেশিরভাগ SQL ডেটাবেস ঐতিহ্যগতভাবে CP (কনসিস্টেন্সি অগ্রাধিকার) দিকে ঝোঁকে, আর অনেক NoSQL ডেটাবেস (Cassandra, DynamoDB) AP (এভেইলেবিলিটি অগ্রাধিকার) দিকে ঝোঁকে — যদিও আধুনিক সিস্টেমে অনেক ডেটাবেসই টিউনযোগ্য (tunable) কনসিস্টেন্সি অফার করে।

৪ · কোড দিয়ে দুটো মডেলিং স্টাইলের তুলনা

নিচে একই ডেটা (একজন ইউজার ও তার অর্ডারসমূহ) দুইভাবে মডেল করা হয়েছে — প্রথমে SQL-স্টাইলে (আলাদা লিস্ট, লুপ দিয়ে জয়েন), তারপর NoSQL-স্টাইলে (অর্ডারগুলো সরাসরি ইউজার ডকুমেন্টের ভেতরে embedded)।

Python
# --- SQL-স্টাইল: নরমালাইজড, আলাদা টেবিল, user_id দিয়ে যুক্ত ---
users_table = [
    {"user_id": 1, "name": "Rahim"},
    {"user_id": 2, "name": "Karim"},
]
orders_table = [
    {"order_id": 101, "user_id": 1, "item": "Laptop", "amount": 75000},
    {"order_id": 102, "user_id": 1, "item": "Mouse", "amount": 800},
    {"order_id": 103, "user_id": 2, "item": "Keyboard", "amount": 1500},
]

def sql_style_join(users, orders):
    joined = []
    for user in users:
        user_orders = [o for o in orders if o["user_id"] == user["user_id"]]
        joined.append({
            "user_id": user["user_id"],
            "name": user["name"],
            "orders": user_orders,
        })
    return joined

sql_result = sql_style_join(users_table, orders_table)

print("=== SQL-স্টাইল (লুপ দিয়ে জয়েন করা) ===")
for row in sql_result:
    print(" ", row)

# --- NoSQL-স্টাইল: ডিনরমালাইজড, অর্ডার সরাসরি ইউজার ডকুমেন্টের ভেতরে ---
nosql_documents = [
    {
        "user_id": 1, "name": "Rahim",
        "orders": [
            {"order_id": 101, "item": "Laptop", "amount": 75000},
            {"order_id": 102, "item": "Mouse", "amount": 800},
        ],
    },
    {
        "user_id": 2, "name": "Karim",
        "orders": [
            {"order_id": 103, "item": "Keyboard", "amount": 1500},
        ],
    },
]

print("\n=== NoSQL-স্টাইল (আগে থেকেই embedded, কোনো জয়েন লাগেনি) ===")
for doc in nosql_documents:
    print(" ", doc)

    
উভয় পদ্ধতিই একই চূড়ান্ত ফলাফল দেয় (ইউজার + তার অর্ডারসমূহ), কিন্তু SQL-স্টাইলে ডেটা দুটো আলাদা টেবিলে থাকে (রানটাইমে জয়েন লাগে), আর NoSQL-স্টাইলে ডেটা আগে থেকেই একসাথে থাকে (কোনো জয়েন লজিক লাগে না, কিন্তু ইউজারের নাম একাধিক জায়গায় প্রয়োজন হলে তা ডুপ্লিকেট হতে পারে)।

৫ · কীভাবে সিদ্ধান্ত নেবেন

প্রশ্নটা "SQL ভালো নাকি NoSQL ভালো" নয় — প্রশ্নটা "আমার ডেটার শেপ ও অ্যাক্সেস প্যাটার্ন কী, এবং আমার নন-ফাংশনাল রিকোয়ারমেন্ট (L01) কী দাবি করে"। ট্রানজেকশনাল সঠিকতা ও জটিল সম্পর্ক দরকার হলে SQL। বিশাল স্কেল, নমনীয় স্কিমা, বা একটি নির্দিষ্ট অ্যাক্সেস প্যাটার্নের (কী-ভ্যালু লুকআপ, গ্রাফ ট্রাভার্সাল) জন্য অপ্টিমাইজেশন দরকার হলে সংশ্লিষ্ট NoSQL ক্যাটাগরি। বাস্তব বড় সিস্টেমে প্রায়ই দুটোই একসাথে ব্যবহৃত হয় — এক অংশে SQL, অন্য অংশে NoSQL (polyglot persistence)।

মূল কথা · Key takeaway

SQL ও NoSQL একে অপরের প্রতিস্থাপক নয় — দুটো ভিন্ন টুলসেট, ভিন্ন ট্রেড-অফসহ। ডেটাবেস বাছাই সবসময় ফিরে যায় সেই একই মূল প্রশ্নে যা L01-এ শিখেছিলাম — নন-ফাংশনাল রিকোয়ারমেন্টই আর্কিটেকচার ঠিক করে দেয়।

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

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

প্র ০১ একটি ব্যাংকিং সিস্টেমে কেন প্রায় সবসময় SQL ব্যবহার করা হয়, NoSQL নয়?

ব্যাংকিং লেনদেনে ACID গ্যারান্টি জীবন-মরণ বিষয় — "টাকা পাঠানো" মানে একজনের ব্যালেন্স কমা এবং অন্যজনের ব্যালেন্স বাড়া দুটোই একসাথে হতে হবে, অথবা কিছুই হবে না (atomicity)। বেশিরভাগ NoSQL ডেটাবেস এই ধরনের মাল্টি-রেকর্ড স্ট্রং ট্রানজেকশনাল গ্যারান্টি দেওয়ার জন্য ডিজাইন করা নয় (যদিও কিছু আধুনিক NoSQL এখন সীমিত ট্রানজেকশন সাপোর্ট করে) — তাই ঝুঁকি এড়াতে SQL-ই স্বাভাবিক পছন্দ।

প্র ০২ Document ডেটাবেসে ডেটা ডিনরমালাইজ করলে (যেমন প্রতিটি অর্ডারে ইউজারের নাম কপি করে রাখা) ইউজার তার নাম বদলালে কী সমস্যা হতে পারে?

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

প্র ০৩ একটি সোশ্যাল নেটওয়ার্কে "ইউজার A কি ইউজার B-এর বন্ধুর বন্ধু?" এই ধরনের প্রশ্নের জন্য কোন ধরনের ডেটাবেস সবচেয়ে স্বাভাবিক, এবং কেন?

একটি Graph ডেটাবেস (যেমন Neo4j) — কারণ এখানে সম্পর্ক (এজ) নিজেই প্রথম-শ্রেণির ডেটা। SQL-এ এই ধরনের "বন্ধুর বন্ধু" (multi-hop) কোয়েরির জন্য একাধিক সেলফ-জয়েন লাগবে, যা গভীরতা বাড়ার সাথে সাথে দ্রুত জটিল ও ধীরগতির হয়ে যায়। Graph ডেটাবেস এই ধরনের ট্রাভার্সাল-হেভি কোয়েরির জন্যই বিশেষভাবে অপ্টিমাইজড।

অনুশীলন

  1. চিন্তা করুন: একটি ই-কমার্স সাইটের প্রোডাক্ট ক্যাটালগ (প্রতিটি প্রোডাক্টের ভিন্ন ভিন্ন attribute থাকতে পারে — জামার জন্য সাইজ/রঙ, ল্যাপটপের জন্য RAM/প্রসেসর) সংরক্ষণের জন্য SQL নাকি NoSQL বেশি উপযুক্ত মনে হয়? কেন?

    এখানে Document (NoSQL) ডেটাবেস বেশি স্বাভাবিক ফিট, কারণ প্রতিটি প্রোডাক্ট ক্যাটাগরির জন্য আলাদা attribute সেট থাকে — একটি ফিক্সড SQL স্কিমায় এটি হয় প্রচুর NULL কলাম দিয়ে (জামার রেকর্ডে RAM কলাম খালি) অথবা জটিল EAV (Entity-Attribute-Value) প্যাটার্নে বাস্তবায়ন করতে হবে। Document ডেটাবেসে প্রতিটি প্রোডাক্ট তার নিজস্ব শেপের ডকুমেন্ট হিসেবে স্বাভাবিকভাবেই ফিট করে।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে একটি তৃতীয় ইউজার (user_id 3, নাম "Salma", কোনো অর্ডার নেই) যোগ করুন — উভয় (SQL-স্টাইল ও NoSQL-স্টাইল) মডেলিং ফাংশনে। ফলাফলে Salma-এর orders লিস্ট কেমন দেখাবে বলে মনে হয়?

    দুই ক্ষেত্রেই Salma-এর orders একটি খালি লিস্ট [] হবে — SQL-স্টাইলে কারণ orders_table-এ তার user_id মিলে এমন কোনো এন্ট্রি নেই (list comprehension খালি লিস্ট ফেরত দেয়), আর NoSQL-স্টাইলে কারণ তার ডকুমেন্টে "orders": [] সরাসরি এভাবেই লেখা হবে।

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

আগের পাঠ
কনসিস্টেন্ট হ্যাশিং