शायद हर प्रोग्रामर केंट बेक के शब्दों को जानता है: "इसे काम करो, इसे सही करो, इसे तेज करो।" पहले आपको कार्यक्रम को काम करने की आवश्यकता है, फिर आपको इसे सही ढंग से काम करने की आवश्यकता है, और उसके बाद ही आप अनुकूलन के लिए आगे बढ़ सकते हैं।

लेख के लेखक, जिसका अनुवाद हम प्रकाशित करते हैं, का कहना है कि हाल ही में उन्होंने अपने ओपन-सोर्स गो-प्रोजेक्ट
फ्लिप की रूपरेखा को लेने का फैसला किया। वह इस परियोजना में कोड ढूंढना चाहते थे, जिसे सहजता से अनुकूलित किया जा सके और जिससे कार्यक्रम को गति मिल सके। प्रोफाइलिंग के दौरान, उन्होंने लोकप्रिय ओपन सोर्स प्रोजेक्ट में कुछ अप्रत्याशित समस्याओं की खोज की जिसका उपयोग फ़्लिप ने राउटिंग और मिडलवेयर समर्थन को व्यवस्थित करने के लिए किया था। नतीजतन, ऑपरेशन के दौरान आवेदन द्वारा आवंटित मेमोरी की मात्रा को 100 गुना कम करना संभव था। इसके कारण, कचरा संग्रहण कार्यों की संख्या में कमी आई और परियोजना के समग्र प्रदर्शन में सुधार हुआ। यहाँ है कि यह कैसे था।
उच्च यातायात पीढ़ी
इससे पहले कि मैं प्रोफाइलिंग शुरू कर पाऊं, मुझे पता था कि पहले मुझे एप्लिकेशन में प्रवेश करने के लिए बड़ी मात्रा में ट्रैफ़िक उत्पन्न करने की आवश्यकता थी, जो मुझे इसके व्यवहार के कुछ पैटर्न देखने में मदद करेगा। यहां, मैं तुरंत एक समस्या में भाग गया, क्योंकि मेरे पास कुछ भी नहीं है जो उत्पादन में फ़्लिप्ट का उपयोग करेगा और कुछ ट्रैफ़िक प्राप्त करेगा जो मुझे लोड के तहत परियोजना के काम का मूल्यांकन करने की अनुमति देता है। नतीजतन, मुझे लोड परीक्षण परियोजनाओं के लिए एक महान उपकरण मिला। यह
सब्ज़ी है । परियोजना के लेखकों का कहना है कि वेजा लोड परीक्षण के लिए एक सार्वभौमिक HTTP उपकरण है। इस परियोजना का जन्म HTTP सेवाओं को एक बड़ी संख्या में दी गई आवृत्ति के साथ आने वाले अनुरोधों के साथ लोड करने की आवश्यकता से हुआ था।
सब्ज़ी परियोजना वास्तव में मेरे लिए आवश्यक उपकरण है, क्योंकि इसने मुझे आवेदन के लिए अनुरोधों की एक सतत धारा बनाने की अनुमति दी। इन अनुरोधों के साथ, आप ढेर पर आवंटन / मेमोरी के उपयोग, गोरोइटिन की सुविधाओं, कचरा संग्रह पर खर्च किए गए समय जैसे संकेतकों का पता लगाने के लिए एप्लिकेशन को "शेल" कर सकते हैं।
कुछ प्रयोग करने के बाद, मैं सब्ज़ लॉन्च के निम्नलिखित विन्यास में गया:
echo 'POST http://localhost:8080/api/v1/evaluate' | vegeta attack -rate 1000 -duration 1m -body evaluate.json
यह कमांड एक मिनट के लिए 1000 अनुरोधों (जो कि, वास्तव में, एक गंभीर भार है) की गति से HTTP POST अनुरोधों को
REST API Flipt को भेजकर,
attack
मोड में
सब्जियों को लॉन्च करता है। Flipt द्वारा भेजा गया JSON डेटा विशेष रूप से महत्वपूर्ण नहीं है। उन्हें केवल अनुरोध निकाय के सही गठन के लिए आवश्यक है। ऐसा अनुरोध फ़्लिप सर्वर द्वारा प्राप्त किया गया था, जो अनुरोध
सत्यापन प्रक्रिया कर सकता है।
कृपया ध्यान दें कि मैंने पहली बार Flipt का परीक्षण
/evaluate
करने का निर्णय लिया था। तथ्य यह है कि यह अधिकांश कोड चलाता है जो परियोजना के तर्क को लागू करता है और "जटिल" सर्वर गणना करता है। मैंने सोचा था कि इस समापन बिंदु के परिणामों का विश्लेषण करने से मुझे आवेदन के क्षेत्रों पर सबसे मूल्यवान डेटा मिलेगा, जिसे बेहतर बनाया जा सकता है।
माप
अब जब मेरे पास पर्याप्त मात्रा में ट्रैफ़िक उत्पन्न करने के लिए एक उपकरण था, तो मुझे उस प्रभाव को मापने का एक तरीका खोजने की आवश्यकता थी जो इस ट्रैफ़िक का चल रहे अनुप्रयोग पर था। सौभाग्य से, गो में बहुत अच्छे मानक उपकरण हैं जो कार्यक्रम के प्रदर्शन को माप सकते हैं। यह
पवित्र पैकेज के बारे में है।
मैं pprof का उपयोग करने के विवरण में नहीं जाऊंगा। मुझे नहीं लगता कि मैं जूलिया इवांस की तुलना में बेहतर करूंगा, जिन्होंने
इस अद्भुत लेख को प्रोफिटिंग के साथ गो कार्यक्रमों की रूपरेखा के बारे में लिखा था (यदि आप इसे नहीं पढ़ते हैं, तो मैं निश्चित रूप से आपको इस पर एक नज़र डालने की सलाह देता हूं)।
चूंकि Flipt में HTTP राऊटर
गो-ची / ची का उपयोग करके कार्यान्वित किया जाता है, इसलिए
उपयुक्त ची इंटरमीडिएट हैंडलर का उपयोग कर मेरे लिए pprof को सक्षम करना मुश्किल नहीं था।
इसलिए, एक खिड़की में Flipt ने मेरे लिए काम किया, और सब्जियों ने, Flipt को अनुरोधों के साथ भरने के लिए, दूसरी खिड़की में काम किया। मैंने ढेर प्रोफाइलिंग डेटा को इकट्ठा करने और जांचने के लिए तीसरी टर्मिनल विंडो लॉन्च की:
pprof -http=localhost:9090 localhost:8080/debug/pprof/heap
यह Google pprof टूल का उपयोग करता है, जो सीधे ब्राउज़र में प्रोफाइलिंग डेटा की कल्पना कर सकता है।
सबसे पहले मैंने ढेर पर क्या हो रहा है यह समझने के लिए
inuse_objects
और
inuse_space
जाँच की। हालांकि, मुझे कुछ भी उल्लेखनीय नहीं मिला। लेकिन जब मैंने
alloc_objects
और
alloc_space
पर एक नज़र डालने का फैसला किया, तो कुछ ने मुझे सचेत किया।
प्रोफाइलिंग परिणामों का विश्लेषण ( मूल )एक भावना थी कि कुछ कहा जाता है
flate.NewWriter
ने एक मिनट के लिए 19370 एमबी मेमोरी आवंटित की थी। और यह, वैसे, 19 से अधिक गीगाबाइट है! यहाँ, जाहिर है, कुछ अजीब हो रहा था। लेकिन वास्तव में क्या? यदि आप ऊपर दिए गए आरेख के मूल को करीब से देखते हैं, तो यह पता चलता है कि
flate.NewWriter
को
gzip.(*Writer).Write
कहा जाता है
gzip.(*Writer).Write
। मुझे जल्दी से एहसास हुआ कि जो हो रहा था उसका फ़्लिप कोड से कोई लेना-देना नहीं था। एपीआई से प्रतिक्रियाओं को संपीड़ित करने के लिए इस्तेमाल किए जाने वाले
ची मिडलवेयर कोड में समस्या कहीं न कहीं थी।
// r.Use(middleware.Compress(gzip.DefaultCompression))
मैंने उक्त लाइन पर टिप्पणी की और परीक्षण फिर से चलाया। जैसी कि उम्मीद थी, स्मृति आवंटन कार्यों की एक बड़ी संख्या गायब हो गई है।
इससे पहले कि मैं इस समस्या का हल खोजने के बारे में सोचूँ, मैं दूसरी तरफ से इन मेमोरी आवंटन कार्यों को देखना चाहता था और यह समझना चाहता था कि वे प्रदर्शन को कैसे प्रभावित करते हैं। विशेष रूप से, जब मैं कचरा इकट्ठा करने के कार्यक्रम को लेती हूं, तो उनके प्रभाव में मेरी दिलचस्पी थी। मुझे याद आया कि गो में अभी भी एक
ट्रेस उपकरण है जो आपको उनके निष्पादन के दौरान कार्यक्रमों का विश्लेषण करने और कुछ समय के लिए उनके बारे में जानकारी एकत्र करने की अनुमति देता है। ट्रेस द्वारा एकत्र किए गए डेटा में ढेर उपयोग जैसे महत्वपूर्ण संकेतक, निष्पादित किए जाने वाले गोरोइटिन की संख्या, नेटवर्क और सिस्टम अनुरोधों के बारे में जानकारी, और जो विशेष रूप से मेरे लिए मूल्यवान थे, कचरा कलेक्टर में बिताए समय के बारे में जानकारी शामिल है।
एक चल रहे कार्यक्रम के बारे में जानकारी को प्रभावी ढंग से एकत्र करने के लिए, मुझे सब्जियों का उपयोग करके आवेदन पर भेजे गए प्रति सेकंड अनुरोधों की संख्या को कम करने की आवश्यकता थी, क्योंकि सर्वर नियमित रूप से मुझे
socket: too many open files
त्रुटियां। मैंने मान लिया कि ऐसा इसलिए था क्योंकि मेरे कंप्यूटर पर
ulimit
को बहुत कम सेट किया गया था, लेकिन मैं तब इसमें नहीं जाना चाहता था।
इसलिए, मैंने इस आदेश के साथ सब्जियों को फिर से शुरू किया:
echo 'POST http://localhost:8080/api/v1/evaluate' | vegeta attack -rate 100 -duration 2m -body evaluate.json
परिणामस्वरूप, अगर हम पिछले परिदृश्य से इसकी तुलना करते हैं, तो केवल दसवें अनुरोध को सर्वर पर भेजा गया था, लेकिन यह लंबे समय तक किया गया था। इसने मुझे कार्यक्रम के काम पर उच्च-गुणवत्ता वाले डेटा एकत्र करने की अनुमति दी।
एक और टर्मिनल विंडो में, मैंने इस कमांड को चलाया:
wget 'http://localhost:8080/debug/pprof/trace?seconds=60' -O profile/trace
नतीजतन, मेरे पास मेरे निपटान में 60 सेकंड में ट्रेस डेटा के साथ एक फ़ाइल थी। आप निम्न कमांड का उपयोग करके इस फ़ाइल की जांच कर सकते हैं:
go tool trace profile/trace
इस कमांड के निष्पादन से ब्राउज़र में एकत्रित जानकारी की खोज हुई। उन्हें अध्ययन के लिए एक सुविधाजनक ग्राफिक रूप में प्रस्तुत किया गया था।
go tool trace
बारे में विवरण
इस अच्छे लेख में पाया जा सकता है।
फ़्लिप ट्रेस परिणाम। हीप पर मेमोरी आवंटन का आरी ग्राफ स्पष्ट रूप से दिखाई देता है ( मूल )इस ग्राफ पर, यह देखना आसान है कि ढेर पर आवंटित स्मृति की मात्रा काफी तेजी से बढ़ती है। इस मामले में, वृद्धि के बाद तेज गिरावट होनी चाहिए। जिन स्थानों पर आवंटित मेमोरी गिरती है, वे कचरा संग्रहण अभियान हैं। यहां आप जीसी क्षेत्र में स्पष्ट नीले कॉलम देख सकते हैं, जो कचरा संग्रहण पर खर्च किए गए समय का प्रतिनिधित्व करते हैं।
अब मैंने "अपराध" के सभी सबूत एकत्र कर लिए हैं जिनकी मुझे आवश्यकता है और स्मृति को आवंटित करने की समस्या के समाधान के लिए खोज शुरू कर सकते हैं।
समस्या हल करना
इस कारण का पता लगाने के लिए कि क्यों कॉलिंग
flate.NewWriter
ने बहुत सारे मेमोरी आवंटन का नेतृत्व किया, मुझे
ची स्रोत कोड को देखने की आवश्यकता थी। यह जानने के लिए कि मैं किस संस्करण का उपयोग कर रहा हूं, मैंने निम्न कमांड चलाई:
go list -m all | grep chi github.com/go-chi/chi v3.3.4+incompatible
स्रोत कोड
chi / मिडलवेयर / compress.go @ v3.3.4 तक पहुंचने के बाद, मैं निम्नलिखित विधि खोजने में सक्षम था:
func encoderDeflate(w http.ResponseWriter, level int) io.Writer { dw, err := flate.NewWriter(w, level) if err != nil { return nil } return dw }
आगे के शोध में, मुझे पता चला कि एक मध्यवर्ती हैंडलर के माध्यम से
flate.NewWriter
विधि को प्रत्येक प्रतिक्रिया के लिए बुलाया गया था। यह स्मृति आवंटन संचालन की बड़ी मात्रा के अनुरूप था, जिसे मैंने पहले देखा था, प्रति सेकंड एक हजार अनुरोधों के साथ एपीआई को लोड करना।
मैं एपीआई प्रतिक्रियाओं को संक्षिप्त करने या नए HTTP राउटर और एक नए मिडलवेयर सपोर्ट लाइब्रेरी की तलाश करने से इंकार नहीं करना चाहता था। इसलिए, मैंने सबसे पहले यह पता लगाने का निर्णय लिया कि क्या केवल ची को अपडेट करके मेरी समस्या का सामना करना संभव है।
मैं चला गया
go get -u -v "github.com/go-chi/chi"
, ची 4.0.2 में अपग्रेड किया गया, लेकिन डेटा कम्प्रेशन के लिए मिडिलवेयर कोड जैसा दिखता था, वह मुझे पहले जैसा ही लग रहा था। जब मैंने दोबारा परीक्षण किए, तो समस्या दूर नहीं हुई।
इस सवाल का अंत करने से पहले, मैंने ची रिपॉजिटरी में समस्याओं या पीआर संदेशों की तलाश करने का फैसला किया, जो "संपीड़न मिडलवेयर" जैसी किसी चीज़ का उल्लेख करते हैं। मैं निम्नलिखित हेडिंग के साथ एक पीआर में आया: "मिडलवेयर कंप्रेशन लाइब्रेरी को फिर से लिखा"। इस पीआर के लेखक ने निम्नलिखित कहा: "इसके अलावा, सिंक.पोल का उपयोग एन्कोडर के लिए किया जाता है, जिसमें एक रीसेट विधि (io.Writer) होती है, जो मेमोरी लोड को कम करने की अनुमति देती है।"
यहाँ यह है! सौभाग्य से, इस पीआर को
master
शाखा में जोड़ा गया था, लेकिन चूंकि कोई नई ची रिलीज़ नहीं बनाई गई थी, इसलिए मुझे इस तरह से अपडेट करने की आवश्यकता थी:
go get -u -v "github.com/go-chi/chi@master"
यह अपडेट, जिसने मुझे बहुत प्रसन्न किया, वह पिछड़ा संगत था, इसके उपयोग को मेरे आवेदन के कोड में बदलाव की आवश्यकता नहीं थी।
परिणाम
मैंने लोड परीक्षण और फिर से रूपरेखा तैयार की। इसने मुझे सत्यापित करने की अनुमति दी कि ची अपडेट ने समस्या का समाधान किया।
अब flate.NewWriter आवंटित मेमोरी के पहले इस्तेमाल की गई राशि का सौवां हिस्सा उपयोग करता है ( मूल )ट्रेस परिणामों को फिर से देखते हुए, मैंने देखा कि ढेर का आकार अब बहुत धीरे-धीरे बढ़ रहा है। इसके अलावा, कचरा संग्रहण के लिए आवश्यक समय कम हो गया है।
अलविदा - "देखा" ( मूल )कुछ समय बाद, मैंने फ़्लिप रिपॉजिटरी को
अपडेट किया, पहले की तुलना में अधिक आत्मविश्वास होने पर मेरी परियोजना पर्याप्त रूप से उच्च भार का सामना करने में सक्षम होगी।
परिणाम
उपरोक्त समस्याओं को खोजने और ठीक करने में कामयाब होने के बाद मैं यहां दिए गए निष्कर्ष हैं:
- आपको इस धारणा पर भरोसा नहीं करना चाहिए कि ओपन सोर्स लाइब्रेरी (यहां तक कि लोकप्रिय भी) को अनुकूलित किया गया है, या उन्हें कोई स्पष्ट समस्या नहीं है।
- एक निर्दोष समस्या गंभीर परिणाम हो सकती है, "डोमिनो प्रभाव" की अभिव्यक्तियों के लिए, विशेष रूप से भारी भार के तहत।
- यदि संभव हो, तो आपको सिंक का उपयोग करना चाहिए।
- लोड के तहत परीक्षण परियोजनाओं के लिए और उन्हें रूपरेखा के लिए उपकरण को हाथ में रखना उपयोगी है।
- टूलकिट और ओपन सोर्स जाओ - महान!
प्रिय पाठकों! आप अपने गो प्रोजेक्ट्स के प्रदर्शन पर शोध कैसे करते हैं?
