मैं इसे लिख रहा हूँ ताकि आप मुझे जैसा लगभग कर न पाएं जब मैंने मेमरी स्पाइक को अवशोषित करने के लिए प्रोडक्शन में स्वैप चलाने का फैसला किया। मेरे पास एक cgroup था जिसमें दो प्रक्रियाएँ थीं: एक Go प्रक्रिया है जो io.ReadAll को कॉल करती है और फिर proto.Unmarshal करती है, एक blob बनाती है और फिर एक ग्राफ स्ट्रक्चर बनाती है (जो Go के आवंटकर्ता द्वारा स्कैन के रूप में चिह्नित है)। दूसरी प्रक्रिया एक HTTP सर्वर है जो अधिकांश समय चुप रहता है। हर बार जब संग्राहक चलता है, तो वह इन स्कैन स्पैन को पढ़ता है, पॉइंटर के माध्यम से, और फिर उनका निर्णय लेता है। इसलिए मैंने सोचा: ठीक है, मेमरी दबाव के तहत, कर्नल पृष्ठों को स्वैप डिवाइस पर निकाल देगा, लेकिन चूंकि निकासी प्रति cgroup है और प्रति प्रक्रिया नहीं, इसलिए दोनों प्रक्रियाओं के पृष्ठ निकाल लिए जाएंगे - इसलिए इसे कर्नल और गार्बेज कलेक्टर के बीच स्वैप-इन और स्वैप-आउट के एक दुखद नृत्य में बदलने की संभावना बहुत छोटी है। मैं गलत था। इसके साथ प्रयोग करते समय, मुझे एक समस्या मिली जो मुझे नुकसान पहुँचा सकती थी: Go का गार्बेज कलेक्टर अपने मेटाडेटा (हीप के बाहर, एक क्षेत्र में जो मुक्त नहीं है) को स्टॉप-द-वर्ल्ड रोकथाम के दौरान पढ़ता है, और उन मेटाडेटा को स्वैप में हो सकता है। मैंने MGLRU सक्षम करके कर्नल 6.8 का उपयोग करके Hetzner पर एक सिमुलेशन चलाया। मीडियन रोकथाम लगभग 51 माइक्रोसेकंड थी। NVMe पर मेटाडेटा के साथ, सबसे खराब रोकथाम 40 मिलीसेकंड थी। यह जाँचने के लिए कि वह 40 मिलीसेकंड कहाँ गई, मैंने एक छोटा bpf स्क्रिप्ट लिखा जो स्टॉप-द-वर्ल्ड के दौरान पेज फॉल्ट की गिनती करता है। यह सबसे खराब था: 39902 माइक्रोसेकंड, उसके दौरान 228 फॉल्ट, 39013 माइक्रोसेकंड फॉल्ट में। उन 40 मिलीसेकंड में से 39 मिलीसेकंड 228 पेज फॉल्ट में खर्च हुईं। ये फॉल्ट GC के बुककीपिंग के भीतर हुए। यह एक संभावित विफलता मोड है। Go के GC को दो बिंदुओं पर दुनिया रोकनी पड़ती है: जब वह स्वीप समाप्ति करता है, और जब वह मार्क समाप्ति करता है। हमने 30 मिनट में 312 ऐसी रोकथाम देखीं। इसलिए यहीं कारण है कि यह होता है: रनटाइम उन पृष्ठों को आवंटित करता है। वे मुक्त नहीं होते, बल्कि पुन: उपयोग किए जाते हैं। इन पृष्ठों को GC चक्र में पढ़ा जाता है। क्योंकि कर्नल पृष्ठों को उम्र के आधार पर निकालता है, इसलिए वह सबसे कम हाल में एक्सेस किए गए पृष्ठों को स्वैप भेज देता है। GC चलता है, दुनिया रोकता है, उन पृष्ठों को पढ़न की कोशिश करता है, लेकिन अब हमें एक प्रमुख पेज फॉल्ट मिलता है। कर्नल को PTE पढ़ने की आवश्यकता है, फिर do_swap_page को कॉल करना है, एक नई फ्रेम ढूँढनी है, उसे cgroup में चार्ज करना है, पृष्ठों को पढ़ना है, bio जमा करना है, डिस्क का इंतजार करना है और फिर उन्हें मेमरी में वापस रखना है - बस इतना ही। शुरुआती दिखावट में वह 40 मिलीसेकंड बेमार नहीं लगते। लेकिन हम स्टॉप-द-वर्ल्ड रोकथाम की बात कर रहे हैं। उन 40 मिलीसेकंड का मतलब है कि सब कुछ रुक गया है - Go के शब्दों में, हर P रुक गया है, इसलिए उदाहरण के लिए, अगर एक goroutine I/O का इंतजार कर रहा था, तो उस रोकथाम के दौरान I/O वापस आ सकता है और कोई उसे संभालने वाला नहीं होगा। 40 मिलीसेकंड मीडियन रोकथाम का 800 गुना है। यह परीक्षण के दौरान हर मेमरी स्पाइक के साथ दो या तीन बार होता है। यह बहुत अधिक है।
स्रोत: Hacker News · HeadlinesBriefing द्वारा सारांशित