রাত তিনটে নাগাদ আপনার ডাটাবেস CPU ১০০% এ পৌঁছে যাচ্ছে। ট্রাফিক স্বাভাবিক দেখাচ্ছে। রিকোয়েস্টের সংখ্যা অস্বাভাবিকভাবে বেশি নয়। তবুও আপনার প্রাইমারি স্টোরটি অচল হয়ে পড়ছে কারণ প্রতিটি রিকোয়েস্ট ঠিক একই সময়ে ঠিক একই জিনিস চাইছে।
এটিই হলো thundering herd problem।
একটি কনসার্টের পরের স্টেডিয়ামের কথা ভাবুন। এক ঘণ্টার ব্যবধানে একই প্রস্থানের দরজা দিয়ে সবাই স্বাচ্ছন্দ্যে বেরিয়ে যেতে পারে। কিন্তু যদি পুরো ভিড় মাত্র দশ সেকেন্ডের মধ্যে সেই একটি দরজা দিয়ে বেরিয়ে যাওয়ার সিদ্ধান্ত নেয়, তবে দরজাটি ভেঙে যাবে না। এটি কেবল সেই concurrency সামলাতে পারে না। সমস্যাটি ভিড়ের চাপে, দরজায় নয়।
যেখানে এই ভিড় তৈরি হয়
যখন অনেকগুলো প্রসেস একটি নির্দিষ্ট ইভেন্টের ওপর synchronize হয়, তখনই এই প্যাটার্নটি দেখা দেয়। এর জন্য ক্ষতিকারক ট্রাফিকের প্রয়োজন নেই। সাধারণ সিস্টেমগুলো নিজেরাই নিজেদের সাথে এমনটা করে ফেলে।
Cache expiration. একটি হট ক্যাশ কী (hot cache key) মেয়াদ হারিয়ে ফেলে। এটি কোনো প্রোডাক্ট ক্যাটালগ, ফিচার ফ্ল্যাগ বা ইউজারের পারমিশন লিস্ট হতে পারে। হাজার হাজার অ্যাপ্লিকেশন সার্ভার একই সাথে সেই খালি স্লটটি লক্ষ্য করে। প্রতিটি সার্ভার, ভালো উদ্দেশ্যেই, নিজস্ব ডাটাবেস কানেকশন খোলে এবং ভ্যালুটি পুনরায় তৈরি করতে একই ভারী কুয়েরি চালায়। ডাটাবেসটি কখনোই সমান্তরালভাবে (in parallel) একই দামী প্রশ্নের দুই হাজার বার উত্তর দেওয়ার জন্য কনফিগার করা হয়নি। একটি এক্সপায়ার হওয়া কী পুরো ক্লাস্টারকে অচল করে দিতে পারে।
Connection pool wakeups. কিছু আর্কিটেকচারে, অনেকগুলো ওয়ার্কার প্রসেস একটি নির্দিষ্ট শর্তের ওপর ব্লক হয়ে থাকে এবং কাজের জন্য অপেক্ষা করে। যখন সেই শর্তটি পূরণ হয়, অপারেটিং সিস্টেম প্রতিটি ঘুমন্ত ওয়ার্কারকে জাগিয়ে তোলে। কিন্তু আসলে মাত্র একটি ওয়ার্কার কানেকশন বা কাজটি পায়। বাকিরা জেগে ওঠে, দেখে যে তারা প্রতিযোগিতায় হেরে গেছে এবং আবার ঘুমিয়ে পড়ে। এই চক্রটি context switches-এর মাধ্যমে CPU খরচ করে ফেলে এবং প্রকৃত উৎপাদনশীল কাজকে বাধাগ্রস্ত করতে পারে।
Retry storms. একটি ডাউনস্ট্রিম সার্ভিস সাময়িকভাবে কাজ করা বন্ধ করে দেয়। প্রতিটি ক্লায়েন্ট টাইমআউটটি শনাক্ত করে এবং পুনরায় চেষ্টা করার আগে একটি নির্দিষ্ট সময়, ধরুন ঠিক এক সেকেন্ড, অপেক্ষা করে। যখন সার্ভিসটি আবার সচল হয় এবং ট্রাফিক গ্রহণ করতে শুরু করে, তখন প্রতিটি ক্লায়েন্ট একই মুহূর্তে সেটিকে হিট করে। সার্ভিসটি তখনও পুরোপুরি সচল না হওয়ায় আবারও ডাউন হয়ে যায়। এই চক্রটি বারবার চলতে থাকে।
Cron collisions. আপনি যদি একটি ফ্লিট ইন্সট্যান্সের (fleet of instances) মধ্যে রক্ষণাবেক্ষণ, রিপোর্ট জেনারেশন বা ক্যাশ ওয়ার্মিং ঠিক 00:00 UTC সময়ে চালানোর জন্য শিডিউল করেন, তবে আপনি ঘড়ির কাঁটার নিখুঁততা অনুযায়ী একটি ট্রাফিক স্পাইক তৈরি করছেন। এই লোডটি অনুমানযোগ্য, কিন্তু এর ঘনত্ব মারাত্মক।
Request Coalescing
ক্যাশ স্ট্যাম্পিড (cache stampedes) সমাধানের সবচেয়ে কার্যকর উপায় হলো একই প্রশ্নের উত্তর একাধিকবার দেওয়া বন্ধ করা। যখন একটি cache miss ঘটে, আপনি চান না যে ২,৫০০টি থ্রেড আলাদা আলাদাভাবে ডাটাবেস কুয়েরি চালাবে। আপনি চান একটি থ্রেড কুয়েরিটি চালাবে এবং বাকি ২,৪৯৯টি থ্রেড সেই ফলাফলের জন্য অপেক্ষা করবে।
এই প্যাটার্নটিকে প্রায়ই request coalescing বলা হয়। Go-তে, golang.org/x/sync-এর singleflight প্যাকেজটি এর একটি আদর্শ ইমপ্লিমেন্টেশন প্রদান করে। একটি নির্দিষ্ট কী-এর জন্য প্রথম যে goroutine Do কল করে, সেটি কাজ শুরু করে। একই কী ব্যবহারকারী পরবর্তী কলগুলো সেই একই Do কলের জন্য ব্লক হয়ে থাকে। কাজ শেষ হলে, অপেক্ষমান সবাই একসাথে ফলাফল পেয়ে যায়। আপনি মাত্র একটি কুয়েরি চালিয়ে ২,৫০০টি রিকোয়েস্ট সম্পন্ন করেছেন।
আপনি প্রমিজ (promises), ফিউচার (futures) বা চ্যানেলের (channels) একটি ইন-মেমরি কনকারেন্ট ম্যাপ ব্যবহার করে একই ধরনের আচরণ তৈরি করতে পারেন। আসল কৌশলটি হলো একটি in-flight রিকোয়েস্ট atomically চেক করা এবং রেজিস্টার করা। যদি রিকোয়েস্টটি ব্যর্থ হয়, তবে সবাই এরর পাবে, যা সাধারণত সঠিক আচরণ। যদি এটি সফল হয়, তবে সবাই ক্যাশ করা ভ্যালু পাবে এবং পরবর্তী রিকোয়েস্টগুলো সরাসরি ক্যাশ থেকে ডেটা নিয়ে নেবে। এর ফলে ডাটাবেসের লোড একটি বিশাল স্পাইক থেকে কমে একটি সমান্তরাল রেখায় নেমে আসে।
Probabilistic Early Expiration
কখনও কখনও আপনি স্ট্যাম্পিড ম্যানেজ করার পরিবর্তে এটি পুরোপুরি এড়িয়ে যেতে চান। এখানে কাজ করে probabilistic early expiration, যা XFetch-এর মতো অ্যালগরিদমের মাধ্যমে বাস্তবায়ন করা হয়।
একটি হার্ড (hard) এক্সপায়ারেশনের সময়ের পরিবর্তে, প্রতিটি ক্যাশ করা ভ্যালুর একটি সফট (soft) এক্সপায়ারেশন উইন্ডো থাকে। যখন একটি রিকোয়েস্ট আসে, অ্যাপ্লিকেশনটি একটি র্যান্ডম নম্বর তৈরি করে এবং একটি সাধারণ প্রবাবিলিটি চেক প্রয়োগ করে। যদি ডাইস রোল (dice roll) 'হ্যাঁ' বলে, তবে সেই একটি রিকোয়েস্ট ক্যাশটি আগেভাগেই রিফ্রেশ করে দেয়। যদি না হয়, তবে রিকোয়েস্টটি সামান্য পুরনো (stale) ভ্যালুটি প্রদান করে।
যেহেতু প্রতিটি রিকোয়েস্ট নিজস্ব ডাইস রোল করে, তাই রিফ্রেশগুলো পুরো সফট উইন্ডো জুড়ে ছড়িয়ে পড়ে। একজন ক্লায়েন্ট হার্ড এক্সপায়ারেশনের চল্লিশ সেকেন্ড আগে রিফ্রেশ করতে পারে, অন্যজন বারো সেকেন্ড আগে, এবং বাকিদের বেশিরভাগই হয়তো একদমই করবে না। এর ফলে কাজগুলো একটি একক সিনক্রোনাইজড স্পাইক থেকে একটি মৃদু
