डेवलपर्स अक्सर मुझसे एक ही सवाल पूछते हैं: "मेरी API धीमी है। मुझे कहाँ से शुरुआत करनी चाहिए?" आमतौर पर पहली प्रतिक्रिया सर्वर को अपग्रेड करने या RAM को दोगुना करने की होती है। इसमें पैसा खर्च होता है और यह शायद ही कभी मूल कारण (root cause) को ठीक करता है। अधिकांश Laravel एप्लिकेशन्स में, बाधा (bottleneck) डेटाबेस लेयर में होती है। फ्रेमवर्क का शानदार सिंटैक्स यह भूलना आसान बना देता है कि प्रत्येक Eloquent कॉल अंततः SQL बन जाती है, और अक्सर SQL ही वह जगह है जहाँ समस्याएँ शुरू होती हैं।

सर्वर सेटिंग को छूने से पहले, अपनी क्वेरीज़ (queries) पर व्यवस्थित तरीके से काम करें।

सही डायग्नोस्टिक (Diagnostic) से शुरुआत करें

अंधेरे में ऑप्टिमाइज़ न करें। क्वेरीज़ को बेतरतीब ढंग से फिर से लिखना केवल एक अनुमान है, और अनुमान लगाने में घंटों बर्बाद होते हैं।

आपको उन स्टेटमेंट्स (statements) को ढूँढना होगा जो कुल मिलाकर सबसे अधिक समय लेते हैं। वास्तविक लोड के तहत अपने एप्लिकेशन को देखें। Laravel Telescope आपको एक रिक्वेस्ट के दौरान निष्पादित (executed) प्रत्येक क्वेरी का समय के साथ एक स्पष्ट दृश्य प्रदान करता है। Laravel Debugbar लोकल डेवलपमेंट के दौरान उन्हें आपके ब्राउज़र में दिखाता है ताकि आप विसंगतियों (anomalies) को तुरंत पहचान सकें। जब आपको प्रोडक्शन में समस्याओं को पकड़ने की आवश्यकता हो, तो MySQL Slow Query Log को सक्षम करें। यह उन स्टेटमेंट्स को रिकॉर्ड करता है जो आपके द्वारा निर्धारित थ्रेशोल्ड (threshold) से अधिक होते हैं, जो इसे उन आश्चर्यों को खोजने के लिए आदर्श बनाता है जो छोटे डेटासेट में दिखाई नहीं देते हैं। यदि आप कुछ बड़ा चला रहे हैं, तो एक Application Performance Monitoring टूल धीमे HTTP एंडपॉइंट्स को विशिष्ट डेटाबेस कॉल्स के साथ जोड़ सकता है।

जैसे-जैसे आप डेटा की समीक्षा करते हैं, दो चीज़ों पर ध्यान दें: पूर्ण निष्पादन समय (absolute execution time) और कॉल की आवृत्ति (call frequency)। चालीस मिलीसेकंड लेने वाली क्वेरी हानिरहित लगती है जब तक कि आपको यह एहसास न हो जाए कि यह प्रति मिनट दो हज़ार बार चलती है। प्रति घंटे एक बार चलने वाली तीन सेकंड की रिपोर्ट, हर पेज पर चलने वाले आधे सेकंड के लुकअप (lookup) से कम महत्वपूर्ण हो सकती है। पहले उच्च-प्रभाव वाली समस्याओं को ठीक करें।

सब कुछ माँगना बंद करें

SELECT * सुविधाजनक है। यह महंगा भी है। जब आप Model::all() लिखते हैं या कॉलम का नाम लिए बिना परिणाम सेट (result set) प्राप्त करते हैं, तो MySQL प्रत्येक मेल खाने वाली पंक्ति (row) के लिए हर फ़ील्ड को खींचता है। इसमें बड़े टेक्स्ट फ़ील्ड, JSON ब्लब्स और टेबल में मौजूद कोई भी अन्य चीज़ शामिल है। परिणाम सेट बढ़ता है, मेमोरी का उपयोग बढ़ता है, और रिस्पॉन्स को सीरियलाइज़ (serializing) करने में लगने वाला समय बढ़ जाता है।

स्पष्ट रहें। यदि आपके कंट्रोलर को केवल id, name, और email फ़ील्ड की आवश्यकता है, तो ठीक वही मांगें:

User::select('id', 'name', 'email')->get();

