একটি API যা স্টেজিং এনভায়রনমেন্টে খুব সহজেই চলে যেতে পারে, আসল ট্রাফিক আসার সাথে সাথেই তা ভেঙে পড়তে পারে। এই ব্যর্থতা খুব একটা নাটকীয় হয় না; এটি মূলত হাজারো ছোটখাটো সমস্যার সমষ্টিগত প্রভাব। এখানে একটি ব্লকড থ্রেড, সেখানে একটি অতিরিক্ত কুয়েরি। হালকা লোডের সময়, এই ছোটখাটো অদক্ষতাগুলো লুকিয়ে থাকে। প্রোডাকশন প্রেশারের সময়, এগুলো থ্রেড পুল স্টারভেশন (thread pool starvation), ডাটাবেস চোকিং (database choking) এবং রেসপন্স টাইম বাড়িয়ে দেয়, যা ব্যবহারকারীর আস্থা নষ্ট করে। পারফরম্যান্স টিউনিং মানে কোনো একটি জাদুকরী সমাধান খুঁজে বের করা নয়। এটি এমন একটি সিস্টেম তৈরি করা সম্পর্কে যেখানে প্রতিটি লেয়ার থ্রেড, মেমরি এবং ডাটাবেস কানেকশনের সীমাবদ্ধতাকে সম্মান করে। যখন আপনি এই রিসোর্সগুলোকে সীমিত হিসেবে বিবেচনা করবেন, তখন আপনি কেবল আউটটেজ (outage) সামাল দেওয়ার বদলে সেগুলো প্রতিরোধ করতে শুরু করবেন।

Sync-over-Async আপনার সিস্টেমকে ধ্বংস করার আগেই তা নির্মূল করুন

হাই-ট্রাফিক ASP.NET Core অ্যাপ্লিকেশনের ক্ষেত্রে সবচেয়ে ধ্বংসাত্মক প্যাটার্ন হলো sync-over-async। আপনি এটি তখন দেখতে পান যখন কেউ কোনো asynchronous মেথডের ওপর .Result বা .Wait() কল করে, কারণ তারা তাৎক্ষণিকভাবে ভ্যালুটি চায় এবং কল স্ট্যাক রিফ্যাক্টর করতে চায় না। সেই সিদ্ধান্তটি কলিং থ্রেডকে ব্লক করে দেয়। থ্রেডটি অলসভাবে বসে থাকে অন্য কোথাও চলমান কাজের জন্য অপেক্ষা করতে করতে, কিন্তু রানটাইম এটিকে অন্য কোনো রিকোয়েস্টের জন্য পুনরায় ব্যবহার করতে পারে না।

যখন যথেষ্ট পরিমাণ রিকোয়েস্ট এমনটি করে, তখন থ্রেড পুল স্টার্ভেশন ঘটে। আপনার CPU গ্রাফ দেখতে সুস্থ মনে হতে পারে কারণ প্রসেসরগুলো ব্যস্ত নেই, তবুও আপনার ল্যাটেন্সি (latency) বহুগুণ বেড়ে যায়। রিকোয়েস্টগুলো কিউতে (queue) জমা হতে থাকে, এমন থ্রেডের জন্য অপেক্ষা করতে থাকে যা কখনোই খালি হবে না। এর সমাধান যান্ত্রিক কিন্তু এর জন্য শৃঙ্খলা প্রয়োজন: কল স্ট্যাকের শুরু থেকে শেষ পর্যন্ত await ব্যবহার করুন। যদি একটি মেথড কোনো async API কল করে, তবে সেই মেথডটিকে অবশ্যই async হতে হবে। এখানে কোনো শর্টকাট নেই। প্রতিটি সিনক্রোনাস ব্লকেজ যা আপনি দূর করবেন, তা আসল ট্রাফিকের জন্য অতিরিক্ত জায়গা (headroom) তৈরি করে দেবে।

