मेमोरी लीक मेमोरी उपयोग में अनजाने में, अनियंत्रित, और अंतहीन वृद्धि है। चाहे कितना भी छोटा हो, अंततः लीक आपके प्रोसेस को मेमोरी से बाहर कर देगा और क्रैश कर देगा। यहां तक कि यदि आप इस क्रैश से बचने के लिए अपने ऐप को समय-समय पर रीस्टार्ट करते हैं (कोई निर्णय नहीं, मैंने ऐसा किया है!), तो आप अभी भी मेमोरी लीक के प्रदर्शन निहितार्थों से पीड़ित हैं।
इस पोस्ट में, मेमोरी लीक पर दो-भाग श्रृंखला का पहला भाग, हम यह देखकर शुरू करेंगे कि रूबी मेमोरी कैसे प्रबंधित करता है, गार्बेज कलेक्शन (GC) कैसे काम करता है, और लीक कैसे ढूंढें।
दूसरे भाग में, हम लीक को ट्रैक करने में गहरा गोता लगाएंगे।
शुरू करते हैं!
रूबी मेमोरी प्रबंधन
रूबी ऑब्जेक्ट्स हीप पर संग्रहीत होते हैं, और प्रत्येक ऑब्जेक्ट हीप पर एक स्लॉट भरता है।
रूबी 3.1 से पहले, हीप पर सभी स्लॉट्स का आकार समान था — सटीक होने के लिए 40 बाइट्स। स्लॉट में फिट होने के लिए बहुत बड़े ऑब्जेक्ट्स को हीप के बाहर संग्रहीत किया गया था। प्रत्येक स्लॉट में एक संदर्भ शामिल था जहां ऑब्जेक्ट्स को स्थानांतरित किया गया था।
रूबी 3.1 में, String ऑब्जेक्ट्स के लिए वेरिएबल विड्थ एलोकेशन मर्ज किया गया था। जल्द ही, वेरिएबल विड्थ एलोकेशन सभी ऑब्जेक्ट टाइप्स के लिए मानक होगा।
वेरिएबल विड्थ एलोकेशन का उद्देश्य कैश लोकैलिटी में सुधार करके प्रदर्शन में सुधार करना है — एक ऑब्जेक्ट की सभी जानकारी दो मेमोरी स्थानों के बजाय एक स्थान पर संग्रहीत होगी।
यह मेमोरी प्रबंधन के कुछ हिस्सों को सरल बनाना चाहिए। फिलहाल, दो 'हीप्स' हैं:
- रूबी हीप (या GC हीप) जो छोटे रूबी ऑब्जेक्ट्स को संग्रहीत करता है।
- C हीप (या malloc/ट्रांजिएंट हीप) जो बड़े ऑब्जेक्ट्स को संग्रहीत करता है।
एक बार वेरिएबल विड्थ एलोकेशन मानक बन जाने के बाद, बाद वाले हीप की आवश्यकता नहीं होनी चाहिए।
हीप एक दिए गए आकार (डिफ़ॉल्ट रूप से 10,000 स्लॉट्स) से शुरू होता है और ऑब्जेक्ट्स को बनाए जाने पर फ्री स्लॉट्स को असाइन किया जाता है। जब रूबी एक ऑब्जेक्ट बनाने की कोशिश करता है और कोई फ्री स्लॉट उपलब्ध नहीं होता, तो गार्बेज कलेक्शन (GC) होता है ताकि कुछ फ्री स्लॉट्स उपलब्ध हो सकें।
यदि GC के बाद बहुत कम फ्री स्लॉट्स हैं, तो हीप का विस्तार किया जाएगा (इस पर थोड़ी देर बाद)।
यहां वे कारक हैं जिन्हें आप नियंत्रित कर सकते हैं, उनके पर्यावरण चर के साथ:
- हीप का प्रारंभिक आकार -
RUBY_GC_HEAP_INIT_SLOTS - GC होने के बाद उपलब्ध होने वाले फ्री स्लॉट्स की संख्या -
RUBY_GC_HEAP_FREE_SLOTS - हीप कितना विस्तारित होता है -
RUBY_GC_HEAP_GROWTH_FACTOR
रूबी में गार्बेज कलेक्शन
रूबी में गार्बेज कलेक्शन 'दुनिया को रोकता है' — जब GC होता है तो कोई अन्य प्रक्रिया नहीं होती। रूबी में गार्बेज कलेक्शन (2.1 से) जनरेशनल भी है, जिसका अर्थ है कि गार्बेज कलेक्टर के दो मोड हैं:
- माइनर GC - 'युवा' ऑब्जेक्ट्स (हाल ही में बनाए गए ऑब्जेक्ट्स) का निरीक्षण करता है
- मेजर GC - 'पुराने' ऑब्जेक्ट्स के साथ-साथ 'युवा' ऑब्जेक्ट्स का निरीक्षण करता है (सभी ऑब्जेक्ट्स)
नोट: एक 'पुराना' ऑब्जेक्ट वह है जो 3 GC रन, मेजर या माइनर, से बच गया है।
जब हीप भर जाता है, तो माइनर GC पहले आह्वान किया जाता है। यदि यह सीमा से नीचे पर्याप्त स्लॉट्स खाली नहीं कर सकता, तो मेजर GC आह्वान किया जाएगा। केवल तभी, यदि अभी भी पर्याप्त फ्री स्लॉट्स नहीं हैं, तो हीप का विस्तार किया जाएगा।
मेजर GC माइनर GC से अधिक महंगा है क्योंकि यह अधिक ऑब्जेक्ट्स को देखता है।
जनरेशनल GC के अधिक प्रदर्शनकारी होने के पीछे सिद्धांत यह है कि ऑब्जेक्ट्स आमतौर पर दो श्रेणियों में आते हैं:
- ऑब्जेक्ट्स जो आवंटित होते हैं और फिर जल्दी स्कोप से बाहर चले जाते हैं। एक रेल्स ऐप में, एक पेज रेंडर करने के लिए DB से लाए गए मॉडल्स रिक्वेस्ट समाप्त होने पर स्कोप से बाहर चले जाएंगे।
- ऑब्जेक्ट्स जो आवंटित होते हैं और लंबे समय तक बने रहते हैं। क्लासेस और कैश ऐप के जीवनकाल भर उपयोग में रहने की संभावना रखते हैं।
मेजर GC माइनर GC के बाद भी चलेगा यदि पुराने ऑब्जेक्ट्स की संख्या एक निश्चित सीमा से ऊपर है, भले ही पर्याप्त फ्री स्लॉट्स हों। यह सीमा हीप के आकार के बढ़ने के साथ बढ़ती है और इसे RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR पर्यावरण चर द्वारा नियंत्रित किया जा सकता है।
जब आपके पास लीक होता है, तो आप ऐसे ऑब्जेक्ट्स बनाते हैं जिन्हें साफ नहीं किया जा सकता — अधिक से अधिक old ऑब्जेक्ट्स। इसका मतलब है कि मेजर (महंगा) GC जितना होना चाहिए उससे कहीं अधिक बार चलेगा। चूंकि GC चलने पर कुछ और नहीं चलता, यह वह समय है जो आप बर्बाद करते हैं।
मैंने इस लेख के अंत में रूबी में मेमोरी लेआउट और गार्बेज कलेक्टर पर आगे पढ़ने के लिए कुछ लिंक छोड़े हैं।
रूबी में मेमोरी लीक कैसी दिखती है?
आप किसी भी यूनिक्स सिस्टम पर उपलब्ध सरल टूल्स का उपयोग करके मेमोरी लीक देख सकते हैं। निम्न कोड को उदाहरण के रूप में लें।
यह कहना कि यह कोड 'लीक' करता है थोड़ा अनुचित है — यह केवल लीक करता है! — लेकिन यह हमारे उद्देश्यों को पूरा करता है।
हम इस प्रोग्राम को एक टर्मिनल में चलाकर और watch के साथ ps का उपयोग करके समय के साथ मेमोरी वृद्धि देखकर लीक को कमांड लाइन से काफी सरलता से देख सकते हैं।
pgrep -f "ruby ./leaky.rb" हमारे लिए प्रोसेस आईडी ढूंढता है, ताकि हम ps आउटपुट को केवल उस प्रोसेस तक सीमित कर सकें जिसमें हम रुचि रखते हैं। जैसा कि आप अनुमान लगा सकते हैं, यह प्रोसेस के लिए grep की तरह है।
watch टूल हमें दिए गए कमांड के आउटपुट को पोल करने और इसे स्थानांतरित करके अपडेट करने की अनुमति देता है, जिससे हमें अपने टर्मिनल के भीतर एक लाइव डैशबोर्ड मिलता है।
आपको इस तरह का आउटपुट मिलेगा, जो हर कुछ सेकंड में अपडेट होता है।
आपको %MEM और RSS बढ़ते हुए देखने चाहिए। वे हैं:
%MEM- होस्ट मशीन पर मेमोरी के प्रतिशत के रूप में प्रोसेस द्वारा उपयोग की जाने वाली मेमोरी की मात्रा।RSS(रेजिडेंट सेट साइज) - प्रोसेस द्वारा उपयोग की जाने वाली RAM की मात्रा बाइट्स में।
यह बुनियादी OS-ओनली जानकारी लीक होने पर स्पॉट करने के लिए पर्याप्त है — यदि मेमोरी बढ़ती रहती है, तो इसका मतलब है कि आपके पास लीक है!
गार्बेज कलेक्टर मॉड्यूल के साथ रूबी लीक ढूंढें
हम GC मॉड्यूल के साथ रूबी कोड के भीतर लीक का पता लगा सकते हैं।
GC.stat मेथड उपयोगी जानकारी के साथ एक हैश लौटाएगा। यहां, हम :heap_live_slots में रुचि रखते हैं, जो हीप पर उपयोग में स्लॉट्स की संख्या है। यह :heap_free_slots का विपरीत है। लूप के अंत में, हम एक मेजर GC को मजबूर करते हैं और उपयोग किए गए स्लॉट्स की संख्या प्रिंट करते हैं, यानी, GC के बाद शेष ऑब्जेक्ट्स की संख्या।
जब हम अपना छोटा प्रोग्राम चलाते हैं, तो हम इसे अनंत तक बढ़ते हुए देखते हैं। हमारे पास लीक है! हम GC.stat(:old_objects) का उपयोग उसी प्रभाव के लिए भी कर सकते थे।
जबकि GC मॉड्यूल का उपयोग यह देखने के लिए किया जा सकता है कि क्या हमारे पास लीक है और (यदि आप अपने puts स्टेटमेंट्स के साथ स्मार्ट हैं) लीक कहां हो सकता है, हम ObjectSpace मॉड्यूल के साथ लीक हो सकने वाले ऑब्जेक्ट्स के प्रकार देख सकते हैं।
ObjectSpace.count_objects मेथड लाइव ऑब्जेक्ट्स की गिनती के साथ एक हैश लौटाता है। T_STRING, उदाहरण के लिए, मेमोरी में लाइव स्ट्रिंग्स की संख्या है। हमारे बल्कि लीक प्रोग्राम के लिए, यह मान प्रत्येक लूप के साथ बढ़ता है, यहां तक कि GC के बाद भी। हम देख सकते हैं कि हम स्ट्रिंग ऑब्जेक्ट्स लीक कर रहे हैं।
AppSignal के साथ प्रोडक्शन में एप्लिकेशन प्रदर्शन निगरानी
ps और GC के साथ खेलना खिलौना प्रोजेक्ट्स के लिए एक समझदार मार्ग हो सकता है — वे मजेदार और जानकारीपूर्ण भी हैं! — मैं नहीं उन्हें प्रोडक्शन ऐप्स में आपके मेमोरी लीक डिटेक्शन समाधान के रूप में अनुशंसा करूंगा।
यह वह जगह है जहां आप एप्लिकेशन प्रदर्शन निगरानी (APM) टूल का उपयोग करेंगे। यदि आप एक बहुत बड़ी कंपनी हैं, तो आप इन्हें स्वयं बना सकते हैं। छोटे संगठनों के लिए, हालांकि, ऑफ-द-शेल्फ APM चुनना जाने का रास्ता है। आपको मासिक सदस्यता का भुगतान करना होगा, लेकिन उनके द्वारा प्रदान की जाने वाली जानकारी इसके लिए पर्याप्त है।
मेमोरी लीक का पता लगाने के लिए, आप सर्वर या प्रोसेस मेमोरी उपयोग (कभी-कभी RSS कहा जाता है) ग्राफ़ को समय के साथ ढूंढना चाहते हैं। यहां AppSignal के 'प्रोसेस मेमोरी उपयोग' डैशबोर्ड से एक स्वस्थ ऐप का उदाहरण स्क्रीनशॉट है जिसे तैनात किए जाने के तुरंत बाद:

और यहां तैनाती के बाद एक अस्वस्थ ऐप है:

AppSignal रूबी VM स्टैट्स जैसे GC और हीप स्लॉट्स को भी सतह पर लाएगा, जो मेमोरी लीक के लिए एक स्पष्ट संकेत दे सकता है। यदि लाइव स्लॉट्स की संख्या बढ़ती रहती है, तो आपके पास लीक है!

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