2017-04-25 12 views
12

मेरे पास एक इलीक्सिर/ओटीपी एप्लीकेशन है जो आउट-ऑफ-मेमोरी समस्या के कारण उत्पादन में क्रैश होता है। क्रैश का कारण बनने वाला कार्य एक समर्पित प्रक्रिया में हर 6 घंटे कहलाता है। यह कई मिनट (~ 30) लेता है चलाने के लिए और इस तरह दिखता है:बड़ी बाइनरी लीक को हल करना

def entry_point do 
    get_jobs_to_scrape() 
    |> Task.async_stream(&scrape/1) 
    |> Stream.map(&persist/1) 
    |> Stream.run() 
end 

अपने स्थानीय मशीन पर मैं बड़े बाइनरी स्मृति की खपत में लगातार वृद्धि देख समारोह चलता है जब:

observer memory usage shows constant memory growth of large binaries

ध्यान दें कि जब मैं उस प्रक्रिया पर कचरा संग्रह मैन्युअल रूप से ट्रिगर करता हूं जो फ़ंक्शन चलाता है तो मेमोरी खपत में काफी गिरावट आती है, इसलिए यह निश्चित रूप से जीसी में असमर्थ कई अलग-अलग प्रक्रियाओं में कोई समस्या नहीं है, लेकिन केवल एक ही जीसी सही नहीं है। इसके अलावा, यह कहना महत्वपूर्ण है कि प्रत्येक कुछ मिनट प्रक्रिया जीसी का प्रबंधन करती है, लेकिन कभी-कभी यह पर्याप्त नहीं है। उत्पादन सर्वर में केवल 1 जीबी रैम है और यह जीसी के आने से पहले दुर्घटनाग्रस्त हो जाता है।

इस समस्या को हल करने की कोशिश कर रहा था, मैं Erlang in Anger (पेज 66-67 देखें) में आया था। एक सुझाव है कि सभी बंद बाइनरी मैनिप्लेशंस को एक-ऑफ प्रक्रियाओं में डालना है। scrape फ़ंक्शन का वापसी मूल्य एक मानचित्र है जिसमें बड़ी बाइनरीयां होती हैं। इसलिए, वे Task.async_stream "श्रमिकों" और प्रक्रिया को चलाने वाली प्रक्रिया के बीच साझा किए जाते हैं। तो, सिद्धांत रूप में, मैं persist 10 के अंदर scrape के साथ persist डाल सकता था। मैं ऐसा नहीं करना पसंद करता हूं, और प्रक्रिया को persist पर प्रक्रिया के माध्यम से सिंक्रनाइज़ करता हूं।

एक और सुझाव समय-समय पर :erlang.garbage_collect पर कॉल करना है। ऐसा लगता है कि यह समस्या हल करता है लेकिन रास्ता बहुत हैकी लगता है। लेखक भी इसकी सिफारिश नहीं करता है।

def entry_point do 
    my_pid = self() 
    Task.async(fn -> periodically_gc(my_pid) end) 
    # The rest of the function as before... 
end 

defp periodically_gc(pid) do 
    Process.sleep(30_000) 
    if Process.alive?(pid) do 
    :erlang.garbage_collect(pid) 
    periodically_gc(pid) 
    end 
end 

और परिणामस्वरूप स्मृति लोड:

observer memory usage after GC hack

मैं काफी कैसे पुस्तक में अन्य सुझावों समस्या फिट समझ में नहीं आता यहाँ मेरे वर्तमान समाधान है।

आप उस मामले में क्या अनुशंसा करेंगे? हैकी समाधान रखें या बेहतर विकल्प हैं।

+0

आप ईटीएस में बाइनरी रखने पर विचार किया था:

इस विषय पर एक अच्छा पढ़ा है? यदि आप उन्हें विश्वसनीय रूप से रिलीज़ कर सकते हैं, तो प्रभावी ढंग से आप मैन्युअल आवंटन करने के लिए आगे बढ़ते हैं और बीएएम के जीसी में किसी भी तरह की नीचता को बाधित करते हैं। ओटीओएच, अगर यह एक उचित हिमपात का टुकड़ा है, तो मैन्युअल कॉल-जीसी समाधान पर्याप्त है? – cdegroot

+0