ডিসকানেক্টেড ক্লায়েন্টের জন্য কাজ অপচয় করা বন্ধ করুন

ক্লায়েন্টরা ডিসকানেক্ট হয়ে যায়। ব্রাউজার ট্যাব বন্ধ করে দেয়। মোবাইল অ্যাপ সিগন্যাল হারায়। যদি আপনার সার্ভার না জানে যে ক্লায়েন্ট চলে গেছে, তবে এটি ডাটাবেস কুয়েরি চালানো, JSON পার্স করা এবং এমন একটি রেসপন্সের জন্য থ্রেড খরচ করতে থাকে যা কেউ পাবে না। প্রতিটি async অপারেশনে, যা এটি সাপোর্ট করে, একটি CancellationToken পাস করুন। এর মানে হলো Entity Framework কুয়েরি, HttpClient দিয়ে করা HTTP কল এবং যেকোনো দীর্ঘস্থায়ী ব্যাকগ্রাউন্ড কাজ।

যখন কানেকশন বিচ্ছিন্ন হয়ে যায়, তখন টোকেনটি ক্যানসেলেশন ট্রিগার করে এবং কাজ সাথে সাথে বন্ধ হয়ে যায়। এটি ডাটাবেস CPU সাইকেল বাঁচায় এবং থ্রেডগুলোকে দ্রুত পুলের কাছে ফিরিয়ে দেয়। মেথড সিগনেচারে এটি একটি ছোট পরিবর্তন যা লোডের সময় অনেক বড় সুবিধা দেয়।

যা পরিবর্তন হয় না তা ক্যাশ (Cache) করে রাখুন

আপনার প্রোডাক্ট ক্যাটালগ সম্ভবত প্রতিটি রিকোয়েস্টের মাঝে পরিবর্তন হয় না। আপনার কনফিগারেশন ফ্ল্যাগগুলো তো নিশ্চিতভাবেই পরিবর্তন হয় না। তবুও অনেক API একই স্ট্যাটিক ডেটার জন্য বারবার ডাটাবেসে হিট করে। ASP.NET Core-এর আউটপুট ক্যাশিং আপনাকে রেন্ডার করা রেসপন্সগুলো স্টোর করতে দেয় এবং আপনার কন্ট্রোলার বা ডাটাবেসকে পুনরায় স্পর্শ না করেই সরাসরি মেমরি থেকে সেগুলো প্রদান করতে দেয়।

ট্যাগ-ভিত্তিক ইনভ্যালিডেশন (tag-based invalidation) সাবধানে ব্যবহার করুন। যখন আপনি কোনো প্রোডাক্ট আপডেট করবেন, তখন শুধুমাত্র সেই ক্যাটাগরি বা আইটেমের সাথে যুক্ত ট্যাগটি ইনভ্যালিড করুন। আপনাকে পুরো ক্যাশ ফ্লাশ করার প্রয়োজন নেই। এটি আপনার ক্যাশ হিট রেশিও (cache hit ratio) উচ্চ এবং ডাটাবেস কুয়েরি সংখ্যা কম রাখতে সাহায্য করবে।

প্রথমে আপনার ডাটা অ্যাক্সেস ঠিক করুন

বেশিরভাগ API-তে ডাটাবেস কল রিকোয়েস্ট সময়ের সিংহভাগ দখল করে থাকে। অন্য কিছু অপ্টিমাইজ করার আগে, এখানে নজর দিন।

আপনি যদি ডেটা শুধুমাত্র দেখানোর জন্য কুয়েরি করেন এবং সেটি আপডেট করার কোনো পরিকল্পনা না থাকে, তবে আপনার Entity Framework কুয়েরির সাথে AsNoTracking() যুক্ত করুন। EF Core এতে চেঞ্জ ট্র্যাকিং (change tracking) এবং স্ন্যাপশট তৈরি করা এড়িয়ে যায়।