এই হতাশা জনক সময়ে শাহাদ ইশরাক চমৎকার একটা উদ্দ্যোগ নিয়েছেন। উনি একটা সিস্টেম ডিজাইন সংক্রান্ত সমস্যা শেয়ার করবেন, আগ্রহীরা সেটার সমাধান শেয়ার করবেন, সবাই মিলে আলোচনা করবো সে সব সমাধান নিয়ে।
সমস্যা:
একটা অনলাইন পরীক্ষা নেয়ার সিস্টেম ডিজাইন করব আমরা। এই সিস্টেমে যা যা করা যাবে : ১। পরীক্ষা শুরুর আগে কোন এক সময়ে রেজিস্ট্রেশন করা যাবে। ২। ৩ গ্রুপের পরীক্ষা একই সাথে হবে। সবাই পরীক্ষার সময় বা তার অল্প আগে লগইন করবে। ৩। প্রতি গ্রুপে আলাদা ১৫০ টি প্রশ্ন থাকবে। প্রতি পরীক্ষার্থী দৈবচয়নে ১৫ টি প্রশ্ন পাবে। ৪। প্রশ্নগুলো হবে টেক্সট + একটি ছবি (নাও থাকতে পারে) ৫। প্রশ্নের উত্তর হবে একটি সংখ্যা। উত্তর বারবার বদলানো যাবে। ৬। ১ ঘণ্টা সময়ের পরে কোন উত্তর বদলানো যাবে না। এখন কিছু সংখ্যা। ১। পরীক্ষার্থী হবে ১ লাখ। তিন গ্রুপে এই সংখ্যার বণ্টন 2 : 5 : 3 এর কাছাকাছি। ২। পরীক্ষার সময় ১ ঘণ্টা। ফলে প্রতি প্রশ্নে গড়ে সময় ৪ মিনিট। ৩। প্রথম ৩ টি প্রশ্ন খুবই সহজ হবে, ফলে প্রথমেই অনেকে উত্তর করবে খুব দ্রুত। এখন যেই প্রশ্নের উত্তর আপনি দিবেন: ১। এপ্লিকেশনের কী কী API রাখবেন? ২। ডেটাবেজ কেমন হবে? মানে কী ধরণের ডেটাবেজ, শার্ডিং হবে কী না, শার্ডিং হলে কিসের ভিত্তিতে, কয়টি ডেটাবেজ সার্ভার হবে , ইত্যাদি। ৩। এপ্লিকেশন সার্ভার কেমন হবে? মানে লোডব্যালেন্সিং কিভাবে করবেন, ক্যাশিং করা দরকার কিনা, ক্যাশিং করলে কিভাবে, ইত্যাদি।
এরকম সিস্টেম আমি এখন ডিজাইন করলে যে এপ্রচে আগাতাম, যে টুল গুলো বেছে নিতাম তা নিয়ে আলোচনা করবো।
১। এপ্লিকেশনের API: ধরে নিচ্ছি এখানে আমরা এপ্লিকেশন সার্ভারের HTTP API এর কথা বলছি। রেজিস্ট্রেশন-লগইন-প্রোফাইল-এডমিনিস্ট্রেশন এসব ফাংশনালিটি গুলো হিসাব থেকে বাইরে রাখলে এই API গুলো দরকার হবে:
- প্রত্যেক ইউজারের প্রশ্নের লিস্ট
- প্রতি প্রশ্নের বিস্তারিত
- প্রতি প্রশ্নের উত্তর দেয়ার জন্য
- প্রতি প্রশ্নের উত্তর আপডেট করার জন্য
- প্রতি প্রশ্নের উত্তর দেখার জন্য
ধরে নিচ্ছি পরীক্ষা শেষে সার্বিক উত্তর মেইল করে বা প্রোফাইল পেজে জানানো হবে।
২। ডেটাবেজ:
সব চেয়ে স্পাইক ওয়ালা সময়ের কিছু হিসাব করে নেয়া যাক।
১ লাখ পরীক্ষার্থী পরীক্ষা শুরুর দিকে প্রায় একই সময়ে লগইন করবে। ধরে নেই ১ মিনিটে সবাই লগইন করবে। তারমানে ওই সময় গড়ে প্রতি সেকেন্ডে লগইনের জন্য ডেটাবেজে কুয়েরি চলবে (১০০০০ / ৬০) = ১৬৬৬.৬৭ টি। ধরে নেই ১৭০০ কুয়েরি পার সেকেন্ড। একদম সাদাসিদে কুয়েরি, জয়েন টয়েন নাই।
প্রতিটা প্রশ্নের উত্তর দেয়ার জন্য সময় ৪ মিনিট, কিন্তু প্রথম তিনটা প্রশ্ন খুবই সহজ হবে। ধরে নেই প্রথম ৩ টা প্রশ্ন সবাই ২ মিনিটের মধ্যে সমাধান করে ফেলবে। তারমানে গড়ে প্রতি সেকেন্ডে ইনসার্শন হবে {(১০০০০০ x ৩) / (২ x ৬০)} = ২৫০০। যেহেতু খুবই সহজ প্রশ্ন তাই ধরে নিচ্ছি উত্তর বদলানোর হার ইগনোর করা মত।
শুরুর লগইনের সময় এ ইনসার্শন স্পাইক হবে না। আবার এই ইনসার্শনের সময়েও লগইন স্পাইক হবে না।
তারমানে আমাদের এমন একটা ডেটাবেজ লাগবে যেটা অন্তত ১৭০০ রিড কুয়েরি পার সেকেন্ড হ্যান্ডেল করতে পারে এবং ২৫০০ ইনসার্শন পার সেকেন্ড হ্যান্ডেল করতে পারে। ভাল কনকারেন্ট কানেকশন হ্যান্ডেল করতেও পারা লাগবে।
এসব বিবেচনায় আমি Postgres বেছে নিব। পোস্টগ্রেসের সিংগেল ইন্সট্যান্সের এর চেয়ে ঢের স্পাইক হ্যান্ডেল করার কেস স্টাডি/বেঞ্চমার্ক রেজাল্ট আছে।
সুতরাং কোন শার্ডিং ছাড়া একটা ডেটাবেজ সার্ভার।
হাই এভেইলিবিলিটি এনশিওর করার জন্য আরেকটা রেপ্লিকা নোড রাখা যেতে পারে স্ট্যান্ডবাই হিসেবে। যদি প্রাইমারি/মাস্টার সার্ভার ফেইল করে, তাহলে স্ট্যান্ডবাই সার্ভারটা অপারেশনাল হয়ে মাস্টারকে রিপ্লেস করবে।
৩। মেসেজ কিউ:
আমাদের ডেটাবেজ সার্ভারের স্পাইক আমরা ক্রুশিয়াল টাইমে এভারেজ করে হিসাব করেছি। কিন্তু হঠাৎ করেই যদি কিছু সময়ের জন্য স্পাইক বেড়ে যায়? ডেটাবেজ ইন্সট্যান্স যদি আনরিচেবল হয়ে যায় কিছু সময়ের জন্য?
ডেটাবেজের কাছে আমাদের রিকুয়েস্টের ধরন রাইট হ্যাভি, রিড গুলোতে ক্যাশিং সাহায্য করবে আমাদের। রিলায়েবিলিটি এনশিওর করার জন্য আমরা সব গুলো রাইট মেসেজ কিউ এর মাধ্যমে করবো। এপ্লিকেশন সার্ভারের কাছে যখনই কোন রাইট রিকুয়েস্ট আসবে, সেটা সরাসরি ডেটাবেজে ইনসার্ট না করে আমরা একটা কিউতে মেসজ পাবলিশ করবো। কিউ থেকে সে মেসেজ পড়ে পড়ে সময় সুযোগ মত ডেটাবেজে রাইট করবো।
তো আমাদের এভারেজ হিসাবের সময় আমাদের যেরকম রাইট রিকুয়েস্টের হার বের করেছি, আমাদের কিউকে অন্তত এর চারগুণ মেসেজ পার সেকেন্ড ডেলিভারি করতে পারতে হবে। আমি এখানে RabbitMQ চুজ করবো। এর সিংগেল ইন্সট্যান্সের ৪০ হাজার মেসেজ পার সেকেন্ডে ডেলিভারি করার রেকর্ড আছে। ৩০ নোডের ক্লাস্টার সেটআপে ১ মিলিয়ন রিকুয়েস্ট পার সেকেন্ডের কেস স্টাডিও আছে।
যদিও আমাদের রিকুয়ারমেন্ট একটা ইন্সট্যান্সই হ্যান্ডেল করতে পারবে, কিন্তু হাই এভেইলিবিলিটি এনশিওর করতে আমাদের অন্তত ২ নোডের একটা ক্লাস্টার নেয়া উচিৎ। এতে আমরা একটা সিংগেল পয়েন্ট অব ফেইলর এভোয়েড করতে পারবো।
৪। ক্যাশিং:
যে রিড রিকুয়েস্টের ডেটা গুলো ইন-মেমরিতে ক্যাশ করা সম্ভব, সেগুলো আমরা পারতপক্ষে ডেটাবেজ থেকে সার্ভই করবো না। কারণ ইন-মেমরি ক্যাশ সাধরণত ডেটাবেজ কুয়েরির চেয়ে কয়েক গুণ ফাস্ট হয়।
১ লক্ষ পরীক্ষার্থী প্রায় একই সময়ে প্রথমে তাদের প্রশ্নের লিস্ট গুলো দেখতে চাইবে। ধরে নেই প্রথম ১০ সেকেন্ডে প্রত্যেকে তাদের প্রশ্নের লিস্ট দেখার জন্য চেষ্টা করবে। তারমানে সেকেন্ডে ১০ হাজার রিড রিকুয়েস্ট।
প্রত্যেক পরীক্ষার্থীর জন্য নিশ্চই পরীক্ষা শুরু হওয়ার কিছুটা সময় আগেই দৈবচয়নে প্রশ্ন নির্ধারিত হবে। ঠিক তখনই প্রত্যেকের জন্য প্রতিটা প্রশ্ন আর প্রশ্নের লিস্ট আমরা ক্যাশ করে ফেলব, যেহেতু প্রশ্ন আর কম্বিনেশন আর চেঞ্জ হবার কোন চান্স নাই।
এজন্য আমরা একটা ইন-মেমরি কি-ভ্যালু স্টোরেজ ব্যাবহার করবো। Redis। রেডিসের একটা সিংগেল ইন্সট্যান্সের ৫ লাখ রিড রিকুয়েস্ট পার সেকেন্ড সার্ভ করার মত রেকর্ডও আছে।
যদিও একটা ইন্সট্যান্সেই হয়ে যাওয়ার কথা, কিন্তু ওইযে! এভেইলিবিলিটি! ফেইলওভার! রেডিসের জন্যও আমরা অন্তত ২ নোডের একটা ক্লাস্টার ব্যাবহার করবো।
৫। এপ্লিকেশন সার্ভার:
ক্যাশিং সেকশনের হিসাব মতই ক্রিটিকাল সময়ে আমাদের এপ্লিকেশন সার্ভারের ক্ষেত্রেও ১০ হাজার রিকুয়েস্ট পার সেকেন্ডের মত হ্যান্ডেল করতে হবে।
এখন আমাদের এপ্লিকেশন সার্ভার এমন ল্যাঙ্গুয়েজ/ফ্রেমওয়ার্ক ব্যাবাহার করে বানানো উচিৎ যাতে এই ভলিউমের থ্রোপুট সহজে হ্যান্ডেল করতে পারে। গোল্যাং এক্ষেত্রে কার্যকরি হতে পারে। অনেক জায়গায়ই বেঞ্চমার্ক দেখা যায় যে গো তে লেখা সার্ভার সেকেন্ডে লাখ রিকুয়েস্ট সার্ভ করছে। কিন্তু অনেক কিছুই নির্ভর করছে আমাদের এপ্লিকেশনের ধরনের উপরে। পূর্বের অভিজ্ঞতা থেকে মনে হচ্ছে আমাদের এপ্লিকেশনের ধরন অনুযায়ী, যদি I/O বটলনেক না হয়, তাহলে হয়ত আমরা ৫ হাজার রিকুয়েস্ট পার সেকেন্ড সহজেই হ্যান্ডেল করতে পারবো।
তাহলে আমাদের এখানে একটা লোড ব্যালেন্সার দরকার। যদি একটা লোড ব্যালেন্সারের পেছনে ৩ টা এপ্লিকেশন ইন্সট্যান্স রাখি, রাউন্ড রবিন উপায়ে লোড ডিস্ট্রিবিউট করি, তাহলে আমরা সেকেন্ডে ১৫ হাজার রিকুয়েস্ট সার্ভ করতে পারবো। আর অটো স্কেলিং স্ট্র্যাটেজিও ইন প্লেস রাখা যেতে পারে, তবে এটা অতটা জরুরি মনে হচ্ছে না কারণ আমাদের ইউজারবেজ, ইউসেজের ধরন হুট করে পাল্টে যাবার সম্ভাবনা নেই।
আমাদের এপ্লিকেশন সার্ভার রেস্টফুল হবার দরুন প্রতিটা রিকুয়েস্ট হবে স্টেটলেস, আবার ডায়নামিকালি স্কেল আপ করার সম্ভাবনাও প্রায় নেই, তাই এখানে কন্সিস্টেন্ট হ্যাশিং এরও প্রয়োজন নেই।
এখানে যে সমাধান নিয়ে আলোচনা করেছি, পুরোটাই থিওরিটকাল, সেটাও আবার অনেক কিছুকে ইগনোর করে, যেমন নেটওয়ার্ক, কনকারেন্ট কানেকশন ইত্যাদি। প্র্যাক্টিকালি আরও অনেক ভ্যারিয়েবল চলে আসবে। যেমন আমরা রেডিস ব্যাবহার কারার জন্য সার্ভার এপ্লিকেশনে একটা লাইব্রেরি ব্যাবহার করবো। রেডিস যদিও সেকেন্ডে ৫ লাখ রিড হ্যান্ডেল করতে পারে, কিন্তু দেখা যেতে পারে ওই লাইব্রেরি হয়ত ৫ শতর বেশি হ্যান্ডেল করতে পারছে না। আবার এপ্লিকেশন সার্ভার সেকেন্ডে ৫ হাজার রিকুয়েস্ট সার্ভ করতে পারবে বলে ধরে নিয়েছি, দেখা যতে পারে ১ হাজার করতেই জান কাহিল।
এই প্রবলেম যদি বস্তবিক ভাবে সল্ভ করতে হত, আমি উপরে উল্লেখিত এজাম্পশন গুলো নিয়ে একটা POC করতাম, সেটায় বেঞ্চমার্ক চালিয়ে এজাম্পশন গুলো ভ্যালিডেট আর ফাইন টিউন করতাম।