কনসিস্টেন্ট হ্যাশিং (Consistent Hashing)
সিম্পল হ্যাশিং (Simple Hashing) নিয়ে সমস্যা
কল্পনা করুন, আপনার ৪টি সার্ভার আছে এবং আপনি সেগুলোতে ডেটা রেন্ডমলি ভাগ করে রাখতে চান। এর সবচেয়ে সাধারণ উপায়: server = hash(key) % 4। "anika" কী (Key)-টিকে হ্যাশ করলে ২ পাওয়া যায়, তাই এটি সার্ভার ২-এ যায়। খুব সহজ।
কিন্তু যখন আপনি আরেকটি সার্ভার যোগ করেন তখন কী হবে? হিসাবটি বদলে গিয়ে হবে hash(key) % 5। হুট করেই আপনার প্রায় প্রত্যেকটি কী অন্য কোনো সার্ভারের দিকে নির্দেশ করবে। "anika" হয়তো এখন সার্ভার ২-এর পরিবর্তে সার্ভার ৩-এ যাবে। এর মানে হলো আপনার অধিকাংশ ডেটাই এক সার্ভার থেকে অন্য সার্ভারে স্থানান্তরিত করতে হবে — যা ডেটার এক বড়সড় পুনর্বিন্যাস (Reshuffling)।
বাস্তব সিস্টেমগুলোতে এই ধরনের রিশাফলিং এক ভয়ংকর ব্যাপার। চিন্তা করুন, ১০০টি সার্ভার এবং বিলিয়ন বিলিয়ন কী সংবলিত একটি ক্যাশ ক্লাস্টারের কথা। একটি মাত্র সার্ভার যোগ করলে সব ক্যাশ ডেটা ইনভ্যালিডেট (Invalidate) হয়ে যাবে, ফলে ক্যাশ স্ট্যাম্পেড (Cache stampede) বা ক্যাশের উপর এমন এক প্রবল চাপ পড়বে যা আপনার ডেটাবেসকে নিমেষেই ডাউন করে দিতে পারে।
কনসিস্টেন্ট হ্যাশিং অত্যন্ত সুন্দরভাবে এই সমস্যার সমাধান করে।
হ্যাশ রিং (The Hash Ring)
সংখ্যার একটি লাইনের পরিবর্তে ০ থেকে 2^32 পর্যন্ত একটি বৃত্তের (Ring) কথা কল্পনা করুন। সার্ভার এবং কী, দুটিকেই একটি হ্যাশ ফাংশন ব্যবহার করে এই রিংয়ের ওপর বসানো হয়।
এটি কীভাবে কাজ করে:
- সার্ভারগুলোকে রিংয়ে বসান। সার্ভারের নাম/আইপি-কে হ্যাশ করে তার পজিশন নির্ণয় করুন। ধরুন, সার্ভার A-এর পজিশন ১০,০০০; সার্ভার B-এর ৪০,০০০ এবং সার্ভার C-এর ৮০,০০০।
- কী-গুলোকে রিংয়ে বসান। প্রতিটি কী-কে হ্যাশ করে তার পজিশন বের করুন। ধরুন "anika" কী-টির পজিশন হয়েছে ২৫,০০০।
- মালিককে খুঁজে বের করুন। কী-এর পজিশন থেকে ঘড়ির কাঁটার দিকে (Clockwise) চলতে থাকুন যতক্ষণ না কোনো সার্ভার পাওয়া যায়। ২৫,০০০ পজিশনে থাকা "anika" ঘড়ির কাঁটার দিকে চলতে চলতে ৪০,০০০ পজিশনে থাকা সার্ভার B-কে পাবে। এর মানে, সার্ভার B হলো "anika"-এর মালিক।
এখন আসি মূল জাদুতে: যখন আপনি কোনো সার্ভার যোগ করবেন বা সরিয়ে নেবেন, তখন শুধু নতুন সার্ভার ও আগের সার্ভারের মধ্যবর্তী কী-গুলোই সরানোর প্রয়োজন হবে। সবকিছু রিশাফলিং করার বদলে, মোট ডেটার খুব সামান্য অংশই এর দ্বারা প্রভাবিত হবে।
কনসিস্টেন্ট হ্যাশিংয়ের ইমপ্লিমেন্টশন
ভার্চুয়াল নোড (Virtual Nodes)
প্রাথমিক কনসিস্টেন্ট হ্যাশিং-এ একটি বড় সমস্যা দেখা যায়: সার্ভারগুলো রিংয়ের ওপর অসমভাবে ছড়িয়ে থাকতে পারে। যদি সার্ভার A এবং সার্ভার B একে অপরের ভীষণ কাছাকাছি চলে আসে, তাহলে দেখা যাবে সার্ভার A খুব ছোট একটা অংশ সামলাচ্ছে, অন্যদিকে সার্ভার C রিংয়ের বিশাল একটা অংশ জুড়ে আছে। এতে করে সার্ভারগুলোর মধ্যে লোড ডিস্ট্রিবিউশন আনব্যালান্সড হয়ে যায়।
ভার্চুয়াল নোড এই সমস্যার সমাধান করে। রিংয়ে প্রতিটি সার্ভারকে একবার করে না বসিয়ে, সেগুলো অনেকবার (হতে পারে ১০০-২০০টি কপি) বসানো হয়। প্রতিটি কপিকেই একটি ভার্চুয়াল নোড বলা হয়। সার্ভার A এর জন্য "A:vnode0", "A:vnode1", ..., থেকে শুরু করে "A:vnode149" পর্যন্ত পজিশন থাকে।
সার্ভারপ্রতি ১৫০টি করে ভার্চুয়াল নোড থাকার ফলে পজিশনগুলো রিংজুড়ে খুব সমানভাবে ছড়িয়ে থাকে। ফলে লোড ডিস্ট্রিবিউশনটিও অনেক বেশি ইউনিফর্ম হয় — সাধারণত দুই সার্ভারের মধ্যে ৫-১০% এর বেশি ভ্যারিয়্যান্স থাকে না।
ভার্চুয়াল নোড ব্যবহার করে হেটেরোজিনিয়াস সার্ভার (Heterogeneous servers) বা ভিন্ন ভিন্ন সক্ষমতার সার্ভার হ্যান্ডেল করাটাও সহজ হয়। একটি শক্তিশালী সার্ভারকে ৩০০টি ভার্চুয়াল নোড দেওয়া যেতে পারে, যেখানে তুলনামূলক দুর্বল একটি সার্ভারকে ১০০টি ভার্চুয়াল নোড দেওয়া হলো, যার ফলে শক্তিশালী সার্ভারটি অনেক বেশি ট্র্যাফিক রিসিভ করতে পারে।
সার্ভার যোগ-বিয়োগের সময় আসলে কী ঘটে?
সার্ভার যোগ করা: নতুন যোগ করা সার্ভারটি তার ঠিক ঘড়ির কাঁটার দিকের প্রতিবেশী সার্ভার থেকে কিছু কী নিজের দায়িত্বে নিয়ে নেয়। ঠিক ওই নির্দিষ্ট কীগুলো ছাড়া অন্য আর কোনো কিছুই নড়ে না। N সংখ্যক সার্ভারের ক্ষেত্রে, একটি সার্ভার যোগ করলে মোট কী-এর মাত্র ~1/N অংশ জায়গা পরিবর্তন করে। এর সাথে মডুলার হ্যাশিংয়ের তুলনা করে দেখুন, যেখানে প্রায় প্রতিটি কী-কেই জায়গা পরিবর্তন করতে হতো!
সার্ভার রিমুভ করা: যে সার্ভারটিকে রিমুভ করা হলো, তার কাছে থাকা কীগুলো সেটির ঘড়ির কাঁটার দিকের প্রতিবেশী সার্ভারের কাছে চলে যায়। এক্ষেত্রেও শুধুমাত্র ~1/N অংশ কী প্রভাবিত হয়।
চলুন, ১০০টি সার্ভার এবং ১মিলিয়ন (১০ লক্ষ) কী নিয়ে অংকটি করে দেখি:
- মডুলার হ্যাশিং: একটি সার্ভার যোগ করা হলো → প্রায় ৯,৯০,০০০টি (৯৯%) কী জায়গা বদলায়।
- কনসিস্টেন্ট হ্যাশিং: একটি সার্ভার যোগ করা হলো → প্রায় ১০,০০০টি (১%) কী জায়গা বদলায়।
এটি ৯৯ গুণ কম! একটি ক্যাশ ক্লাস্টারে এর মানে হলো আপনার ক্যাশের ৯৯% একদম অক্ষত থাকবে এবং সেগুলো ইনভ্যালিড হবে না। এর ফলে আপনার ডেটাবেসে কোনো প্রভাবই পড়বে না।
বাস্তব অ্যাপ্লিকেশন (Real-World Use Cases)
ডিস্ট্রিবিউটেড সিস্টেমের আনাচকানাচে কনসিস্টেন্ট হ্যাশিং ব্যবহার করা হয়:
অ্যামাজন ডাইনামো ডিবি (Amazon DynamoDB) — স্টোরেজ নোডগুলোতে ডেটা বণ্টন করতে কনসিস্টেন্ট হ্যাশিং ব্যবহার করে। একটি নোড যোগ করা হলে বা রিমুভ করা হলে, শুধু আশপাশের পার্টিশনগুলোতে প্রভাব পড়ে।
অ্যাপাচি ক্যাসান্ড্রা (Apache Cassandra) — প্রতিটি নোডকে একটি হ্যাশ রিংয়ে বসানো হয়। পার্টিশন কী-এর হ্যাশের ওপর নির্ভর করে ডেটাগুলো স্বয়ংক্রিয়ভাবে নিজেদের মধ্যে বণ্টন করে নেয়। কোনো নোড যুক্ত করার সময় খুব সামান্য ডেটাই মাইগ্রেশন করতে হয়।
আকামাই CDN — এটি কনসিস্টেন্ট হ্যাশিংয়ের একেবারে শুরুর দিকের ব্যবহারকারীদের একটি! কোন এর সার্ভার কোন কন্টেন্ট ক্যাশ করে রাখবে তা ঠিক করতে তারা কনসিস্টেন্ট হ্যাশিং ব্যবহার করে। এটি তাদের ১৯৯৭ সালের অরিজিনাল পেপারে উল্লেখ রয়েছে।
ডিসকর্ড (Discord) — নির্দিষ্ট সার্ভার প্রসেসে ইউজারদের রাউট করার কাজে কনসিস্টেন্ট হ্যাশিং ব্যবহার করে। যখন তারা ক্যাপাসিটি বৃদ্ধি করে, তখন খুব অল্প সংখ্যক ইউজারকেই নতুন সার্ভিসের সাথে ব্যালেন্স করতে হয়।
লোড ব্যাল্যান্সার (Load Balancers) — কিছু কিছু লোড ব্যাল্যান্সার সেশন অ্যাফিনিটি (Session Affinity) নিশ্চিত করার জন্য কনসিস্টেন্ট হ্যাশিং ব্যবহার করে। সিম্পল হ্যাশ-মডের রিশাফলিং এর ঝামেলা এড়িয়ে একই কী (যেমন ইউজার আইডি, সেশন আইডি) সবসময় একই ব্যাকএন্ড সার্ভারে ম্যাপ করা হয়।
Key Metrics
ছোট কুইজ
পড়া চালিয়ে যান