मेमोरी लीक पर इस दो-भाग श्रृंखला के पहले भाग में, हमने देखा कि रूबी मेमोरी कैसे प्रबंधित करता है और गार्बेज कलेक्शन (GC) कैसे काम करता है।
आप शक्तिशाली मशीनें अधिक मेमोरी के साथ खरीद सकते हैं, और आपका ऐप अक्सर पर्याप्त रीस्टार्ट हो सकता है कि आपके उपयोगकर्ता नोटिस न करें, लेकिन मेमोरी उपयोग मायने रखता है।
आवंटन और गार्बेज कलेक्शन मुफ्त नहीं हैं। यदि आपके पास लीक है, तो आप गार्बेज कलेक्शन पर अधिक से अधिक समय खर्च करते हैं बजाय उसके जो आपने अपने ऐप को करने के लिए बनाया है।
इस पोस्ट में, हम उन टूल्स में गहराई से गोता लगाएंगे जिनका उपयोग आप मेमोरी लीक को खोजने और निदान करने के लिए कर सकते हैं।
जारी रखते हैं!
रूबी में लीक ढूंढना
लीक का पता लगाना काफी सरल है। आप GC, ObjectSpace, और अपने APM टूल में RSS ग्राफ़ का उपयोग करके अपने मेमोरी उपयोग को बढ़ते हुए देख सकते हैं। लेकिन बस यह जानना कि आपके पास लीक है, इसे ठीक करने के लिए पर्याप्त नहीं है। आपको यह जानने की आवश्यकता है कि यह कहां से आ रहा है। कच्चे नंबर आपको यह नहीं बता सकते।
सौभाग्य से, रूबी इकोसिस्टम में उन नंबरों को संदर्भ जोड़ने के लिए कुछ बेहतरीन टूल्स हैं। दो हैं memory-profiler और derailed_benchmarks।
रूबी में memory_profiler
memory_profiler जेम एक बहुत ही सरल API और एक विस्तृत (हालांकि थोड़ा भारी) आवंटित और बनाए रखा मेमोरी रिपोर्ट प्रदान करता है — जिसमें आवंटित ऑब्जेक्ट्स के क्लासेस, उनका आकार, और जहां उन्हें आवंटित किया गया था, शामिल हैं। इसे हमारे लीक प्रोग्राम में जोड़ना सीधा है।
एक रिपोर्ट आउटपुट करना जो इसके समान दिखती है।
यहां बहुत सारी जानकारी है, लेकिन आम तौर पर, allocated objects by location और retained objects by location सेक्शन लीक की तलाश में सबसे उपयोगी हो सकते हैं। ये वे फाइल लोकेशन हैं जो ऑब्जेक्ट्स आवंटित करते हैं, आवंटित ऑब्जेक्ट्स की संख्या के अनुसार क्रमबद्ध।
allocatedऑब्जेक्ट्सreportब्लॉक के भीतर आवंटित (बनाए गए) सभी ऑब्जेक्ट्स हैं।retainedऑब्जेक्ट्स वे ऑब्जेक्ट्स हैं जोreportब्लॉक के अंत तक गार्बेज कलेक्ट नहीं किए गए हैं। हमने ब्लॉक के अंत से पहले एक GC रन को मजबूर किया ताकि हम लीक ऑब्जेक्ट्स को अधिक स्पष्ट रूप से देख सकें।
retained ऑब्जेक्ट काउंट्स पर भरोसा करने के बारे में सावधान रहें। वे इस बात पर बहुत निर्भर करते हैं कि लीक कोड का कौन सा हिस्सा report ब्लॉक के भीतर है।
उदाहरण के लिए, यदि हम an_array की घोषणा को report ब्लॉक में ले जाते हैं, तो हमें यह सोचकर धोखा दिया जा सकता है कि कोड लीक नहीं है।
परिणामी रिपोर्ट का शीर्ष कई रिटेन्ड ऑब्जेक्ट्स की रिपोर्ट नहीं करेगा (सिर्फ रिपोर्ट ही)।
रूबी में derailed_benchmarks
derailed_benchmarks जेम सभी प्रकार के प्रदर्शन कार्य के लिए बहुत उपयोगी टूल्स का एक सूट है, मुख्य रूप से रेल्स ऐप्स के उद्देश्य से। लीक खोजने के लिए, हम perf:mem_over_time, perf:objects, और perf:heap_diff को देखना चाहते हैं।
ये कार्य एक चल रहे ऐप पर curl रिक्वेस्ट भेजकर काम करते हैं, इसलिए हम इन्हें अपने छोटे लीक प्रोग्राम में नहीं जोड़ सकते। इसके बजाय, हमें एक लीक एंडपॉइंट के साथ एक छोटा रेल्स ऐप सेट करना होगा, फिर उस ऐप पर derailed_benchmarks इंस्टॉल करना होगा।
अब आप bin/rails s के साथ ऐप को बूट कर सकते हैं। आप एक एंडपॉइंट को curl कर सकेंगे जो प्रत्येक रिक्वेस्ट पर मेमोरी लीक करता है।
अब हम अपने लीक को कार्रवाई में देखने के लिए derailed_benchmarks का उपयोग कर सकते हैं।
perf:mem_over_time
यह हमें समय के साथ मेमोरी उपयोग दिखाएगा (इसी तरह से जैसे हमने watch और ps के साथ अपने लीक स्क्रिप्ट की मेमोरी वृद्धि देखी थी)।
Derailed प्रोडक्शन मोड में ऐप को बूट करेगा, बार-बार एक एंडपॉइंट (/ डिफ़ॉल्ट रूप से) को हिट करेगा, और मेमोरी उपयोग की रिपोर्ट करेगा। यदि यह कभी भी बढ़ना बंद नहीं करता, तो हमारे पास लीक है!
नोट: Derailed टेस्ट करने के लिए रेल्स ऐप को प्रोडक्शन मोड में बूट करेगा। डिफ़ॉल्ट रूप से, यह पहले require rails/all भी करेगा। चूंकि इस ऐप में डेटाबेस नहीं है, हमें इस व्यवहार को DERAILED_SKIP_ACTIVE_RECORD=true के साथ ओवरराइड करने की आवश्यकता है।
हम इस बेंचमार्क को विभिन्न एंडपॉइंट्स के खिलाफ चला सकते हैं ताकि यह देखा जा सके कि कौन सा (यदि कोई) लीक करता है।
perf:objects
perf:objects कार्य memory_profiler का उपयोग करता है इसलिए उत्पादित रिपोर्ट परिचित लगेगी।
यह रिपोर्ट यह संकीर्ण करने में मदद कर सकती है कि आपकी लीक मेमोरी कहां आवंटित की जा रही है। हमारे उदाहरण में, रिपोर्ट का अंतिम खंड — Retained String Report — हमें सटीक बताता है कि हमारी समस्या क्या है।
हमने LeaksController की लाइन 3 से "ABC" वाले 10,000 स्ट्रिंग्स लीक किए हैं। एक गैर-तुच्छ ऐप में, यह रिपोर्ट काफी बड़ी होगी और उन रिटेन्ड स्ट्रिंग्स को समाहित करेगी जिन्हें आप रखना चाहते हैं — क्वेरी कैश, आदि — लेकिन यह और अन्य 'स्थान के अनुसार' सेक्शन आपको अपने लीक को संकीर्ण करने में मदद करेंगे।
perf:heap_diff
perf:heap_diff बेंचमार्क मदद कर सकता है यदि perf:objects की रिपोर्ट यह देखने के लिए बहुत जटिल है कि आपका लीक कहां से आ रहा है।
जैसा कि नाम से पता चलता है, perf:heap_diff तीन हीप डंप बनाता है और उनके बीच अंतर की गणना करता है। यह एक रिपोर्ट बनाता है जिसमें डंप्स के बीच बनाए रखे गए ऑब्जेक्ट्स के प्रकार और उन्हें आवंटित करने वाला स्थान शामिल होता है।
आप Tracking a Ruby memory leak in 2021 भी पढ़ सकते हैं ताकि बेहतर समझ सकें कि क्या हो रहा है।
रिपोर्ट हमें ठीक वहीं इंगित करती है जहां हमें अपने लीक बेबी ऐप के लिए जाने की आवश्यकता है। डिफ के शीर्ष पर, हम LeaksController की लाइन 3 से आवंटित 999991 रिटेन्ड स्ट्रिंग ऑब्जेक्ट्स देखते हैं।
वास्तविक रूबी और रेल्स ऐप्स में लीक
उम्मीद है, अब तक हमारे द्वारा उपयोग किए गए उदाहरण कभी वास्तविक ऐप्स में नहीं डाले गए हैं — मुझे आशा है कि कोई भी जानबूझकर मेमोरी लीक नहीं करना चाहता!
गैर-तुच्छ ऐप्स में, मेमोरी लीक को ट्रैक करना बहुत कठिन हो सकता है। रिटेन्ड ऑब्जेक्ट्स हमेशा खराब नहीं होते — गार्बेज कलेक्टेड आइटम्स वाला कैश ज्यादा उपयोगी नहीं होगा।
हालांकि, सभी लीक के बीच कुछ सामान्य है। कहीं न कहीं, एक रूट-लेवल ऑब्जेक्ट (एक क्लास/ग्लोबल, आदि) एक ऑब्जेक्ट का संदर्भ रखता है।
एक सामान्य उदाहरण बिना किसी सीमा या निष्कासन नीति के कैश है। परिभाषा के अनुसार, यह मेमोरी लीक करेगा क्योंकि कैश में डाला गया हर ऑब्जेक्ट हमेशा के लिए रहेगा। समय के साथ, यह कैश ऐप की अधिक से अधिक मेमोरी पर कब्जा करेगा, जिसमें वास्तव में उपयोग में आने वाला प्रतिशत छोटा और छोटा होता जाएगा।
निम्न कोड पर विचार करें जो एक गेम के लिए हाई स्कोर लाता है। यह कुछ ऐसा है जो मैंने अतीत में देखा है। यह एक महंगा अनुरोध है, और हम इसे आसानी से बस्ट कर सकते हैं जब यह बदलता है, इसलिए हम इसे कैश करना चाहते हैं।
@scores हैश पूरी तरह से अनियंत्रित है। यह हर उपयोगकर्ता के लिए हर एक हाई स्कोर को रखने के लिए बढ़ेगा — आदर्श नहीं यदि आपके पास बहुत सारे हैं।
रेल्स ऐप में, हम शायद Rails.cache का उपयोग एक समझदार एक्सपायरी के साथ करना चाहेंगे (Redis में मेमोरी लीक अभी भी मेमोरी लीक है!) बजाय इसके।
गैर-रेल्स ऐप में, हम हैश आकार को सीमित करना चाहते हैं, सबसे पुराने या कम से कम हाल ही में उपयोग किए गए आइटम्स को हटाकर। LruRedux एक अच्छा कार्यान्वयन है।
इस लीक का एक अधिक सूक्ष्म संस्करण एक सीमा के साथ कैश है, लेकिन जिनकी कुंजियाँ मनमाने आकार की हैं। यदि कुंजियाँ स्वयं बढ़ती हैं, तो कैश भी बढ़ेगा। आमतौर पर, आप इससे नहीं टकराएंगे। लेकिन, यदि आप ऑब्जेक्ट्स को JSON के रूप में सीरियलाइज़ कर रहे हैं और इसे कुंजी के रूप में उपयोग कर रहे हैं, तो दोबारा जांचें कि आप उन चीजों को सीरियलाइज़ नहीं कर रहे हैं जो उपयोग के साथ बढ़ती हैं — जैसे उपयोगकर्ता के पढ़े गए संदेशों की सूची।
सर्कुलर रेफरेंसेस
सर्कुलर रेफरेंसेस हो सकते हैं गार्बेज कलेक्टेड। रूबी में गार्बेज कलेक्शन "मार्क एंड स्वीप" एल्गोरिदम का उपयोग करता है। पीटर झू और मैट वैलेंटाइन-हाउस ने वेरिएबल विड्थ एलोकेशन पेश करते हुए अपनी प्रस्तुति के दौरान इस एल्गोरिदम के काम करने का एक उत्कृष्ट विवरण दिया।
अनिवार्य रूप से, दो चरण हैं: मार्किंग और स्वीपिंग।
-
मार्किंग चरण में, गार्बेज कलेक्टर रूट ऑब्जेक्ट्स (क्लासेस, ग्लोबल्स, आदि) से शुरू होता है, उन्हें मार्क करता है, और फिर उनके संदर्भित ऑब्जेक्ट्स को देखता है।
यह तब सभी संदर्भित ऑब्जेक्ट्स को मार्क करता है। पहले से मार्क किए गए संदर्भित ऑब्जेक्ट्स को फिर से नहीं देखा जाता। यह तब तक जारी रहता है जब तक देखने के लिए कोई और ऑब्जेक्ट नहीं बचते — यानी, सभी संदर्भित ऑब्जेक्ट्स मार्क हो चुके हैं।
-
गार्बेज कलेक्टर फिर स्वीपिंग चरण पर जाता है। कोई भी ऑब्जेक्ट जो मार्क नहीं किया गया है, उसे साफ कर दिया जाता है।
इसलिए, लाइव रेफरेंसेस वाले ऑब्जेक्ट्स को अभी भी साफ किया जा सकता है। जब तक एक रूट ऑब्जेक्ट अंततः किसी ऑब्जेक्ट को संदर्भित नहीं करता, इसे कलेक्ट किया जाएगा। इस तरह, सर्कुलर रेफरेंसेस वाले ऑब्जेक्ट्स के क्लस्टर्स अभी भी गार्बेज कलेक्टेड हो सकते हैं।
एप्लिकेशन प्रदर्शन निगरानी: इवेंट टाइमलाइन और आवंटित ऑब्जेक्ट्स ग्राफ
जैसा कि इस श्रृंखला के पहले भाग में उल्लेख किया गया है, किसी भी प्रोडक्शन-लेवल ऐप को किसी न किसी रूप में एप्लिकेशन प्रदर्शन निगरानी (APM) का उपयोग करना चाहिए।
कई विकल्प उपलब्ध हैं, जिसमें अपना खुद का बनाना शामिल है (केवल बड़ी टीमों के लिए अनुशंसित)। एक प्रमुख विशेषता जो आपको APM से प्राप्त करनी चाहिए वह है एक एक्शन (या बैकग्राउंड जॉब) द्वारा किए गए आवंटन की संख्या देखने की क्षमता। अच्छे APM टूल इसे तोड़ देंगे, यह अंतर्दृष्टि देंगे कि आवंटन कहां से आते हैं — कंट्रोलर, व्यू, आदि।
इसे अक्सर 'इवेंट टाइमलाइन' जैसा कुछ कहा जाता है। बोनस अंक यदि आपका APM आपको कस्टम कोड लिखने की अनुमति देता है जो टाइमलाइन को आगे तोड़ देता है।
रेल्स कंट्रोलर के लिए निम्न कोड पर विचार करें।
जब APM द्वारा रिपोर्ट किया जाता है, तो 'इवेंट टाइमलाइन' AppSignal से निम्न स्क्रीनशॉट जैसा दिख सकता है।

