শার্ডিং ও পার্টিশনিং
এই পাঠে যা শিখবেন
- রেপ্লিকেশন (L16) কেন যথেষ্ট নয় এবং পার্টিশনিং কোন সমস্যাটি (রাইট থ্রুপুট, স্টোরেজ ক্যাপাসিটি) সমাধান করে
- Range-based, Hash-based ও Directory-based শার্ডিং স্ট্র্যাটেজির পার্থক্য ও প্রতিটির ট্রেড-অফ
- Cross-shard join ও resharding কেন অপারেশনালি কঠিন
- Python দিয়ে একটি hash-based sharding ফাংশন — সমান বণ্টন যাচাই করে
১ · রেপ্লিকেশনের পরেও কেন পার্টিশনিং দরকার
L16-এ আমরা দেখেছি রেপ্লিকেশন একই ডেটার একাধিক কপি রেখে রিড স্কেল করে ও availability বাড়ায় — কিন্তু প্রতিটি রেপ্লিকায় একই সম্পূর্ণ ডেটাসেট থাকে। ফলে একটি সিঙ্গেল মেশিনের স্টোরেজ ক্যাপাসিটি ও — মাস্টার-স্লেভ মডেলে — রাইট থ্রুপুট এখনও একটি একক প্রাইমারি সার্ভারের সীমার মধ্যে বন্দী থাকে। পার্টিশনিংPartitioningএকটি বিশাল ডেটাসেটকে একাধিক স্বাধীন ডেটাবেস ইনস্ট্যান্সে (শার্ড) ভাগ করে দেওয়া, যাতে প্রতিটি শার্ড শুধু ডেটার একটি উপসেট ধরে রাখে। — যাকে ডেটাবেসের প্রেক্ষাপটে প্রায়ই শার্ডিংShardingপার্টিশনিং-এর একটি জনপ্রিয় নাম — বিশেষত যখন প্রতিটি অংশ (শার্ড) একটি স্বতন্ত্র ডেটাবেস সার্ভারে হোস্ট করা হয়। বলা হয় — এই সমস্যা সমাধান করে। প্রতিটি শার্ড শুধু ডেটার একটি অংশ ধরে রাখে, ফলে মোট রাইট থ্রুপুট ও স্টোরেজ ক্যাপাসিটি শার্ড সংখ্যার সাথে সাথে বাড়ে (horizontal scaling, L10-এর ধারণা এখানে ডেটাবেস স্তরে প্রয়োগ হচ্ছে)।
রেপ্লিকেশন একই ডেটার একাধিক কপি রাখে (redundancy)। পার্টিশনিং ভিন্ন ডেটাকে ভিন্ন জায়গায় রাখে (distribution)। বাস্তব সিস্টেমে দুটি একসাথে ব্যবহৃত হয় — প্রতিটি শার্ড নিজেই আবার রেপ্লিকেটেড থাকে, যাতে একটি শার্ডের প্রাইমারি নষ্ট হলেও সেই শার্ডের ডেটা হারিয়ে না যায়।
২ · তিনটি শার্ডিং স্ট্র্যাটেজি
একটি রেকর্ড কোন শার্ডে যাবে তা ঠিক করার তিনটি প্রধান পদ্ধতি আছে — প্রতিটির নিজস্ব সুবিধা-অসুবিধা আছে।
কী-এর মান অনুযায়ী রেঞ্জ ভাগ করা (যেমন user_id ১-১০ লাখ → শার্ড ১, ১০ লাখ-২০ লাখ → শার্ড ২)। সহজ ও রেঞ্জ কোয়েরি দ্রুত, কিন্তু ক্রমবর্ধমান কী (যেমন timestamp) সবসময় সর্বশেষ শার্ডে ট্রাফিক কেন্দ্রীভূত করে — হটস্পট তৈরি করে।
কী-এর উপর একটি হ্যাশ ফাংশন প্রয়োগ করে
hash(key) % num_shards দিয়ে শার্ড ঠিক করা। বণ্টন প্রায় সমান হয়, হটস্পট এড়ায় — কিন্তু রেঞ্জ কোয়েরি ("সব ইউজার যাদের ID ১-১০০-এর মধ্যে") কঠিন, কারণ কাছাকাছি কী ভিন্ন শার্ডে ছড়িয়ে থাকে।একটি আলাদা লুকআপ সার্ভিস (mapping table) রাখা হয় যা প্রতিটি কী কোন শার্ডে আছে তা ট্র্যাক করে। সবচেয়ে নমনীয় (কাস্টম রিব্যালেন্সিং সম্ভব) — কিন্তু এই লুকআপ সার্ভিসটি নিজেই একটি নতুন নির্ভরতা ও সম্ভাব্য বটলনেক/সিঙ্গেল-পয়েন্ট-অফ-ফেইলিওর হয়ে দাঁড়ায়।
hash(key) % N ব্যবহার করলে শার্ড সংখ্যা বদলালে প্রায় সব কী পুনর্বণ্টন
করতে হয় — তাই বাস্তব সিস্টেমে প্রায়ই কনসিস্টেন্ট হ্যাশিং বা ভার্চুয়াল শার্ড (fixed বেশি সংখ্যক লজিক্যাল শার্ড,
কম সংখ্যক ফিজিক্যাল সার্ভারে ম্যাপ করা) ব্যবহার করা হয়।
৩ · Cross-shard Join ও Resharding — দুটি বড় চ্যালেঞ্জ
- Cross-shard joinCross-shard Joinএকটি কোয়েরি যার জন্য একাধিক শার্ড থেকে ডেটা টেনে অ্যাপ্লিকেশন স্তরে মেলাতে হয়, কারণ একটি একক SQL JOIN বিভিন্ন ডেটাবেস সার্ভার জুড়ে সরাসরি চলতে পারে না। — একক ডেটাবেসে JOIN দ্রুত (একই মেশিনে ডেটা)। শার্ডেড সিস্টেমে সম্পর্কিত ডেটা ভিন্ন শার্ডে থাকলে অ্যাপ্লিকেশনকে একাধিক নেটওয়ার্ক কল করে ফলাফল নিজে মেলাতে হয় — ধীর ও জটিল। সমাধান: সম্পর্কিত ডেটা (যেমন একই ইউজারের সব রেকর্ড) একই শার্ডে রাখার চেষ্টা করা (shard key নির্বাচন এখানে গুরুত্বপূর্ণ)।
- ReshardingReshardingশার্ড সংখ্যা বাড়ানো/কমানো এবং সেই অনুযায়ী বিদ্যমান ডেটা নতুন শার্ড বিন্যাসে পুনর্বণ্টন করার অপারেশন। — শার্ড সংখ্যা বদলালে (স্কেল করার জন্য) প্রচুর ডেটা এক শার্ড থেকে অন্য শার্ডে সরাতে হয় — একটি লাইভ প্রোডাকশন সিস্টেমে ডাউনটাইম ছাড়া এটি করা অপারেশনালি সবচেয়ে ঝুঁকিপূর্ণ কাজগুলোর একটি।
৪ · Python-এ Hash-based Sharding বাস্তবায়ন
নিচের কোডে hashlib.md5 দিয়ে প্রতিটি কী-কে একটি সংখ্যায় রূপান্তর করে num_shards দিয়ে
মড দিয়ে শার্ড ঠিক করা হচ্ছে। MD5 এখানে ক্রিপ্টোগ্রাফিক নিরাপত্তার জন্য নয় — শুধু একটি নির্ভরযোগ্য, সমানভাবে
বিতরণকারী (uniformly distributing) হ্যাশ ফাংশন হিসেবে ব্যবহৃত হচ্ছে।
import hashlib
def shard_for_key(key, num_shards):
digest = hashlib.md5(key.encode()).hexdigest()
return int(digest, 16) % num_shards
num_shards = 4
keys = [f"user_{i}" for i in range(1, 21)] # ২০টি নমুনা কী
counts = [0] * num_shards
assignments = {}
for key in keys:
shard = shard_for_key(key, num_shards)
counts[shard] += 1
assignments[key] = shard
print("কী → শার্ড ম্যাপিং (প্রথম ৫টি):")
for key in keys[:5]:
print(f" {key} → শার্ড {assignments[key]}")
print("\nপ্রতিটি শার্ডে কী-সংখ্যা:")
for i, c in enumerate(counts):
print(f" শার্ড {i}: {c}টি কী")
print(f"\nমোট কী: {sum(counts)} (প্রত্যাশিত সমান ভাগ ≈ {len(keys)/num_shards:.1f} প্রতি শার্ডে)")
শার্ডিং একটি একক ডেটাবেসের ক্যাপাসিটি সীমা ভেঙে দেয় — কিন্তু বিনিময়ে অ্যাপ্লিকেশন-স্তরে জটিলতা আনে (cross-shard join, resharding)। shard key নির্বাচন তাই সবচেয়ে গুরুত্বপূর্ণ সিদ্ধান্ত — এমন একটি key বেছে নেওয়া উচিত যা সাধারণ কোয়েরি প্যাটার্নে (যেমন "এই ইউজারের সব ডেটা") একই শার্ডে ডেটা রাখে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ Range-based partitioning-এ কেন একটি ক্রমবর্ধমান (ever-increasing) কী যেমন timestamp বা auto-increment ID হটস্পট তৈরি করতে পারে?
কারণ নতুন রেকর্ড সবসময় সবচেয়ে বড় মানের কাছাকাছি তৈরি হয় — যেমন সব নতুন অর্ডার সবসময় সর্বশেষ শার্ডে (যেটি "সাম্প্রতিক" রেঞ্জ ধরে রাখে) যায়। ফলে সমস্ত নতুন রাইট ট্রাফিক একটিমাত্র শার্ডে কেন্দ্রীভূত হয়ে যায়, বাকি শার্ডগুলো প্রায় নিষ্ক্রিয় থাকে — পুরো পার্টিশনিং-এর মূল উদ্দেশ্যই (লোড ছড়ানো) ব্যর্থ হয়ে যায়। এই কারণে সময়-ভিত্তিক বা sequential কী-এর জন্য প্রায়ই hash-based sharding বেশি উপযোগী।
প্র ০২ Hash-based sharding-এ কেন cross-shard কোয়েরি ("সব ইউজার যাদের ID ১০০০-২০০০-এর মধ্যে") রেঞ্জ-বেসড শার্ডিং-এর চেয়ে বেশি কঠিন হয়?
কারণ হ্যাশ ফাংশন ইচ্ছাকৃতভাবে কাছাকাছি মানের কী-কে দূরে ছড়িয়ে দেয় (এটাই সমান বণ্টনের রহস্য) — তাই ১০০০-২০০০ রেঞ্জের ID-গুলো আসলে চারটি শার্ডেই ছড়িয়ে থাকতে পারে, কোনো একটি নির্দিষ্ট শার্ডে জড়ো থাকে না। ফলে এই রেঞ্জ কোয়েরি চালাতে প্রতিটি শার্ডে গিয়ে ফলাফল সংগ্রহ করে অ্যাপ্লিকেশন স্তরে মেলাতে হয় — range-based শার্ডিং-এ একই কোয়েরি সরাসরি একটি বা দুটি শার্ডেই সীমাবদ্ধ থাকত।
প্র ০৩ Directory-based partitioning-এর সবচেয়ে বড় সুবিধা ও সবচেয়ে বড় ঝুঁকি কী?
সুবিধা: সবচেয়ে নমনীয় — যেকোনো কী-কে যেকোনো শার্ডে ম্যাপ করা যায়, এবং রিব্যালেন্সিং-এর সময় শুধু লুকআপ টেবিলের এন্ট্রি আপডেট করলেই হয়, ডেটা কোথায় আছে তা নির্ধারণের লজিক বদলাতে হয় না। ঝুঁকি: এই লুকআপ সার্ভিসটি নিজেই একটি নতুন সিঙ্গেল-পয়েন্ট-অফ-ফেইলিওর ও সম্ভাব্য বটলনেক — প্রতিটি ডেটাবেস কোয়েরির আগে একটি অতিরিক্ত নেটওয়ার্ক হপ (লুকআপ সার্ভিসে) লাগে, এবং লুকআপ সার্ভিসটি ডাউন হলে পুরো সিস্টেম কোন ডেটা কোথায় আছে তা জানতে পারে না।
অনুশীলন
-
চিন্তা করুন: একটি লগিং সিস্টেম যেখানে প্রতিটি রেকর্ডের কী একটি ক্রমবর্ধমান timestamp — এখানে range-based নাকি hash-based শার্ডিং বেশি উপযুক্ত হবে, এবং কেন?
সরল উত্তর "range-based, কারণ সময়-ভিত্তিক কোয়েরি সহজ হয়" মনে হতে পারে, কিন্তু আসল সমস্যা হলো — টাইমস্ট্যাম্প ক্রমবর্ধমান হওয়ায় range-based শার্ডিং-এ সব নতুন লগ সবসময় সর্বশেষ শার্ডে যাবে (হটস্পট, প্র ০১ দেখুন)। বাস্তব সিস্টেমে তাই প্রায়ই hash-based sharding (write লোড সমানভাবে ছড়ানোর জন্য) ব্যবহার করে, আর সময়-ভিত্তিক রেঞ্জ কোয়েরির জন্য আলাদা একটি ইনডেক্স বা metadata layer রাখা হয় — এটি একটি ক্লাসিক ট্রেড-অফ যেখানে "সহজ কোয়েরি" বনাম "সমান লোড বণ্টন" বেছে নিতে হয়।
-
কোড বদলান: উপরের কোড সেলে
num_shards৪ থেকে ৮ করুন এবং কী-এর সংখ্যা ২০ থেকে ১০০ করুন — চেক করুন প্রতিটি শার্ডে কী-সংখ্যা এখনও মোটামুটি সমান থাকে কি না।হ্যাঁ থাকবে — কারণ
hashlib.md5-এর আউটপুট একটি ভালো uniformly-distributed হ্যাশ, তাই যত বেশি কী থাকবে, প্রতিটি শার্ডে গড়েমোট কী / num_shardsসংখ্যক কী পড়ার সম্ভাবনা তত বেশি নিশ্চিত হবে (আইনের বড় সংখ্যার প্রভাব — অল্প কী-তে সামান্য অসমতা দেখা গেলেও কী-সংখ্যা বাড়ার সাথে সাথে বণ্টন আরও মসৃণ হয়)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫১টি পাঠ পরবর্তী পাঠ — ডিস্ট্রিবিউটেড ট্রানজেকশন ও টু-ফেজ কমিট।
- L13 · কনসিস্টেন্ট হ্যাশিং পূর্ববর্তী ধারণা Hash-based sharding-এর রিব্যালেন্সিং সমস্যার সমাধান হিসেবে কনসিস্টেন্ট হ্যাশিং কীভাবে কাজ করে তা দেখুন।
- L16 · রেপ্লিকেশন — Master-Slave ও Multi-Master পূর্ববর্তী পাঠ পার্টিশনিং প্রায়ই রেপ্লিকেশনের সাথে একত্রে ব্যবহৃত হয় — কেন তা রিভাইজ করুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics ও System Design — সব এক জায়গায়।