DDIA: Chapter-2
Data Models and Query Languages

আমার ভাষার সীমাই আমার জগতের সীমা নির্ধারণ করে। - লুডভিগ উইটগেনস্টাইন
সফটওয়্যার তৈরির ক্ষেত্রে ডাটা মডেল সবচেয়ে গুরুত্বপূর্ণ অংশ। এটি কেবল সফটওয়্যার লেখার পদ্ধতিই নির্ধারণ করে না, বরং আমরা একটি সমস্যাকে কীভাবে চিন্তা করছি বা সমাধান করছি, তার ওপরও গভীর প্রভাব ফেলে।
অধিকাংশ অ্যাপ্লিকেশন একাধিক স্তরের ডাটা মডেলের ওপর ভিত্তি করে তৈরি হয়। প্রতিটি স্তরের মূল কাজ হলো তার নিচের স্তরের জটিলতাকে লুকিয়ে রেখে একটি সহজ ইন্টারফেস প্রদান করা। এই প্রক্রিয়াটি নিচের ৪টি ধাপে ঘটে:
অ্যাপ্লিকেশন স্তর (Application Layer): ডেভেলপাররা বাস্তব জগতের বিষয়গুলোকে (মানুষ, টাকা, পণ্য) অবজেক্ট বা ডাটা স্ট্রাকচারে রূপান্তর করেন।
সাধারণ ডাটা মডেল (General Data Model): এই স্ট্রাকচারগুলোকে স্টোর করার জন্য JSON, XML বা রিলেশনাল টেবিলের মতো মডেলে প্রকাশ করা হয়।
স্টোরেজ ইঞ্জিন (Storage Engine): ডাটাবেস সফটওয়্যার এই ডাটাগুলোকে ডিস্ক বা মেমরিতে বাইট (Bytes) হিসেবে কীভাবে সাজাবে, তা নির্ধারণ করে।
হার্ডওয়্যার স্তর (Hardware Layer): ইলেকট্রিক্যাল সিগন্যাল বা ম্যাগনেটিক ফিল্ডের মাধ্যমে সেই বাইটগুলো ফিজিক্যাল হার্ডওয়্যারে সংরক্ষিত হয়।
অ্যাবস্ট্রাকশন (Abstraction): প্রতিটি স্তর তার নিচের স্তরের জটিলতা গোপন রাখে, ফলে ডাটাবেস ইঞ্জিনিয়ার এবং অ্যাপ্লিকেশন ডেভেলপাররা একে অপরের অভ্যন্তরীণ কাজ না জেনেই একসাথে কাজ করতে পারেন।
সঠিক মডেল নির্বাচন: সব মডেল সব কাজের জন্য উপযুক্ত নয়। কিছু কাজ নির্দিষ্ট মডেলে দ্রুত হয়, আবার কিছু কাজ খুব ধীরগতিতে বা জটিলভাবে সম্পন্ন হয়। তাই অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী সঠিক মডেল (যেমন: Relational, Document, বা Graph) বেছে নেওয়া অত্যন্ত জরুরি।
Relational Model Versus Document Model
আমরা প্রথমেই দেখে নিব, আজকের পৃথিবীর সর্বাধিক পরিচিত 'SQL' সম্পর্কে ( রিলেশনাল মডেলের উপর ভিত্তি করে প্রতিষ্ঠিত ), এই টেক্সটটিতে রিলেশনাল ডাটাবেসের উত্থান এবং এর দীর্ঘস্থায়ী জনপ্রিয়তার কারণ ব্যাখ্যা করা হয়েছে। মূল পয়েন্টগুলো হলো:
উৎপত্তি ও কাঠামো: ১৯৭০ সালে এডগার কড (Edgar Codd) প্রথম এই মডেলের প্রস্তাব করেন। এখানে ডাটা মূলত Relations (টেবিল) এবং Tuples (রো) হিসেবে সাজানো থাকে।
সাফল্যের ইতিহাস: শুরুতে এর কার্যকারিতা নিয়ে সন্দেহ থাকলেও, ১৯৮০-র দশকের মাঝামাঝি থেকে এটি ডাটা সংরক্ষণের প্রধান মাধ্যম হয়ে ওঠে। প্রায় ২৫-৩০ বছর ধরে এটি প্রযুক্ত বিশ্বে আধিপত্য বিস্তার করে আছে।
প্রাথমিক ব্যবহার: মূলত ব্যাংকিং লেনদেন, এয়ারলাইন রিজার্ভেশন এবং ইনভেন্টরি ম্যানেজমেন্টের মতো ব্যবসায়িক কাজে এটি ব্যবহৃত হতো।
প্রধান উদ্দেশ্য: আগের ডাটাবেসগুলোতে ডেভেলপারদের ডাটার অভ্যন্তরীণ গঠন নিয়ে অনেক মাথা ঘামাতে হতো। রিলেশনাল মডেলের লক্ষ্য ছিল সেই জটিলতা লুকিয়ে ব্যবহারকারীকে একটি পরিচ্ছন্ন ইন্টারফেস (SQL) প্রদান করা।
প্রতিযোগিতা: সময়ের সাথে সাথে নেটওয়ার্ক মডেল, হায়ারার্কিকাল মডেল, অবজেক্ট ডাটাবেস এবং XML ডাটাবেসের মতো অনেক বিকল্প এলেও রিলেশনাল মডেলের জনপ্রিয়তাকে কেউ ছাড়িয়ে যেতে পারেনি।
বর্তমান অবস্থা: বর্তমানের আধুনিক ওয়েব অ্যাপ্লিকেশন (সোশ্যাল মিডিয়া, ই-কমার্স, গেমস) রিলেশনাল ডাটাবেসের মাধ্যমেই পরিচালিত হচ্ছে, যা এর বহুমুখী কার্যকারিতার প্রমাণ দেয়।
The Birth of NoSQL
"NoSQL" মূলত কোনো নির্দিষ্ট Technology নয়, বরং ২০০৯ সালে একটি মিটআপের জন্য ব্যবহৃত একটি টুইটার হ্যাশট্যাগ ছিল। পরবর্তীতে একে "Not Only SQL" হিসেবে ব্যাখ্যা করা হয়েছে। মূলত ওয়েব স্টার্টআপ কমিউনিটির হাত ধরে এটি দ্রুত ছড়িয়ে পড়ে। রিলেশনাল ডাটাবেসের কিছু সীমাবদ্ধতা দূর করতেই এর জন্ম। NoSQL ব্যবহারের প্রধান কারণসমূহ:
Scalability (স্কেলেবিলিটি): বিশাল পরিমাণ ডাটা হ্যান্ডেল করা এবং খুব দ্রুত ডাটা রাইট (Write throughput) করার সুবিধা, যা প্রথাগত রিলেশনাল ডাটাবেসে কঠিন ছিল।
ওপেন সোর্স সফটওয়্যার: কমার্শিয়াল ডাটাবেসের তুলনায় ফ্রি এবং ওপেন সোর্স সফটওয়্যারের প্রতি মানুষের আগ্রহ।
বিশেষায়িত কুয়েরি: এমন কিছু জটিল অপারেশন যা রিলেশনাল মডেল ঠিকঠাক সাপোর্ট করে না।
ডাইনামিক স্কিমা: রিলেশনাল ডাটাবেসের কঠোর নিয়ম বা স্ট্রাকচারের (Schema) বদলে আরও নমনীয় ও ডাইনামিক ডাটা মডেলের চাহিদা।
Polyglot Persistence: ভবিষ্যতে রিলেশনাল ডাটাবেস সম্পূর্ণ হারিয়ে যাবে না। বরং প্রয়োজন অনুযায়ী রিলেশনাল এবং নন-রিলেশনাল (NoSQL) ডাটাবেস একসাথে ব্যবহার করা হবে—এই ধারণাটিকেই বলা হয় Polyglot Persistence।
The Object-Relational Mismatch
ডাটা মডেলিংয়ের এই অংশটি মূলত "কিভাবে ডাটা রাখা হচ্ছে" বনাম "অ্যাপ্লিকেশন সেই ডাটাকে কিভাবে দেখছে"—এই দুইয়ের conflict নিয়ে।
ইমপেডেন্স মিসম্যাচ (The Impedance Mismatch)
বর্তমান সময়ে আমরা জাভা (Java), পাইথন (Python) বা সি# (C#)-এর মতো অবজেক্ট-ওরিয়েন্টেড ল্যাঙ্গুয়েজ ব্যবহার করি। এই ল্যাঙ্গুয়েজগুলোতে ডাটা থাকে Objects হিসেবে। কিন্তু রিলেশনাল ডাটাবেস (SQL) ডাটাকে রাখে Tables (Rows/Columns) হিসেবে।
এই দুই ফরম্যাটের মধ্যে মিল না থাকাকেই বলা হয় Impedance Mismatch।
- সমস্যা: অবজেক্টের ডাটাকে টেবিলে ঢোকাতে বা টেবিল থেকে অবজেক্টে রূপান্তর করতে একটি বাড়তি 'Translation Layer' (যেমন: Hibernate বা ActiveRecord) লাগে। এটি কোডকে জটিল করে তোলে।
রিলেশনাল মডেলের দৃষ্টিভঙ্গি (Relational Approach)
একটি Résumé বা প্রোফাইলের কথা চিন্তা করুন। এখানে ইউজারের নাম একবার থাকলেও তার শিক্ষা বা কাজের অভিজ্ঞতা একাধিক হতে পারে। রিলেশনাল মডেলে এটি One-to-Many রিলেশনশিপ।
নর্মালাইজেশন: রিলেশনাল মডেলে ডাটাকে ডুপ্লিকেট করা হয় না। তাই ইউজারের মূল তথ্য একটি টেবিলে, চাকরিগুলো আরেকটি টেবিলে এবং শিক্ষা অন্য একটি টেবিলে রাখা হয়।
সমস্যা: আপনি যখন পুরো প্রোফাইলটি দেখতে চাইবেন, তখন ডাটাবেসকে অনেকগুলো টেবিল Join করতে হয়, যা বড় ডাটার ক্ষেত্রে পারফরম্যান্সে প্রভাব ফেলে।