क्वेरी बिल्डर (query builder) में भी यही सिद्धांत लागू होता है। छोटे पेलोड (payloads) नेटवर्क पर तेज़ी से चलते हैं और आपके एप्लिकेशन सर्वर पर कम RAM का उपयोग करते हैं। यह उपलब्ध सबसे सस्ते और प्रभावी तरीकों में से एक है, फिर भी इसे छोड़ देना आसान है क्योंकि Laravel SELECT * को डिफ़ॉल्ट व्यवहार बनाता है।

EXPLAIN को अपने बदलावों का मार्गदर्शन करने दें

EXPLAIN चलाने से पहले कभी भी किसी धीमी क्वेरी को रिफैक्टर (refactor) न करें। MySQL में, EXPLAIN कीवर्ड क्वेरी निष्पादन योजना (execution plan) दिखाता है। यह बताता है कि ऑप्टिमाइज़र (optimizer) वास्तव में आपके डेटा को खोजने की योजना कैसे बनाता है।

type कॉलम पर ध्यान दें। यदि आप ALL देखते हैं, तो MySQL फुल टेबल स्कैन (full table scan) कर रहा है। इसका मतलब है कि यह आपके WHERE क्लॉज़ को पूरा करने के लिए हर पंक्ति को पढ़ रहा है। यह देखने के लिए कि क्या ऑप्टिमाइज़र किसी इंडेक्स का उपयोग कर रहा है, key कॉलम को देखें। फिर Extra कॉलम की जाँच करें। यदि आपको Using temporary या Using filesort दिखाई देता है, तो इसका मतलब है कि MySQL इंटरमीडिएट टेबल बना रहा है या मेमोरी में सॉर्ट कर रहा है क्योंकि आपकी वर्तमान संरचना क्वेरी को सुचारू रूप से पूरा नहीं कर सकती है।

अपने MySQL क्लाइंट में EXPLAIN चलाएँ, या ऐसे टूल का उपयोग करें जो आपके लिए आउटपुट को फॉर्मेट करता हो। एक बार जब आप योजना देख लेते हैं, तो आप जान जाते हैं कि समस्या एक गायब इंडेक्स है, एक खराब जॉइन (join) है, या एक प्रेडिकेट (predicate) है जिसे इंजन ऑप्टिमाइज़ नहीं कर सकता। अनुमान लगाना अनावश्यक हो जाता है।

इरादे के साथ इंडेक्स (Index) बनाएँ

इंडेक्स लुकअप को तेज़ करने के लिए सबसे शक्तिशाली उपकरण हैं, लेकिन वे तभी काम करते हैं जब वे आपके क्वेरी करने के तरीके से मेल खाते हैं। सही इंडेक्स के बिना, MySQL पंक्ति दर पंक्ति स्कैन करता है। विकास (development) के दौरान एक हज़ार पंक्तियों वाली टेबल पर यह ठीक लग सकता है, लेकिन प्रोडक्शन में दस मिलियन पंक्तियों वाली टेबल पर यह विफल हो सकता है।

उन फ़ील्ड्स के लिए सिंगल-कॉलम इंडेक्स से शुरुआत करें जो WHERE क्लॉज़ में बार-बार आते हैं। यदि आप लगातार status द्वारा फ़िल्टर करते हैं, तो status पर एक इंडेक्स जोड़ें।

जब कोई क्वेरी एक साथ कई कॉलम पर फ़िल्टर करती है, तो कंपोजिट इंडेक्स (composite indexes) पर जाएँ। इंडेक्स के अंदर कॉलम का क्रम मायने रखता है क्योंकि MySQL कंपोजिट इंडेक्स को बाएं से दाएं पढ़ता है। इसे 'लेफ्टमोस्ट प्रीफ़िक्स रूल' (leftmost prefix rule) कहा जाता है। यदि आपकी क्वेरी user_id द्वारा खोजती है और फिर created_at द्वारा क्रमबद्ध (order) करती है, तो (user_id, created_at) पर एक कंपोजिट इंडेक्स काफी मदद करता है। क्रम को उल्टा करने पर ऑप्टिमाइज़र फ़िल्टर के लिए इंडेक्स का उपयोग नहीं कर सकता है।

हर कॉलम को इंडेक्स न करें। प्रत्येक इंडेक्स इंसर्ट (insert), अपडेट और डिलीट में ओवरहेड (overhead) जोड़ता है क्योंकि MySQL को संरचना बनाए रखनी पड़ती है। उन्हें माप के दौरान पाए गए पैटर्न के आधार पर सोच-समझकर जोड़ें।

कॉलम से फंक्शन को दूर रखें

यह गलती चुपचाप इंडेक्स को अक्षम कर देती है। जब आप WHERE क्लॉज के अंदर किसी कॉलम को एक फंक्शन में रैप करते हैं, तो MySQL नहीं कर सकता