दिलचस्प विचार! चलो देखते हैं कि मैं समझता हूं: स्क्रैपर फ़ंक्शन होने से डेटा को ईटीएस में रखा जाता है और फिर तालिका में डेटा पर 'persist' फ़ंक्शन को मैप किया जाता है, क्या यह सही है? फिर भी, 'Task.async_stream' कर्मचारी के पास बड़ी बाइनरी के संदर्भ होंगे, और मुख्य प्रक्रिया में' persist 'फ़ंक्शन के अंदर ईटीएस से लाने के लिए समान संदर्भ होंगे, और समस्या बनी रहेगी। – Nagasaki45

+0

ठीक है, "प्रत्येक गणना करने योग्य आइटम को कार्य के लिए तर्क के रूप में पारित किया जाता है और अपने कार्य द्वारा संसाधित किया जाता है" 'Task.async_stream' दस्तावेज़ के अनुसार, इसलिए यदि आपके प्रत्येक 'स्क्रैप' कॉल केवल बाइनरी का निर्माण करते हैं, तो इसे ईटीएस में दबाएं , और एक संदर्भ वापस, तो आप व्यापार में हो सकता है। – cdegroot

उत्तर

6

एरलांग वर्चुअल मशीन में एक कचरा संग्रह तंत्र है जो डिफ़ॉल्ट रूप से, अल्पकालिक डेटा के लिए अनुकूलित किया जाता है। एक छोटी सी जीवित प्रक्रिया तब तक कचरा नहीं हो सकती जब तक कि यह मर न जाए, और अधिकांश कचरा संग्रह केवल नए जोड़े गए आइटमों की जांच करता है। एक जीसी रन से बचने वाले आइटमों को फिर से चेक नहीं किया जाएगा जब तक कि पूर्ण स्वीप न हो जाए।

मैं सुझाव दूंगा कि आप fullsweep_after ध्वज समायोजित करने का प्रयास करें। इसे :erlang.system_flag(:fullsweep_after, value) के माध्यम से या :erlang.spawn_opt/4 का उपयोग करके अपनी विशिष्ट प्रक्रिया के लिए वैश्विक स्तर पर सेट किया जा सकता है।

डॉक्स से:

Erlang क्रम प्रणाली, एक पीढ़ीगत कचरा संग्रहण योजना का उपयोग करता है डेटा है कि कम से कम एक कचरा संग्रहण बच गया है के लिए एक "पुराने ढेर" का उपयोग। जब पुराने ढेर पर कोई और कमरा नहीं होता है, तो एक पूर्ण-कचरा कचरा संग्रह किया जाता है।

विकल्प fullsweep_after पुरानी ढेर पर कमरा होने पर भरे हुए पूर्ण होने के लिए जनरेशनल संग्रह की अधिकतम संख्या निर्दिष्ट करना संभव बनाता है। संख्या को शून्य पर सेट करना सामान्य संग्रह एल्गोरिदम अक्षम करता है, यानी, सभी लाइव डेटा को प्रत्येक कचरा संग्रह में कॉपी किया जाता है।

कुछ मामलों में जब यह fullsweep_after बदलने के लिए उपयोगी हो सकता है:

  • बाइनरी नहीं रह उपयोग किया जाता है कि जितनी जल्दी हो सके फेंक दिया जा करने के लिए कर रहे हैं। (शून्य पर संख्या सेट करें।)
  • एक ऐसी प्रक्रिया जिसमें ज्यादातर अल्पकालिक डेटा होता है, कभी-कभी पूर्ण या कभी नहीं होता है, यानी पुराने ढेर में ज्यादातर कचरा होता है। कभी-कभी पूर्णत: सुनिश्चित करने के लिए, 10 या 20 के रूप में उपयुक्त मान पर सेट करें,
  • सीमित मात्रा में रैम और वर्चुअल मेमोरी वाले एम्बेडेड सिस्टम में, आप संख्या को शून्य पर सेट करके स्मृति को संरक्षित करना चाहेंगे। (मूल्य विश्व स्तर पर स्थापित किया जा सकता, देख erlang: system_flag/2।)

डिफ़ॉल्ट मान 65535 (जब तक आप पहले से ही वातावरण चर ERL_FULLSWEEP_AFTER के माध्यम से इसे बदल), इसलिए किसी भी कम मूल्य कचरा कर देगा है संग्रह अधिक आक्रामक। https://www.erlang-solutions.com/blog/erlang-19-0-garbage-collector.html

संबंधित मुद्दे