इसे इंस्ट्रूमेंट किया जा सकता है ताकि हम देख सकें कि कोड का कौन सा हिस्सा टाइमलाइन में आवंटन करता है। वास्तविक ऐप्स में, यह कोड से शायद कम स्पष्ट होने वाला है 😅
यहां एक इंस्ट्रूमेंटेड इवेंट टाइमलाइन का उदाहरण है, फिर से AppSignal से:

यह जानना कि कहां इंस्ट्रूमेंट करना है, अक्सर समझना मुश्किल हो सकता है। आपके एप्लिकेशन के कोड को वास्तव में समझने का कोई विकल्प नहीं है, लेकिन कुछ संकेत हैं जो 'स्मेल्स' के रूप में काम कर सकते हैं।
यदि आपका APM समय के साथ GC रन या आवंटन को सतह पर लाता है, तो आप स्पाइक्स को देखकर देख सकते हैं कि क्या वे कुछ एंडपॉइंट्स के हिट होने या कुछ चल रहे बैकग्राउंड जॉब्स से मेल खाते हैं। यहां AppSignal के रूबी VM मैजिक डैशबोर्ड से एक और उदाहरण है:

इस तरह आवंटन को देखकर, हम अपनी खोज को संकीर्ण कर सकते हैं जब मेमोरी समस्याओं की जांच कर रहे हों। इससे memory_profiler और derailed_benchmarks जैसे टूल्स का कुशलतापूर्वक उपयोग करना बहुत आसान हो जाता है।
AppSignal के रूबी जेम में नवीनतम परिवर्धन के बारे में पढ़ें, जैसे आवंटन और GC स्टैट्स ट्रैकिंग।
समापन
इस पोस्ट में, हम उन टूल्स में गोता लगाए जो लीक खोजने और ठीक करने में मदद कर सकते हैं, जिसमें memory_profiler, derailed_benchmarks, perf:mem_over_time, perf:objects, perf:heap_diff, AppSignal में इवेंट टाइमलाइन और आवंटित ऑब्जेक्ट्स ग्राफ शामिल हैं।
मुझे आशा है कि आपको यह पोस्ट, भाग एक के साथ, आपके रूबी ऐप में मेमोरी लीक का निदान और समाधान करने में उपयोगी लगी होगी।
उपयोग किए गए टूल्स के बारे में और पढ़ें:
memory_profilerderailed_benchmarks- लीकी रेल्स ऐप
अतिरिक्त विस्तृत पठन:
GCमॉड्यूल दस्तावेज़ीकरणObjectSpaceमॉड्यूल दस्तावेज़ीकरण- गार्बेज कलेक्शन डीप डाइव
- वेरिएबल विड्थ एलोकेशन
हैप्पी कोडिंग!
P.S. यदि आप रूबी मैजिक पोस्ट को प्रेस से बाहर आते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूज़लेटर की सदस्यता लें और कभी भी एक भी पोस्ट मिस न करें!