एक API जो स्टेजिंग में सुचारू रूप से चलती है, वास्तविक ट्रैफिक आते ही ढह सकती है। विफलता शायद ही कभी नाटकीय होती है। यह हज़ारों छोटी-छोटी कमियों से होने वाली बर्बादी है। यहाँ एक ब्लॉक थ्रेड, वहाँ एक अनावश्यक क्वेरी। हल्के लोड के तहत, ये छोटी अक्षमताएँ छिप जाती हैं। लेकिन प्रोडक्शन के दबाव में, वे थ्रेड पूल स्टार्वेशन (thread pool starvation), डेटाबेस चोकिंग (database choking), और ऐसी लेटेंसी (latency) में बदल जाती हैं जो यूजर का भरोसा तोड़ देती हैं। परफॉरमेंस ट्यूनिंग का मतलब कोई एक जादुई समाधान ढूँढना नहीं है। इसका मतलब एक ऐसा सिस्टम बनाना है जहाँ हर लेयर थ्रेड्स, मेमोरी और डेटाबेस कनेक्शन की कमी का सम्मान करे। जब आप इन संसाधनों को सीमित मानते हैं, तो आप आउटेज (outages) पर प्रतिक्रिया देना बंद कर देते हैं और उन्हें रोकना शुरू कर देते हैं।
Sync-over-Async को खत्म करें, इससे पहले कि यह आपको खत्म कर दे
हाई-ट्रैफिक ASP.NET Core एप्लिकेशन में सबसे विनाशकारी पैटर्न sync-over-async है। आप इसे तब देखते हैं जब कोई एसिंक्रोनस (asynchronous) मेथड पर .Result या .Wait() कॉल करता है क्योंकि उन्हें तुरंत वैल्यू चाहिए और वे कॉल स्टैक को रिफैक्टर नहीं करना चाहते। वह निर्णय कॉलिंग थ्रेड को ब्लॉक कर देता है। थ्रेड खाली बैठा रहता है, उस काम का इंतज़ार करता है जो पहले से ही कहीं और हो रहा है, लेकिन रनटाइम उसे किसी अन्य रिक्वेस्ट के लिए दोबारा उपयोग नहीं कर पाता।
जब पर्याप्त रिक्वेस्ट ऐसा करती हैं, तो थ्रेड पूल स्टार्वेशन का शिकार हो जाता है। आपका CPU ग्राफ स्वस्थ दिखता है क्योंकि प्रोसेसर व्यस्त नहीं हैं, फिर भी आपकी लेटेंसी अचानक बढ़ जाती है। रिक्वेस्ट कतार (queue) में लग जाती हैं, उन थ्रेड्स का इंतज़ार करती हैं जो कभी खाली नहीं होंगे। इसका समाधान यांत्रिक है लेकिन इसके लिए अनुशासन की आवश्यकता है: कॉल स्टैक में ऊपर तक await का उपयोग करें। यदि कोई मेथड एक async API को कॉल करता है, तो उसे स्वयं भी async होना चाहिए। यहाँ कोई शॉर्टकट नहीं है। आपके द्वारा हटाया गया हर सिंक्रोनस ब्लॉकेज वास्तविक ट्रैफिक के लिए जगह (headroom) बनाता है।
डिस्कनेक्टेड क्लाइंट्स पर काम बर्बाद करना बंद करें
क्लाइंट डिस्कनेक्ट हो जाते हैं। ब्राउज़र टैब बंद कर देते हैं। मोबाइल ऐप्स सिग्नल खो देते हैं। यदि आपके सर्वर को यह नहीं पता कि क्लाइंट चला गया है, तो वह डेटाबेस क्वेरी चलाता रहता है, JSON पार्स करता रहता है, और उस रिस्पॉन्स के लिए थ्रेड्स बर्बाद करता है जिसे कोई प्राप्त नहीं करेगा। हर उस async ऑपरेशन में CancellationToken पास करें जो इसका समर्थन करता है। इसका मतलब है Entity Framework क्वेरीज़, HttpClient के साथ HTTP कॉल्स, और कोई भी लंबे समय तक चलने वाला बैकग्राउंड काम।
जब कनेक्शन टूटता है, तो टोकन कैंसलेशन को ट्रिगर करता है, और काम तुरंत रुक जाता है। यह डेटाबेस CPU साइकिल को बचाता है और थ्रेड्स को तेज़ी से पूल में वापस भेजता है। यह मेथड सिग्नेचर में एक छोटा सा बदलाव है जो लोड के दौरान बहुत काम आता है।
जो चीज़ें नहीं बदलतीं, उन्हें कैश (Cache) करें
आपका प्रोडक्ट कैटलॉग शायद हर रिक्वेस्ट के बीच नहीं बदलता है। आपके कॉन्फ़िगरेशन फ्लैग्स तो निश्चित रूप से नहीं बदलते। फिर भी कई APIs एक ही स्टैटिक डेटा के लिए बार-बार डेटाबेस हिट करती हैं। ASP.NET Core में आउटपुट कैशिंग आपको रेंडर किए गए रिस्पॉन्स को स्टोर करने और अपने कंट्रोलर्स या डेटाबेस को दोबारा छुए बिना सीधे मेमोरी से उन्हें सर्व करने की अनुमति देता है।
टैग-आधारित इनवैलिडेशन (tag-based invalidation) का सावधानी से उपयोग करें। जब आप किसी प्रोडक्ट को अपडेट करते हैं, तो केवल उस कैटेगरी या आइटम से जुड़े टैग को इनवैलिडेट करें। आपको पूरी कैश को फ्लश करने की आवश्यकता नहीं है। यह आपके कैश हिट रेशियो को उच्च और डेटाबेस क्वेरी काउंट को कम रखता है।
सबसे पहले अपने डेटा एक्सेस (Data Access) को ठीक करें
अधिकांश APIs में डेटाबेस कॉल्स रिक्वेस्ट के समय का अधिकांश हिस्सा लेती हैं। किसी भी अन्य चीज़ को ऑप्टिमाइज़ करने से पहले, यहाँ देखें।
यदि आप डेटा को केवल प्रदर्शित करने के लिए क्वेरी कर रहे हैं और उसे अपडेट करने की योजना नहीं बना रहे हैं, तो अपनी Entity Framework क्वेरीज़ में AsNoTracking() जोड़ें। EF Core चेंज ट्रैकिंग और स्नैपशॉट क्रिएशन को छोड़ देता है।