ডকুমেন্ট মডেলের দৃষ্টিভঙ্গি (Document/JSON Approach)
ডকুমেন্ট মডেলে (যেমন: MongoDB) একটি প্রোফাইলকে একটি সম্পূর্ণ Tree Structure বা একটি সিঙ্গেল JSON অবজেক্ট হিসেবে দেখা হয়। এই যেমন MongoDB তে যদি resume এর ডাটা রাখি তাহলে এভাবে সেইভ রাখা যায় ।
{
"user_id": 251,
"first_name": "Bill",
"last_name": "Gates",
"summary": "Co-chair of the Bill & Melinda Gates... Active blogger.",
"region_id": "us:91",
"industry_id": 131,
"photo_url": "/p/7/000/253/05b/308dd6e.jpg",
"positions": [
{
"job_title": "Co-chair",
"organization": "Bill & Melinda Gates Foundation"
},
{
"job_title": "Co-founder, Chairman",
"organization": "Microsoft"
}
],
"education": [
{
"school_name": "Harvard University",
"start": 1973,
"end": 1975
},
{
"school_name": "Lakeside School, Seattle",
"start": null,
"end": null
}
],
"contact_info": {
"blog": "http://thegatesnotes.com",
"twitter": "http://twitter.com/BillGates"
}
}
এখানে থেকে আমরা কি সুবিধা পেতে পারি:
Locality (লোকালিটি): প্রোফাইলের সব তথ্য (চাকরি, শিক্ষা, কন্টাক্ট) একটি ফাইলের মধ্যেই থাকে। ফলে ডাটাবেস থেকে মাত্র একটি কুয়েরি করলেই পুরো প্রোফাইলটি চলে আসে। আলাদা করে টেবিল জয়েন করার প্রয়োজন হয় না।
অ্যাপ্লিকেশনের সাথে মিল: অ্যাপ্লিকেশন কোডে যে ধরনের অবজেক্ট বা JSON ব্যবহৃত হয়, ডাটাবেসেও ঠিক সেই ফরম্যাটেই ডাটা জমা থাকে। ফলে ইমপেডেন্স মিসম্যাচ কমে যায়।

Many-to-One and Many-to-Many Relationships
আগের সেকশনের Figure 2-1 এ, region_id এবং industry_id ডাটাগুলো ID হিসেবে দেওয়া হয়েছে। কিন্তু আমরা চাইলেই তো প্লেইন টেক্সট (স্ট্রিং) হিসেবে লিখতে পারতাম , যেমনঃ "Greater Seattle Area" এবং "Philanthropy " । কিন্তু কেন করলাম না , কেন ID ব্যাবহার করলাম ?
সরাসরি টেক্সট ব্যবহারের বদলে একটি নির্দিষ্ট তালিকার মাধ্যমে ID ব্যবহার করার কয়েকটি প্রধান সুবিধা রয়েছে:
Consistency (সামঞ্জস্যতা): ড্রপ-ডাউন লিস্ট বা অটো-কমপ্লিট ব্যবহার করলে বানান ভুল হওয়ার সম্ভাবনা থাকে না। সবার প্রোফাইলে একই স্টাইল বজায় থাকে।
Ambiguity (অস্পষ্টতা দূর করা): একই নামের একাধিক শহর থাকতে পারে। ID ব্যবহার করলে নির্দিষ্ট করে বোঝানো যায় কোন শহরের কথা বলা হচ্ছে।
Ease of Updating (সহজে আপডেট করা): যদি কোনো শহরের নাম পরিবর্তন হয় (যেমন: রাজনৈতিক কারণে), তবে আপনাকে হাজার হাজার ইউজারের প্রোফাইল আপডেট করতে হবে না। শুধু মূল টেবিলে একবার নাম পরিবর্তন করলেই সব জায়গায় আপডেট হয়ে যাবে।
Localization (ভাষান্তর): ওয়েবসাইটটি অন্য ভাষায় অনুবাদ করলে শুধু ID-র বিপরীতে থাকা টেক্সটগুলো অনুবাদ করলেই চলে।
Better Search (উন্নত সার্চ): ID-র মাধ্যমে আমরা ডেটার মধ্যে সম্পর্ক স্থাপন করতে পারি। যেমন: Seattle যে Washington স্টেটের ভেতর, তা ID-র মাধ্যমে যুক্ত থাকলে সহজে খুঁজে পাওয়া সম্ভব।
ID বনাম টেক্সট: ডুপ্লিকেশন ও নরমালাইজেশন
ID: মানুষের কাছে এর কোনো অর্থ নেই, তাই এটি পরিবর্তনের প্রয়োজন হয় না। এটি ডেটাবেজের ভেতরে একটি রেফারেন্স হিসেবে কাজ করে।
Text: মানুষের কাছে অর্থপূর্ণ তথ্য যেকোনো সময় পরিবর্তন হতে পারে। যদি এই টেক্সট সরাসরি প্রতিটা রেকর্ডে সেভ করা হয়, তবে একে Duplication বলা হয়।
যদি আপনি একই ভ্যালু বারবার বিভিন্ন জায়গায় স্টোর করেন, তবে বুঝতে হবে আপনার ডেটাবেজ Normalized নয়। ডুপ্লিকেশন কমানোই হলো নরমালাইজেশনের মূল লক্ষ্য।
Relational বনাম Document মডেলের পার্থক্য
Document database-এ many-to-one সম্পর্ক (যেমন অনেক মানুষ এক region বা industry-এর সাথে যুক্ত) ঠিকভাবে normalize করা কঠিন, কারণ এখানে relational database-এর মতো সহজ join সাপোর্ট নেই। Relational database-এ টেবিলগুলো ID দিয়ে join করা যায়, কিন্তু document database-এ join না থাকলে অ্যাপ্লিকেশন কোড দিয়ে আলাদা আলাদা query চালিয়ে সেই কাজ করতে হয়, ফলে database-এর দায়িত্ব অনেকটা application-এর উপর চলে যায়। শুরুতে কোনো অ্যাপ join ছাড়াই ভালোভাবে কাজ করতে পারে, কিন্তু সময়ের সাথে নতুন feature যোগ হলে ডেটা বেশি interconnected হয়ে পড়ে। যেমন:
Organizations and schools as entities
আগে résumé-এ organization আর school_name শুধু লেখা (string) হিসেবে রাখা ছিল। কিন্তু এগুলোকে আলাদা entity হিসেবে reference করলে ভালো হয়। তাহলে প্রতিটি company বা school-এর নিজস্ব web page থাকতে পারে (লোগো, নিউজ, তথ্য ইত্যাদি সহ), আর résumé সেগুলোর সাথে লিংক করে অতিরিক্ত তথ্য ও লোগো দেখাতে পারে।
Recommendations
একজন user অন্য user-কে recommendation দিতে পারে, যা প্রাপকের résumé-তে দেখানো হয় লেখকের নাম ও ছবিসহ। লেখক যদি পরে তার ছবি পরিবর্তন করে, তাহলে তার দেওয়া সব recommendation-এ নতুন ছবিটি দেখাতে হবে। এজন্য recommendation-এর মধ্যে লেখকের profile-এর reference রাখা প্রয়োজন।

Figure 2-4 দেখায় কীভাবে এই নতুন বৈশিষ্ট্যগুলো many-to-many Relationships এর প্রয়োজন সৃষ্টি করে। প্রতিটি ডটেড আয়তক্ষেত্রের ভেতরের ডেটা একত্র করে একটি ডকুমেন্টে রাখা যেতে পারে, তবে organization, schools এবং অন্যান্য users এর যে রেফারেন্সগুলো রয়েছে, সেগুলোকে আলাদা রেফারেন্স হিসেবে উপস্থাপন করতে হবে এবং কুয়েরি করার সময় সেগুলোর জন্য join প্রয়োজন হবে।

Are Document Databases Repeating History?
এই অংশ থেকে আমরা বুঝার চেষ্টা করবো অপেক্ষাকৃত নতুন আবিষ্কৃত Document Database কি ইতিহাসের পুনরাবৃত্তি করছে ? বর্তমানে আমরা NoSQL বা Document Database-এ যে ধরণের সমস্যার সম্মুখীন হচ্ছি, তা নতুন কিছু নয়। সত্তরের দশকে ডাটাবেজ জগতের এই বিবর্তন মূলত তিনটি প্রধান মডেলকে কেন্দ্র করে আবর্তিত হয়েছে:
১. Hierarchical Model
১৯৭০-এর দশকে আইবিএম (IBM)-এর IMS (Information Management System) ছিল সবচেয়ে জনপ্রিয়।
এটি ডেটাকে একটি 'Tree' বা গাছের মতো করে সাজাত, যেখানে একটি রেকর্ডের ভেতরে অন্য রেকর্ডগুলো নেস্টেড (Nested) থাকতো। বর্তমানের JSON ভিত্তিক ডকুমেন্ট ডাটাবেজগুলো অনেকটা এই মডেলের মতোই।তবে, এটি One-to-many সম্পর্কের জন্য ভালো হলেও Many-to-many সম্পর্কের ক্ষেত্রে খুব দুর্বল ছিল। এতে 'Join' সাপোর্ট করতো না, যার ফলে ডেটা ডুপ্লিকেট করতে হতো।
২. Network Model (CODASYL Model)
Hierarchical মডেলের সীমাবদ্ধতা দূর করতে Network Model বা CODASYL মডেল তৈরি হয়।
এখানে একটি রেকর্ডের একাধিক 'Parent' থাকতে পারতো (Many-to-many)। এই মডেলে ডেটা খোঁজার উপায় ছিল অনেকটা প্রোগ্রামিং ল্যাঙ্গুয়েজের Pointer-এর মতো। আপনাকে একটি 'Root' রেকর্ড থেকে শুরু করে লিঙ্কের চেইন ধরে ধরে গন্তব্যে পৌঁছাতে হতো। এটি ছিল অত্যন্ত জটিল। যদি ডেটার গঠন পরিবর্তন করা হতো, তবে কোড লেভেলে ম্যানুয়ালি সব 'Access Path' পরিবর্তন করতে হতো। এটি অনেকটা গোলকধাঁধায় পথ খোঁজার মতো ছিল।
৩. Relational Model
এডগার কড (Edgar Codd) এই মডেলটি প্রস্তাব করেন, যা পরবর্তীতে SQL হিসেবে বিশ্বজয় করে।
এখানে ডেটা থাকে সাধারণ Table (Relation) এবং Row (Tuple) আকারে। কোনো জটিল নেস্টেড স্ট্রাকচার বা পয়েন্টার নেই।
- Query Optimizer: এটিই সবচেয়ে বড় পার্থক্য। রিলেশনাল মডেলে প্রোগ্রামারকে বলে দিতে হয় না যে ডেটা কোন পথে (Path) গিয়ে খুঁজবে। ডেভেলপার শুধু বলে দেয় তার "কী" চাই, আর ডাটাবেজের Query Optimizer স্বয়ংক্রিয়ভাবে ঠিক করে নেয় "কিভাবে" দ্রুততম উপায়ে ডেটা আনা যাবে।
এখানে এসে একটি কথা বলা যেতে পারে - ডকুমেন্ট ডাটাবেজগুলো একটি ক্ষেত্রে ইতিহাসের পুনরাবৃত্তি করছে:
Hierarchical Repeats: তারা আবারও নেস্টেড রেকর্ড স্টোর করছে (One-to-many সম্পর্কের জন্য)।
পার্থক্য: তবে অনেক-এর সাথে অনেক (Many-to-many) সম্পর্কের ক্ষেত্রে ডকুমেন্ট ডাটাবেজগুলো কিন্তু নেটওয়ার্ক মডেলের (CODASYL) কঠিন পথে হাঁটেনি। তারা রিলেশনাল মডেলের মতোই Unique Identifier (যাকে রিলেশনাল মডেলে Foreign Key এবং ডকুমেন্টে Document Reference বলা হয়) ব্যবহার করছে, যা পড়ার সময় (Read time) সমাধান করা হয়।
Relational Versus Document Databases Today
Which data model leads to simpler application code?
কোনো নির্দিষ্ট ডাটা মডেলকে এককভাবে সেরা বলা যায় না; এটি নির্ভর করে আপনার অ্যাপ্লিকেশনের ডাটার ধরনের ওপর।
ডকুমেন্ট মডেলের সুবিধা: এখানে স্কিমা ফ্লেক্সিবিলিটি থাকে, লোকালিটির (Locality) কারণে পারফরম্যান্স ভালো হয় এবং অনেক ক্ষেত্রে এটি অ্যাপ্লিকেশনের অবজেক্ট স্ট্রাকচারের সাথে সরাসরি মিলে যায়।
রিলেশনাল মডেলের সুবিধা: এটি জয়েন (Join), এবং many-to-one বা many-to-many রিলেশনশিপ হ্যান্ডেল করার জন্য অনেক বেশি শক্তিশালী।
যদি আপনার ডাটা একটি "ট্রি" (Tree) স্ট্রাকচারের মতো হয় (যেমন: একজন ইউজারের প্রোফাইল যেখানে শিক্ষা, অভিজ্ঞতা এবং কন্টাক্ট ইনফো একসাথে থাকে), তাহলে ডকুমেন্ট মডেল ব্যবহার করলে অ্যাপ্লিকেশন কোড সহজ হয়। রিলেশনাল মডেলে এই ডাটাগুলোকে আলাদা আলাদা টেবিলে ভাগ করাকে "Shredding" বলা হয়, যা কোডকে জটিল করে তুলতে পারে।
Schema flexibility in the document model
ডকুমেন্ট ডাটাবেসকে প্রায়ই "Schemaless" বলা হয়, কিন্তু লেখক এটিকে ভুল মনে করেন। তিনি দুটি নতুন টার্ম ব্যবহার করেছেন:
Schema-on-write (Relational): ডাটাবেসে ডাটা সেভ করার আগেই নির্দিষ্ট স্ট্রাকচার বা স্কিমা মেনে চলতে হয়। এটি অনেকটা প্রোগ্রামিং ল্যাঙ্গুয়েজের Static Type Checking এর মতো।
Schema-on-read (Document): ডাটাবেস নিজে কোনো স্কিমা জোরপূর্বক চাপিয়ে দেয় না; যখন ডাটা পড়া হয়, তখন অ্যাপ্লিকেশন কোড ধরে নেয় ডাটাটি একটি নির্দিষ্ট ফরম্যাটে আছে। এটি Dynamic Type Checking এর মতো।
কোড উদাহরণ (Schema Evolution): যদি ইউজারের নামকে name থেকে first_name এ রূপান্তর করতে হয়, ডকুমেন্ট ডাটাবেসে (Application Level):
if (user && user.name && !user.first_name) {
// পুরনো ডাটা হ্যান্ডেল করার জন্য কোড
user.first_name = user.name.split(" ")[0];
}
রিলেশনাল ডাটাবেসে (Database Level):
ALTER TABLE users ADD COLUMN first_name text;
-- PostgreSQL
UPDATE users SET first_name = split_part(name, ' ', 1);
-- MySQL
UPDATE users SET first_name = substring_index(name, ' ', 1);
Data locality for queries
ডকুমেন্ট মডেলের একটি বড় শক্তি হলো Locality। একটি ডকুমেন্ট সাধারণত JSON বা BSON হিসেবে একটি নিরবচ্ছিন্ন স্ট্রিং আকারে জমা থাকে।
যদি আপনার একসাথে পুরো ডকুমেন্টের তথ্য লাগে, তবে এটি রিলেশনাল ডাটাবেসের চেয়ে দ্রুত কাজ করবে কারণ রিলেশনাল ডাটাবেসে অনেকগুলো টেবিল থেকে তথ্য খুঁজতে একাধিক ইনডেক্স লুকআপ এবং ডিস্ক সিকের প্রয়োজন হয়।
সীমাবদ্ধতা: যদি ডকুমেন্ট অনেক বড় হয়, তবে ছোট একটি অংশ আপডেট করার জন্য পুরো ডকুমেন্টটিই পুনরায় লিখতে হয়, যা পারফরম্যান্স কমিয়ে দিতে পারে।
Convergence of document and relational databases
বর্তমানে রিলেশনাল এবং ডকুমেন্ট ডাটাবেসের মধ্যে পার্থক্য কমে আসছে:
অধিকাংশ রিলেশনাল ডাটাবেস (PostgreSQL, MySQL, DB2) এখন JSON/XML সাপোর্ট করে।
অন্যদিকে, অনেক ডকুমেন্ট ডাটাবেস (যেমন: RethinkDB) এখন জয়েন (Join) সাপোর্ট করা শুরু করেছে।
ভবিষ্যতে ডাটাবেসগুলো হাইব্রিড মডেলে চলে যাবে, যেখানে আপনি একইসাথে ডকুমেন্ট-লাইক ফ্লেক্সিবিলিটি এবং রিলেশনাল কুয়েরি করার সুবিধা পাবেন।
Query Languages for Data
Declarative vs. Imperative Query Languages
এই অংশে আমরা ডেটাবেস কুয়েরি করার দুটি ভিন্ন পদ্ধতি—Declarative (ডিক্লারেটিভ) এবং Imperative (ইম্পারেটিভ) এর মধ্যে পার্থক্য এবং গুরুত্ব ব্যাখ্যা করতে পারবো।
Imperative Language: এখানে কম্পিউটারকে ধাপে ধাপে বলে দিতে হয় সে কীভাবে কাজটি করবে। আপনাকে কোডের প্রতিটি লাইন বা স্টেপ (যেমন লুপ চালানো, কন্ডিশন চেক করা) নির্দিষ্ট করে দিতে হয়।
Declarative Language: এখানে আপনি শুধু বলেন আপনার কী প্রয়োজন বা ডেটা কোন প্যাটার্নে থাকবে। কিন্তু সেই ডেটা কীভাবে খুঁজে বের করতে হবে, তা ডেটাবেস ইঞ্জিনের ওপর ছেড়ে দেওয়া হয়।
একটি 'Sharks' পরিবারের প্রাণীদের তালিকা খুঁজে বের করার উদাহরণ দেওয়া যায়:
Imperative উদাহরণ (যেমন JavaScript): এখানে একটি নির্দিষ্ট অর্ডারে লুপ চালিয়ে ডেটা ফিল্টার করা হচ্ছে:
function getSharks() {
var sharks = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].family === "Sharks") {
sharks.push(animals[i]);
}
}
return sharks;
}
Declarative (SQL) উদাহরণ: SQL লিখা হয়েছে **Relational Algebra -**এর উপর ভিত্তি করে , যদি উপরের উদাহরণটি **relational algebra -**এর ফরম্যাট এ বানাই তাহলে এমনভাবে লিখা যায় ।
sharks = σfamily = “Sharks” (animals)
সিগমা (σ) অপারেটর ব্যবহার করে কন্ডিশন দেওয়া হচ্ছে , এখানে শুধু বলে দেওয়া হচ্ছে কোন টেবিল থেকে কী শর্তে ডেটা লাগবে:
SELECT * FROM animals WHERE family = 'Sharks';
কেন Declarative ল্যাঙ্গুয়েজ (SQL) বেশি শক্তিশালী?
বইটিতে ডিক্লারেটিভ ল্যাঙ্গুয়েজের ৩টি বড় সুবিধার কথা বলা হয়েছে:
১. সহজ এবং সংক্ষিপ্ত (Concise): ইম্পারেটিভ কোডের তুলনায় SQL অনেক ছোট এবং পড়া সহজ। এটি ডেটাবেস ইঞ্জিনের ভেতরের জটিলতা ব্যবহারকারীর কাছ থেকে লুকিয়ে রাখে (Abstraction)।
২. অটোমেটিক অপ্টিমাইজেশন (Automatic Optimization): যেহেতু আপনি শুধু 'কী চাই' বলছেন, তাই ডেটাবেস ইঞ্জিন তার Query Optimizer ব্যবহার করে সিদ্ধান্ত নিতে পারে কোন ইন্ডেক্স (Index) ব্যবহার করলে বা কোন পদ্ধতিতে জয়েন (Join) করলে দ্রুত ফলাফল আসবে। আপনি যদি ইম্পারেটিভ কোড লিখতেন, তবে ডেটাবেস কখনোই নিজের মতো করে পারফরম্যান্স উন্নত করতে পারতো না, কারণ আপনার কোড হয়তো নির্দিষ্ট কোনো অর্ডারের ওপর নির্ভর করে থাকতো।
৩. প্যারালাল এক্সিকিউশন (Parallel Execution): বর্তমানে প্রসেসরগুলোতে অনেক কোর (Core) থাকে। ইম্পারেটিভ কোডকে মাল্টি-কোর প্রসেসরে ভাগ করে চালানো কঠিন কারণ সেখানে নির্দিষ্ট অর্ডারে কাজ করার বাধ্যবাধকতা থাকে। কিন্তু ডিক্লারেটিভ ল্যাঙ্গুয়েজে যেহেতু আপনি অ্যালগরিদম ঠিক করে দিচ্ছেন না, তাই ডেটাবেস ইঞ্জিন চাইলে কুয়েরিটিকে বিভিন্ন ভাগে ভাগ করে প্যারালালি রান করতে পারে, যা পারফরম্যান্স অনেক বাড়িয়ে দেয়।
Declarative Queries on the Web
ডিক্লারেটিভ কুয়েরি ল্যাঙ্গুয়েজের সুবিধা শুধু ডেটাবেজেই সীমাবদ্ধ নয়, এটি ওয়েব ব্রাউজারের ক্ষেত্রেও সমানভাবে কার্যকর। এখানে CSS (Declarative) এবং JavaScript (Imperative) এর মাধ্যমে একটি নির্দিষ্ট ডিজাইন পরিবর্তনের তুলনা করা যায়।
ধরা যাক, আপনার একটি সমুদ্রের প্রাণীদের ওয়েবসাইট আছে। সেখানে "Sharks" পেজটি সিলেক্ট করা অবস্থায় আছে। HTML স্ট্রাকচারটি এমন:
<ul>
<li class="selected">
<p>Sharks</p>
<ul>
<li>Great White Shark</li>
<li>Tiger Shark</li>
<li>Hammerhead Shark</li>
</ul>
</li>
<li>
<p>Whales</p>
<ul>
<li>Blue Whale</li>
<li>Humpback Whale</li>
<li>Fin Whale</li>
</ul>
</li>
</ul>
এখানে লক্ষ্য করলে দেখবেন, সিলেক্টেড আইটেমটিতে class="selected" ব্যবহার করা হয়েছে। এখন আমাদের উদ্দেশ্য হলো, সিলেক্টেড পেজটির টাইটেল (যেমন: <p>Sharks</p>) এর ব্যাকগ্রাউন্ড নীল রঙ করা। Declarative এবং ইম্পারেটিভ দুই ভাবেই আমরা এটি করে দেখবো ।
ডিক্লারেটিভ পদ্ধতি (The Declarative Approach)
CSS এর মাধ্যমে: CSS একটি ডিক্লারেটিভ ভাষা। এখানে আমরা শুধু বলে দেই আমরা কী চাই (What we want), কীভাবে হবে সেটা ব্রাউজার বুঝে নেয়।
li.selected > p {
background-color: blue;
}
এখানে li.selected > p সিলেক্টরটি নির্দিষ্ট করে দিচ্ছে যে, সেই সব <p> এলিমেন্টকে নীল করো যাদের ডিরেক্ট প্যারেন্ট হলো selected ক্লাস যুক্ত একটি <li> এলিমেন্ট।
XSL এর মাধ্যমে: CSS এর মতো XSL বা XPath-ও একইভাবে কাজ করে:
<xsl:template match="li[@class='selected']/p">
<fo:block background-color="blue">
<xsl:apply-templates/>
</fo:block>
</xsl:template>
ইম্পারেটিভ পদ্ধতি (The Imperative Approach)
যদি আমাদের এই একই কাজ JavaScript ব্যবহার করে করতে হতো, তবে আমাদের প্রতিটি স্টেপ বলে দিতে হতো (How to do it)। কোডটি হবে নিচের মতো:
var liElements = document.getElementsByTagName("li");
for (var i = 0; i < liElements.length; i++) {
if (liElements[i].className === "selected") {
var children = liElements[i].childNodes;
for (var j = 0; j < children.length; j++) {
var child = children[j];
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
child.setAttribute("style", "background-color: blue");
}
}
}
}
ইম্পারেটিভ পদ্ধতির সমস্যাগুলো (Why it's awful)
জটিলতা: ডিক্লারেটিভ কোড (CSS) যেখানে মাত্র কয়েক লাইনের, সেখানে ইম্পারেটিভ কোড অনেক দীর্ঘ এবং বোঝা কঠিন।
স্টেট ম্যানেজমেন্ট (State Management): যদি ইউজার অন্য কোনো পেজে ক্লিক করে এবং
selectedক্লাসটি রিমুভ হয়ে যায়, তবুও ওই নীল রঙটি থেকে যাবে যদি না আপনি ম্যানুয়ালি কোড লিখে সেটি মুছিয়ে দেন। কিন্তু CSS-এ ব্রাউজার নিজে থেকেই বোঝে কখন রুলটি আর কাজ করছে না এবং সাথে সাথে ব্যাকগ্রাউন্ড রিমুভ করে দেয়।পারফরম্যান্স এবং আপডেট: যদি ভবিষ্যতে নতুন কোনো দ্রুতগতির API আসে (যেমন:
getElementsByClassName), তবে আপনাকে পুরো কোড নতুন করে লিখতে হবে। অন্যদিকে, ব্রাউজার যদি নিজে থেকে CSS-এর পারফরম্যান্স ইমপ্রুভ করে, তবে আপনাকে আপনার কোডে হাত দিতে হবে না, তা নিজে থেকেই দ্রুত চলবে।
ওয়েব ব্রাউজারের ক্ষেত্রে যেমন ইম্পারেটিভ জাভাস্ক্রিপ্টের চেয়ে ডিক্লারেটিভ CSS অনেক বেশি কার্যকর, ঠিক তেমনি ডেটাবেজের ক্ষেত্রেও SQL-এর মতো ডিক্লারেটিভ ল্যাঙ্গুয়েজগুলো অনেক বেশি শক্তিশালী এবং সুবিধাজনক।
MapReduce Querying
MapReduce হলো অনেকগুলো মেশিনে একসাথে বিশাল পরিমাণ ডেটা প্রসেস করার একটি প্রোগ্রামিং মডেল। এটি পুরোপুরি ডিক্লারেটিভ নয় (যেমন SQL), আবার পুরোপুরি ইম্পারেটিভ-ও নয়। এখানে কুয়েরির লজিক লেখা হয় কোড স্নsnippet (সাধারণত JavaScript) হিসেবে, যা প্রসেসিং ফ্রেমওয়ার্ক বারবার কল করে।
ধরা যাক, আপনি একজন সামুদ্রিক জীববিজ্ঞানী। আপনি যখনই কোনো প্রাণী দেখেন, ডেটাবেজে একটি রেকর্ড জমা করেন। এখন আপনি প্রতি মাসে কয়টি করে হাঙ্গর (Shark) দেখেছেন, তার একটি রিপোর্ট তৈরি করতে চান।
SQL পদ্ধতি (PostgreSQL): SQL-এ এই কুয়েরিটি খুবই সহজ এবং ডিক্লারেটিভ:
SELECT date_trunc('month', observation_timestamp) AS observation_month,
sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks'
GROUP BY observation_month;
এখানে date_trunc ফাংশনটি টাইমস্ট্যাম্পকে মাসের শুরুতে রাউন্ড করে দেয়। এটি প্রথমে শার্ক ফিল্টার করে, তারপর মাস অনুযায়ী গ্রুপ করে এবং শেষে প্রাণীর সংখ্যা যোগ করে।
MongoDB-তে MapReduce পদ্ধতি
MapReduce মূলত দুটি প্রধান ফাংশনের ওপর ভিত্তি করে কাজ করে: map এবং reduce। কোড উদাহরণ:
db.observations.mapReduce(
function map() {
var year = this.observationTimestamp.getFullYear();
var month = this.observationTimestamp.getMonth() + 1;
emit(year + "-" + month, this.numAnimals);
},
function reduce(key, values) {
return Array.sum(values);
},
{
query: { family: "Sharks" },
out: "monthlySharkReport"
}
);
কিভাবে কাজ করে?
Filter: প্রথমে
query: { family: "Sharks" }অংশটি ডিক্লারেটিভ পদ্ধতিতে শুধু শার্কের ডেটাগুলো বেছে নেয়।Map ফাংশন: প্রতিটি ডকুমেন্ট (document) এর জন্য এই ফাংশন একবার কল হয়। এটি একটি 'Key' (যেমন: "1995-12") এবং একটি 'Value' (প্রাণীর সংখ্যা)
emitবা নির্গত করে।Group: ফ্রেমওয়ার্ক একই কী (Key) যুক্ত ভ্যালুগুলোকে একসাথে গ্রুপ করে।
Reduce ফাংশন: একই মাসের সব ভ্যালু নিয়ে এই ফাংশনটি কল হয় এবং সব সংখ্যা যোগ করে একটি ফলাফল দেয়।
একটি উদাহরণ দিয়ে বুঝলে: যদি দুটি ডেটা থাকে:
ডিসেম্বর ২৫, ১৯৯৫: ৩টি শার্ক
ডিসেম্বর ১২, ১৯৯৫: ৪টি শার্ক
map ফাংশন দুইবার কল হয়ে emit("1995-12", 3) এবং emit("1995-12", 4) তৈরি করবে। এরপর reduce ফাংশনটি reduce("1995-12", [3, 4]) হিসেবে কল হয়ে ফলাফল 7 দেবে।
MapReduce এর সীমাবদ্ধতা ও নিয়ম
Pure Functions:
mapএবংreduceফাংশন অবশ্যই 'Pure' হতে হবে। অর্থাৎ এরা শুধু ইনপুট ডেটা ব্যবহার করবে, অন্য কোনো ডেটাবেজ কুয়েরি করতে পারবে না এবং এদের কোনো সাইড ইফেক্ট (Side effect) থাকতে পারবে না।সুবিধা: এই কড়াকড়ির ফলে ডেটাবেজ চাইলে যেকোনো ক্রমে বা যেকোনো মেশিনে এই ফাংশনগুলো চালাতে পারে এবং ফেইল করলে আবার রান করতে পারে।
MongoDB Aggregation Pipeline (একটি আধুনিক বিকল্প)
MapReduce-এ দুটি আলাদা জাভাস্ক্রিপ্ট ফাংশন লেখা বেশ কঠিন এবং এটি অপ্টিমাইজ করাও ব্রাউজারের জন্য জটিল। তাই MongoDB পরবর্তীতে Aggregation Pipeline নামে একটি ডিক্লারেটিভ ভাষা নিয়ে আসে।
Aggregation Pipeline উদাহরণ:
db.observations.aggregate([
{ $match: { family: "Sharks" } },
{ $group: {
_id: {
year: { $year: "$observationTimestamp" },
month: { $month: "$observationTimestamp" }
},
totalAnimals: { $sum: "$numAnimals" }
} }
]);
এটি অনেকটা SQL-এর মতোই, কিন্তু এর সিনট্যাক্স JSON ভিত্তিক। লেখক মজা করে বলেছেন যে, অনেক NoSQL সিস্টেম আসলে ঘুরিয়ে ফিরিয়ে SQL-কেই পুনরায় আবিষ্কার করছে (Reinventing SQL in disguise)।
Graph-Like Data Models
Graph Model কখন এবং কেন প্রয়োজন, তা নিয়ে আলোচনা করব এখন। আমরা জানি যে ডেটার মধ্যে সম্পর্কের ওপর ভিত্তি করে বিভিন্ন মডেল বেছে নেওয়া হয়।
কেন গ্রাফ মডেল ব্যবহার করবেন?
One-to-Many Relationship: যদি আপনার ডেটা মূলত ট্রি-স্ট্রাকচারড (Tree-structured) হয়, তবে Document Model উপযুক্ত।
Simple Many-to-Many: যদি ডেটার মধ্যে রিলেশনশিপ খুব সাধারণ হয়, তবে Relational Model দিয়ে কাজ চালানো যায়।
Complex Many-to-Many: কিন্তু যখন আপনার ডেটার কানেকশন বা সম্পর্কগুলো অনেক বেশি জটিল হয়ে যায়, তখন সেটিকে Graph হিসেবে মডেল করাটাই সবথেকে স্বাভাবিক এবং কার্যকর সমাধান।
গ্রাফের মূল উপাদান (Vertices and Edges)
একটি গ্রাফ প্রধানত দুটি উপাদানের সমন্বয়ে গঠিত:
Vertices (ভার্টিসেস): এদেরকে Nodes বা Entities-ও বলা হয় (যেমন: একজন ব্যক্তি, একটি স্থান বা একটি ইভেন্ট)।
Edges (এজেস): এদেরকে Relationships বা Arcs বলা হয়। এটি দুটি ভার্টেক্সের মধ্যে সম্পর্ক নির্দেশ করে।
গ্রাফ মডেলের উদাহরণ
বইয়ে গ্রাফ মডেলের কিছু বাস্তবধর্মী উদাহরণ দেওয়া হয়েছে:
Social Graphs: মানুষ হলো ভার্টিসেস, আর তাদের মধ্যকার পরিচয় বা বন্ধুত্ব হলো এজেস।
The Web Graph: প্রতিটি ওয়েব পেজ একটি ভার্টেক্স এবং পেজের মধ্যকার HTML লিঙ্কগুলো হলো এজেস।
Road/Rail Networks: রাস্তার মোড় বা জংশনগুলো ভার্টিসেস এবং রাস্তা বা রেললাইনগুলো হলো এজেস।
গ্রাফ মডেলের ওপর ভিত্তি করে অনেক শক্তিশালী অ্যালগরিদম কাজ করে। যেমন: জিপিএস নেভিগেশনে 'Shortest Path' খোঁজা অথবা সার্চ ইঞ্জিনে 'PageRank' এর মাধ্যমে পেজের র্যাঙ্কিং নির্ধারণ করা।
বৈচিত্র্যময় ডেটা (Heterogeneous Data)
গ্রাফ মডেলের একটি বড় শক্তি হলো এটি একই ডেটাস্টোরে সম্পূর্ণ ভিন্ন ভিন্ন ধরণের অবজেক্ট স্টোর করতে পারে। যেমন ফেসবুকের গ্রাফে ভার্টিসেস হিসেবে মানুষ, লোকেশন, ইভেন্ট বা কমেন্ট থাকতে পারে এবং এজেস হিসেবে কে কার বন্ধু, কে কোথায় চেক-ইন দিয়েছে বা কে কোন পোস্টে কমেন্ট করেছে তা থাকতে পারে।

এই ডায়াগ্রামে দেখা যাচ্ছে লুসি (Lucy) এবং অ্যালাইন (Alain) নামক দুজন ব্যক্তি, তাদের বৈবাহিক সম্পর্ক এবং তাদের বর্তমান বসবাসের স্থান (লন্ডন)। এটি একটি চমৎকার গ্রাফ স্ট্রাকচার্ড ডেটার উদাহরণ।
গ্রাফ ডেটা মডেলের প্রকারভেদ ও কুয়েরি ল্যাঙ্গুয়েজ
ডেটা স্ট্রাকচার এবং কুয়েরি করার পদ্ধতির ওপর ভিত্তি করে গ্রাফ মডেলকে কয়েকটি ভাগে ভাগ করা হয়েছে:
Property Graph Model: এটি Neo4j, Titan এবং InfiniteGraph-এর মতো ডেটাবেজে ব্যবহৃত হয়।
Triple-store Model: এটি Datomic বা AllegroGraph-এ ব্যবহৃত হয়।
কুয়েরি ল্যাঙ্গুয়েজ (Declarative Query Languages): গ্রাফ ডেটা কুয়েরি করার জন্য তিনটি প্রধান ডিক্লেয়ারেটিভ ল্যাঙ্গুয়েজ হলো:
Cypher
SPARQL
Datalog
এছাড়া Gremlin-এর মতো ইম্পারেটিভ (Imperative) ল্যাঙ্গুয়েজ এবং Pregel-এর মতো প্রসেসিং ফ্রেমওয়ার্কও গ্রাফ ডেটা নিয়ে কাজ করতে ব্যবহৃত হয়।
Property Graphs
প্রপার্টি গ্রাফ মডেল মূলত ডেটাকে এমনভাবে সাজায় যেখানে প্রতিটি নোড (Vertex) এবং প্রতিটি সম্পর্ক (Edge) বিস্তারিত তথ্য ধারণ করতে পারে।
একটি Vertex এবং Edge-এর গঠন
একটি প্রপার্টি গ্রাফে প্রতিটি উপাদানের কিছু নির্দিষ্ট বৈশিষ্ট্য থাকে:
একটি Vertex-এ থাকে:
একটি ইউনিক আইডি (Unique identifier)।
একগুচ্ছ আউটগোয়িং এজ (Outgoing edges)।
একগুচ্ছ ইনকামিং এজ (Incoming edges)।
প্রপার্টিজ বা বৈশিষ্ট্য (Key-value pairs)।
একটি Edge-এ থাকে:
একটি ইউনিক আইডি।
যেখান থেকে সম্পর্ক শুরু হয় (Tail vertex)।
যেখানে সম্পর্ক শেষ হয় (Head vertex)।
সম্পর্কের ধরণ বোঝানোর জন্য একটি লেবেল (Label)।
প্রপার্টিজ বা বৈশিষ্ট্য (Key-value pairs)।
রিলেশনাল স্কিমায় গ্রাফের রূপ
গ্রাফ স্টোরকে আপনি সহজভাবে দুটি রিলেশনাল টেবিল হিসেবে কল্পনা করতে পারেন। আমরা এখানে PostgreSQL-এর JSON ডেটা টাইপ ব্যবহার করে উদাহরনটি দেখবো ।
Example 2-2. Representing a property graph using a relational schema
CREATE TABLE vertices (
vertex_id integer PRIMARY KEY,
properties json
);
CREATE TABLE edges (
edge_id integer PRIMARY KEY,
tail_vertex integer REFERENCES vertices (vertex_id),
head_vertex integer REFERENCES vertices (vertex_id),
label text,
properties json
);
CREATE INDEX edges_tails ON edges (tail_vertex);
CREATE INDEX edges_heads ON edges (head_vertex);
গ্রাফ মডেলের বিশেষ দিকসমূহ
গ্রাফ মডেলের তিনটি গুরুত্বপূর্ণ সুবিধার কথা বলা যায়:
Schema-less Flexibility: যেকোনো ভার্টেক্স অন্য যেকোনো ভার্টেক্সের সাথে যুক্ত হতে পারে। কোন জিনিসের সাথে কী যুক্ত হতে পারবে বা পারবে না, তার কোনো ধরাবাঁধা নিয়ম বা বাধা নেই।
Efficient Traversal: একটি ভার্টেক্স থেকে তার ইনকামিং এবং আউটগোয়িং উভয় এজ খুব সহজে খুঁজে পাওয়া যায়, ফলে গ্রাফের ভেতর দিয়ে এক নোড থেকে অন্য নোডে যাতায়াত করা বা পথ (Path) খুঁজে বের করা সহজ হয়।
Diverse Relationships: আলাদা আলাদা লেবেল ব্যবহার করে একই গ্রাফে ভিন্ন ভিন্ন ধরণের তথ্য জমা রাখা সম্ভব, যা ডেটা মডেলকে পরিষ্কার রাখে।
কেন এটি রিলেশনাল মডেলের চেয়ে ফ্লেক্সিবল?
আমরা যদি Figure 2-5 এর দিকে তাকাই তবে দেখতে পারবো, কিছু বাস্তব পরিস্থিতি রিলেশনাল মডেলে প্রকাশ করা বেশ কঠিন, কিন্তু গ্রাফে তা খুব সহজ:
ফ্রান্সে 'départements' এবং 'régions' থাকে, কিন্তু আমেরিকায় থাকে 'counties' এবং 'states'। গ্রাফে এগুলোকে সহজে আলাদা নোড দিয়ে দেখানো যায়।
Granularity: যেমন কারও বর্তমান ঠিকানা শহর পর্যন্ত দেওয়া আছে, কিন্তু জন্মস্থান দেওয়া আছে শুধুমাত্র একটি স্টেট বা রাজ্য হিসেবে। গ্রাফে এই কম-বেশি তথ্যের পার্থক্য খুব সহজেই ম্যানেজ করা যায়।
গ্রাফ মডেলের একটি বড় সুবিধা হলো এর পরিবর্তনের ক্ষমতা (Evolvability)। ধরুন, আপনি অ্যাপ্লিকেশনে নতুন একটি ফিচার আনলেন যেখানে মানুষের এলার্জি ট্র্যাক করতে হবে। আপনি সহজেই একটি 'Allergen' ভার্টেক্স তৈরি করতে পারেন এবং মানুষের সাথে তার একটি এজ তৈরি করে দিতে পারেন। এটি আপনার বিদ্যমান ডেটা স্ট্রাকচারকে নষ্ট না করেই সম্ভব।
The Cypher Query Language
আমরা এখন Cypher নামক একটি declarative query language সম্পর্কে আলোচনা করব। এটি মূলত Neo4j গ্রাফ ডাটাবেজের জন্য তৈরি করা হয়েছে। মুভি 'The Matrix'-এর একটি ক্যারেক্টারের নামানুসারে এর নামকরণ করা হয়েছে।
Cypher-এ ডাটা ইনসার্ট করা খুব সহজ এবং এটি অনেকটা ছবির মতো। এখানে প্রতিটি নোড বা ভার্টেক্সকে (Vertex) একটি নাম দেওয়া হয় (যেমন: USA বা Idaho), এবং অ্যারো নোটেশন -> ব্যবহার করে তাদের মধ্যে সম্পর্ক বা এজ (Edge) তৈরি করা হয়।
Example 2-3 : Figure 2-5 এর ডেটার একটি উপসেট, যা Cypher Query হিসেবে উপস্থাপিত।
Cypher
CREATE
(NAmerica:Location {name:'North America', type:'continent'}),
(USA:Location {name:'United States', type:'country' }),
(Idaho:Location {name:'Idaho', type:'state' }),
(Lucy:Person {name:'Lucy' }),
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
(Lucy) -[:BORN_IN]-> (Idaho)
Note: এখানে
(Idaho) -[:WITHIN]-> (USA)মানে হলো আইডাহো একটি নোড যা একটি "WITHIN" সম্পর্কের মাধ্যমে ইউএসএ নোডের সাথে যুক্ত।
যদি আমরা এমন কাউকে খুঁজে বের করতে চাই যিনি যুক্তরাষ্ট্রে জন্মেছেন কিন্তু বর্তমানে ইউরোপে থাকেন, তবে Cypher-এ নিচের মতো করে কুয়েরি লেখা হয়:
Example 2-4
MATCH
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name
এই কুয়েরিটি কীভাবে কাজ করে?
১. এটি এমন একজন ব্যক্তিকে (person) খোঁজে যার একটি BORN_IN এজ আছে কোনো একটি লোকেশনে। সেই লোকেশন থেকে WITHIN এজ ধরে এগোতে থাকলে এক সময় "United States" লোকেশনে পৌঁছানো যায়।
২. ঠিক একইভাবে, ওই একই ব্যক্তির একটি LIVES_IN এজ থাকবে যা ধরে এগোলে শেষ পর্যন্ত "Europe" লোকেশনে পৌঁছানো যাবে। ৩. [:WITHIN*0..] অংশটির মানে হলো—সম্পর্কটি সরাসরি হতে পারে অথবা কয়েক ধাপ পরেরও হতে পারে (যেমন: শহর -> স্টেট -> কান্ট্রি)।
কুয়েরি অপ্টিমাইজার: সাইফার একটি declarative ল্যাঙ্গুয়েজ, তাই আপনাকে বলে দিতে হবে না যে কীভাবে ডাটা খুঁজতে হবে। ডাটাবেজের 'Query Optimizer' নিজে থেকেই ঠিক করে নেয় কোন দিক থেকে খুঁজলে দ্রুত রেজাল্ট পাওয়া যাবে (যেমন: ইনডেক্স ব্যবহার করে আগে United States খুঁজে বের করা)।
Graph Queries in SQL
গ্রাফ ডাটা কি রিলেশনাল ডাটাবেজে SQL দিয়ে কুয়েরি করা সম্ভব? উত্তর হলো: হ্যাঁ, সম্ভব, কিন্তু এটি বেশ জটিল।
রিলেশনাল ডাটাবেজে (SQL) কুয়েরি করার সময় আমরা আগে থেকেই জানি কতগুলো JOIN লাগবে। কিন্তু গ্রাফের ক্ষেত্রে একটি নোড থেকে অন্য নোডে পৌঁছাতে কতগুলো ধাপ (edges) পার হতে হবে তা আগে থেকে নির্দিষ্ট থাকে না (যেমন: একটি শহর স্টেটের ভেতর, স্টেট দেশের ভেতর—এখানে ধাপ সংখ্যা পরিবর্তনশীল)।
Recursive CTE (Common Table Expressions)
SQL:1999 স্ট্যান্ডার্ডে WITH RECURSIVE সিনট্যাক্স ব্যবহার করে এই ধরণের ভ্যারিয়েবল-লেংথ পাথ খোঁজা সম্ভব।
Example 2-5: নিচে সাইফার কুয়েরিটির সমতুল্য SQL কুয়েরি দেওয়া হলো:
WITH RECURSIVE
-- in_usa: United States এর ভেতরের সব লোকেশন আইডি বের করা
in_usa(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'United States'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'within'
),
-- in_europe: Europe এর ভেতরের সব লোকেশন আইডি বের করা
in_europe(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'Europe'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'within'
),
-- born_in_usa: যারা US-এ জন্মেছে
born_in_usa(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'born_in'
),
-- lives_in_europe: যারা ইউরোপে বাস করে
lives_in_europe(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'lives_in'
)
SELECT vertices.properties->>'name'
FROM vertices
JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;
যে কুয়েরিটি Cypher-এ মাত্র ৪ লাইনে লেখা গেছে, সেটি SQL-এ লিখতে ২৯ লাইন লেগেছে।
এর কারণ হলো, একেকটি ডাটা মডেল একেক ধরণের ব্যবহারের জন্য তৈরি।
গ্রাফ ডাটাবেজ তৈরিই করা হয়েছে জটিল সম্পর্কগুলো সহজে খোঁজার জন্য, যা রিলেশনাল মডেলে অনেক কষ্টসাধ্য এবং জটিল কোডের প্রয়োজন হয়। তাই অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী সঠিক ডাটা মডেল বেছে নেওয়া অত্যন্ত জরুরি।
Triple-Stores and SPARQL
গ্রাফ ডেটা মডেলের আরেকটি রূপ Triple-Store মডেল এবং এর কুয়েরি ল্যাঙ্গুয়েজ SPARQL সম্পর্কে আলোচনা জানব। এটি মূলত প্রপার্টি গ্রাফ মডেলের (Property Graph Model) মতোই, তবে এখানে Data Representation Style কিছুটা ভিন্ন।
Triple-Store মডেলের মূল ধারণা
Triple-store মডেলে সব তথ্যকে তিনটি অংশের একটি স্টেটমেন্ট হিসেবে রাখা হয়: (Subject, Predicate, Object)।
Subject: এটি গ্রাফের একটি Vertex (যেমন: Jim)।
Predicate: এটি সাবজেক্ট এবং অবজেক্টের মধ্যে সম্পর্ক বা বৈশিষ্ট্য বোঝায় (যেমন: likes)।
Object: এটি দুই ধরনের হতে পারে:
Primitive Value: যেমন স্ট্রিং বা নাম্বার। এক্ষেত্রে (lucy, age, 33) এ lucy একটি ভার্টেক্স এবং age:33 তার একটি প্রপার্টি।
Another Vertex: যখন অবজেক্ট নিজেই একটি ভার্টেক্স হয়, তখন Predicate টি একটি Edge হিসেবে কাজ করে। যেমন: (lucy, marriedTo, alain)। এখানে
marriedToহলো একটি এজ।
Turtle ফরম্যাট
Triple-store ডেটাকে মানুষের পড়ার উপযোগী করার জন্য Turtle ফরম্যাট ব্যবহার করা হয়।
Example 2-6: Turtle ফরম্যাটে ডেটা
@prefix : <urn:example:>.
_:lucy a :Person.
_:lucy :name "Lucy".
_:lucy :bornIn _:idaho.
_:idaho a :Location.
_:idaho :name "Idaho".
_:idaho :type "state".
_:idaho :within _:usa.
_:usa a :Location.
_:usa :name "United States".
_:usa :type "country".
_:usa :within _:namerica.
_:namerica a :Location.
_:namerica :name "North America".
_:namerica :type "continent".
এখানে _:lucy বা _:idaho হলো ভার্টেক্স। সেমিকোলন ব্যবহার করে একই সাবজেক্টের একাধিক তথ্য আরও সংক্ষেপে লেখা যায়
Example 2-7

The Semantic Web এবং RDF
Triple-store এর কথা আসলে Semantic Web বিষয়টি চলে আসে। এর মূল আইডিয়া ছিল ওয়েবসাইটগুলো মানুষের পাশাপাশি কম্পিউটারের পড়ার উপযোগী ডেটা (Machine-readable) পাবলিশ করবে।
RDF (Resource Description Framework): এটি বিভিন্ন ওয়েবসাইট থেকে ডেটা সংগ্রহ করে একটি গ্লোবাল "Database of everything" তৈরির প্রোটোকল।
URI এর ব্যবহার: RDF-এ সাবজেক্ট, প্রেডিকেট এবং অবজেক্টকে প্রায়ই URI (যেমন:
http://my-company.com/namespace#within) হিসেবে লেখা হয়। যাতে ভিন্ন ভিন্ন সোর্স থেকে ডেটা কম্বাইন করলেও নামের সংঘাত (Conflict) না হয়।
Example 2-8: RDF/XML ফরম্যাটে একই ডেটা
<rdf:RDF xmlns="urn:example:"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Location rdf:nodeID="idaho">
<name>Idaho</name>
<within>
<Location rdf:nodeID="usa">
<name>United States</name>
</Location>
</within>
</Location>
<Person rdf:nodeID="lucy">
<name>Lucy</name>
<bornIn rdf:nodeID="idaho"/>
</Person>
</rdf:RDF>
SPARQL কুয়েরি ল্যাঙ্গুয়েজ (Example 2-9)
RDF ডেটা মডেলের জন্য SPARQL হলো একটি শক্তিশালী কুয়েরি ল্যাঙ্গুয়েজ। এটি Cypher এর আগে তৈরি হয়েছে এবং Cypher এর প্যাটার্ন ম্যাচিং SPARQL থেকেই অনুপ্রাণিত।
Example 2-9: SPARQL কুয়েরি উদাহরণ (যুক্তরাষ্ট্র থেকে ইউরোপে চলে আসা ব্যক্তিদের খুঁজে বের করার কুয়েরি)
PREFIX : <urn:example:>
SELECT ?personName WHERE {
?person :name ?personName.
?person :bornIn / :within* / :name "United States".
?person :livesIn / :within* / :name "Europe".
}
গ্রাফ ডাটাবেস বনাম নেটওয়ার্ক মডেল (CODASYL)
অনেকে গ্রাফ ডাটাবেসকে পুরনো নেটওয়ার্ক মডেল (CODASYL) এর সাথে গুলিয়ে ফেলেন। কিন্তু এদের মধ্যে বড় কিছু পার্থক্য আছে:
Schema: CODASYL এ নির্দিষ্ট স্কিমা ছিল যে কোন রেকর্ড কার ভেতরে থাকবে। গ্রাফ ডাটাবেসে এমন কোনো বাধ্যবাধকতা নেই; যেকোনো ভার্টেক্স যেকোনোটির সাথে কানেক্ট হতে পারে।
Access: CODASYL-এ ডেটা পেতে হলে নির্দিষ্ট পাথ (Access path) ধরে যেতে হতো। গ্রাফে আপনি সরাসরি ID বা ইনডেক্স দিয়ে যেকোনো ভার্টেক্স খুঁজে পেতে পারেন।
Ordering: CODASYL-এ চাইল্ড রেকর্ডগুলো অর্ডারে থাকতো, যা মেইনটেইন করা কঠিন ছিল। গ্রাফে ভার্টেক্স বা এজ-এর কোনো ডিফল্ট অর্ডার নেই।
Query Language: CODASYL ছিল ইম্পারেটিভ (কীভাবে খুঁজতে হবে তা বলে দিতে হতো), যা স্কিমা বদলালে ভেঙে যেত। গ্রাফ ডাটাবেসে Cypher বা SPARQL এর মতো ডিক্লারেটিভ (কী চাই তা বলা) ল্যাঙ্গুয়েজ ব্যবহার করা হয়।
The Foundation: Datalog
Datalog একটি প্রাচীন কিন্তু অত্যন্ত শক্তিশালী কুয়েরি ল্যাঙ্গুয়েজ। ১৯৮০-র দশকে এটি অ্যাকাডেমিক পর্যায়ে অনেক জনপ্রিয় ছিল। বর্তমান সময়ের আধুনিক কুয়েরি ল্যাঙ্গুয়েজগুলোর (যেমন SPARQL বা Cypher) ভিত্তি বলা হয় এই Datalog-কে। বর্তমানে Datomic বা Cascalog-এর মতো সিস্টেমে এটি ব্যবহৃত হয়।
Datalog-এর ডেটা মডেল
Datalog-এর ডেটা মডেল অনেকটা ট্রিপল-স্টোর (Triple-store) মডেলের মতো, তবে এটি আরও একটু জেনারালাইজড। ট্রিপল-স্টোর যেখানে (subject, predicate, object) ফরম্যাট ব্যবহার করে, Datalog সেখানে predicate(subject, object) ফরম্যাট ব্যবহার করে।
Example 2-10: Datalog facts
name(namerica, 'North America').
type(namerica, continent).
name(usa, 'United States').
type(usa, country).
within(usa, namerica).
name(idaho, 'Idaho').
type(idaho, state).
within(idaho, usa).
name(lucy, 'Lucy').
born_in(lucy, idaho).
কুয়েরি করার পদ্ধতি: Rules এবং Predicates
Cypher বা SPARQL সরাসরি SELECT বা MATCH দিয়ে শুরু করলেও, Datalog ধাপে ধাপে কাজ করে। এখানে আমরা কিছু Rules (নিয়ম) তৈরি করি, যা ডেটা থেকে নতুন তথ্য বা সম্পর্ক (Derived Predicates) বের করতে সাহায্য করে।
Example 2-11: Datalog-এ জটিল কুয়েরি
/* Rule 1: সরাসরি নাম থাকলে সেটি ওই লোকেশনের অন্তর্ভুক্ত */
within_recursive(Location, Name) :- name(Location, Name).
/* Rule 2: যদি A থাকে B এর ভেতর এবং B থাকে C এর ভেতর, তবে A আছে C এর ভেতর (Recusion) */
within_recursive(Location, Name) :- within(Location, Via),
within_recursive(Via, Name).
/* Rule 3: মাইগ্রেশনের তথ্য বের করার নিয়ম */
migrated(Name, BornIn, LivingIn) :- name(Person, Name),
born_in(Person, BornLoc),
within_recursive(BornLoc, BornIn),
lives_in(Person, LivingLoc),
within_recursive(LivingLoc, LivingIn).
/* কুয়েরি: কে আমেরিকা থেকে ইউরোপে মাইগ্রেট করেছে? */
?- migrated(Who, 'United States', 'Europe').
Datalog কীভাবে কাজ করে?
Variables: যেসব শব্দ বড় হাতের অক্ষর (Uppercase) দিয়ে শুরু হয় (যেমন:
Location,Name), সেগুলো হলো ভেরিয়েবল।Rule Application: একটি রুল তখন কার্যকর হয় যখন
:-চিহ্নের ডান পাশের সব প্রেডিকেট ডেটাবেসের তথ্যের সাথে মিলে যায়। যখন মিলে যায়, তখন ভেরিয়েবলগুলোর মান বসিয়ে বাম পাশের অংশটি ডেটাবেসে একটি নতুন তথ্য হিসেবে গণ্য হয়।Recursion: এখানে
within_recursiveরুলটি নিজেকেই নিজে কল করতে পারে (Rule 2)। এটি ব্যবহারের ফলে আমরা খুব সহজেই একটি জায়গার ভেতর আরেকটি জায়গা, তার ভেতর আরেকটি—এভাবে গভীর শিকল বা হায়ারার্কি খুঁজে বের করতে পারি।

কেন Datalog গুরুত্বপূর্ণ?
Datalog-এ জটিল কুয়েরিগুলোকে ছোট ছোট রুলে ভাগ করা যায় এবং এক রুল অন্য রুলে কল করা যায় (যেমন ফাংশন কল করা হয়)।
সহজ কুয়েরির জন্য এটি কিছুটা জটিল মনে হতে পারে, কিন্তু ডেটা যদি অনেক বেশি ইন্টার-কানেক্টেড বা জটিল হয়, তবে Datalog-এর এই "Building blocks" পদ্ধতিটি অনেক বেশি কার্যকর।
Summary
এই অধ্যায়ে আমরা বিভিন্ন ধরনের ডেটা মডেল এবং তাদের ব্যবহারের ক্ষেত্র সম্পর্কে জেনেছি। প্রতিটি মডেলের নিজস্ব শক্তি এবং সীমাবদ্ধতা রয়েছে। নিচে এর মূল পয়েন্টগুলো বিস্তারিত আলোচনা করা হলো:
ডেটা মডেলের বিবর্তন
Hierarchical Model (হায়ারার্কিকাল মডেল): ঐতিহাসিকভাবে ডেটাকে একটি বড় (Tree) এর মতো রিপ্রেজেন্ট করা হতো। কিন্তু এতে Many-to-Many সম্পর্ক দেখানো খুব কঠিন ছিল।
Relational Model (রিলেশনাল মডেল): হায়ারার্কিকাল মডেলের সমস্যা সমাধান করতেই রিলেশনাল মডেলের জন্ম। এটি দীর্ঘ সময় ধরে আধিপত্য বিস্তার করেছে।
NoSQL-এর উত্থান: আধুনিক অ্যাপ্লিকেশনের প্রয়োজনীয়তা অনুযায়ী NoSQL দুটি প্রধান ধারায় বিভক্ত হয়েছে:
Document Databases: যখন ডেটা স্বয়ংসম্পূর্ণ (Self-contained) ডকুমেন্টের মতো থাকে এবং এক ডকুমেন্টের সাথে অন্যটির সম্পর্ক খুব কম থাকে।
Graph Databases: এর উল্টোটা; যেখানে সবকিছুই সবার সাথে সম্পর্কিত হতে পারে।
মডেলগুলোর তুলনা ও উপযোগিতা
আজকের দিনে রিলেশনাল, ডকুমেন্ট এবং গ্রাফ—তিনটি মডেলই সমানভাবে জনপ্রিয়।
One-size-fits-all এর অভাব: একটি মডেলকে অন্যটির মাধ্যমে রিপ্রেজেন্ট করা সম্ভব (যেমন: রিলেশনাল ডাটাবেসে গ্রাফ ডেটা রাখা), কিন্তু সেটি খুব জটিল এবং Awkward হয়ে পড়ে। তাই নির্দিষ্ট কাজের জন্য নির্দিষ্ট মডেল ব্যবহার করাই বুদ্ধিমানের কাজ।
Schema: ডকুমেন্ট এবং গ্রাফ ডাটাবেস সাধারণত Schema-on-read (ইমপ্লিসিট) অনুসরণ করে, যা দ্রুত পরিবর্তনশীল অ্যাপ্লিকেশনের জন্য সুবিধাজনক। অন্যদিকে রিলেশনাল ডাটাবেস Schema-on-write (এক্সপ্লিসিট) কঠোরভাবে মেনে চলে।
কুয়েরি ল্যাঙ্গুয়েজসমূহ
আমরা এই অধ্যায়ে বেশ কিছু কুয়েরি ল্যাঙ্গুয়েজ এবং ফ্রেমওয়ার্ক সম্পর্কে জেনেছি:
Declarative: SQL, Cypher, SPARQL, Datalog.
Functional/Processing: MapReduce, MongoDB’s aggregation pipeline.
Parallels: CSS এবং XSL/XPath (যা সরাসরি ডাটাবেস ল্যাঙ্গুয়েজ না হলেও এদের কাজের ধরন একই রকম)।
বিশেষায়িত ডেটা মডেল (Specialized Models)
আমাদের আলোচিত মডেলগুলোর বাইরেও অনেক বিশেষায়িত মডেল রয়েছে:
Genome Data : ডিএনএ সিকোয়েন্স সার্চ করার জন্য গবেষকরা GenBank-এর মতো বিশেষ সফটওয়্যার ব্যবহার করেন, কারণ সাধারণ ডাটাবেস এই বিশাল স্ট্রিং ম্যাচিং হ্যান্ডেল করতে পারে না।
Particle Physics: LHC-এর মতো প্রজেক্টগুলোতে শত শত পেটাবাইট ডেটা অ্যানালাইসিস করতে কাস্টম হার্ডওয়্যার এবং সফটওয়্যার সলিউশন লাগে।
Full-text Search: এটিও এক ধরনের ডেটা মডেল যা সার্চ ইনডেক্স তৈরির কাজে লাগে
ডেটা মডেল নির্বাচন করার সময় অ্যাপ্লিকেশনের ডেটা স্ট্রাকচার এবং রিলেশনশিপের ওপর ভিত্তি করে সিদ্ধান্ত নিতে হয়। পরবর্তী অধ্যায়ে (Chapter 3) আমরা আলোচনা করব এই মডেলগুলো পর্দার আড়ালে (Implementation level) কীভাবে কাজ করে এবং তাদের স্টোরেজ ইঞ্জিন সম্পর্কে।

