गायब स्वैप फ़ाइल जिसने हमारे सर्वर जमा दिए
24 सितंबर को हमारे एक प्रोडक्शन सर्वर ने जवाब देना बंद कर दिया। लोड बैलेंसर ने उसे अस्वस्थ चिह्नित किया और हमारा रिमोट-कमांड टूल उस तक नहीं पहुँच सका, लेकिन कुछ क्रैश नहीं हुआ था और मेमोरी ज़्यादा इस्तेमाल करने के कारण कोई प्रोसेस नहीं मरा था। सर्वर थ्रैश कर रहा था: बिना स्वैप स्पेस के, और कंटेनरों को मशीन की मेमोरी से ज़्यादा इस्तेमाल की अनुमति होने पर, Linux अपना समय उन फ़ाइलों को हटाने और दोबारा पढ़ने में बिता रहा था जो चलने के लिए ज़रूरी थीं, और लगभग कोई प्रगति नहीं कर रहा था। हर सर्वर पर 2 GB की स्वैप फ़ाइल जोड़ने से यह ठीक हो गया। ज़्यादा दिलचस्प सबक हेल्थ चेक्स के बारे में था।
"अटकना" कैसा दिखा
- लोड बैलेंसर के हेल्थ चेक उस सर्वर पर टाइम आउट हो गए।
- AWS Systems Manager के ज़रिए भेजी गई कमांड "Pending" में अटकी रहीं और कभी नहीं चलीं।
- लॉग में कोई आउट-ऑफ़-मेमोरी किल नहीं था। कोई प्रोसेस नहीं मरा।
एक हफ़्ता पहले हमने प्रोडक्शन को छोटे ARM सर्वरों पर ले गए थे, हर एक में 2 GB RAM के साथ। हमारा स्टेजिंग सर्वर, जो और भी छोटा है, डिप्लॉय के दौरान एक कंटेनर इमेज अनपैक करते समय उसी तरह जम गया था।
बिना मेमोरी एरर के क्यों?
Linux केवल तभी प्रोसेस मारता है जब उसके पास वास्तव में वापस लेने लायक मेमोरी खत्म हो जाए। उस बिंदु से पहले, वह पेज कैश से मेमोरी वापस लेता है, यानी फ़ाइलों की वे कॉपियाँ जो वह RAM में रखता है, जिसमें हर चलते प्रोग्राम का कोड भी शामिल है।
हमारे सर्वरों पर कंटेनरों की संयुक्त मेमोरी सीमाएँ लगभग 3.3 GB जुड़ती थीं, जबकि मशीन पर लगभग 1.8 GB उपयोगी थे। आमतौर पर वे सब एक साथ अपनी सीमा तक नहीं जाते। जब जाते, तो कर्नल कैश से प्रोग्राम कोड हटाता रहता ताकि जगह बने, और जैसे ही वह कोड दोबारा चलता, उसे डिस्क से वापस पढ़ना पड़ता। यह चक्र, थ्रैशिंग, मशीन को लगभग पूरी तरह अप्रतिक्रियाशील बना सकता है, जबकि तकनीकी रूप से मेमोरी कभी खत्म नहीं होती। इसलिए कुछ मरता नहीं, और कुछ ठीक नहीं होता।
स्वैप के बिना कर्नल के पास वह मेमोरी रखने की जगह नहीं होती जो आवंटित है पर कम इस्तेमाल होती है, इसलिए उसे कैश से लेना पड़ता है।
समाधान
सर्वर बूटस्ट्रैप स्क्रिप्ट अब हर बार सर्वर बूट या रिपेयर होने पर एक 2 GB स्वैप फ़ाइल बनाती है, कम स्वैपिनेस सेटिंग (10) के साथ। कम स्वैपिनेस का अर्थ है कि कर्नल अब भी कार्यरत मेमोरी RAM में रखना पसंद करता है, पर कम छुए गए पेज डिस्क पर जा सकते हैं, बजाय इसके कि प्रोग्राम कोड को कैश से बाहर धकेला जाए। हमने इसे दोनों प्रोडक्शन सर्वरों और स्टेजिंग पर लाइव लागू किया, और डिप्लॉय पाइपलाइन हर प्रोडक्शन डिप्लॉय पर बूटस्ट्रैप स्क्रिप्ट दोबारा प्रकाशित करती है, ताकि बदला गया सर्वर भी इसे पाए।
जो हमें ज़्यादा परेशान कर गया: अस्वस्थ सर्वर वहीं रहा
हमारा सर्वर ग्रुप EC2 हेल्थ चेक इस्तेमाल करता था, यानी "क्या मशीन चल रही है?", न कि लोड बैलेंसर हेल्थ चेक, यानी "क्या यह अनुरोधों का जवाब दे रही है?"। तो जिस सर्वर को लोड बैलेंसर छोड़ चुका था, ग्रुप के लिए वह अब भी स्वस्थ था। उसे कभी बदला नहीं जाता। प्रोडक्शन चुपचाप एक ही सर्वर पर चलता रहता, जब तक कोई इसे नोटिस करके दूसरे को रीबूट न करता।
ग्रुप को लोड बैलेंसर हेल्थ चेक पर बदलना स्पष्ट समाधान लगता है, पर हमारे लिए यह सीधा नहीं है: डिप्लॉय के दौरान सर्वर जानबूझकर अपने हेल्थ एंडपॉइंट पर एरर देते हैं ताकि बैलेंसर उन्हें ड्रेन कर दे (देखें हमने डिप्लॉय को शून्य 502 तक कैसे लाया)। जो ग्रुप उस संकेत पर सर्वर बदलता है, वह हर डिप्लॉय के बीच में उन्हें बदल देगा।
इसलिए अभी हमने अपनी डिप्लॉय चेकलिस्ट में एक कदम जोड़ा है: हर डिप्लॉय के बाद लोड बैलेंसर पर हर टार्गेट का हेल्थ जाँचें, न कि केवल यह कि डिप्लॉय जॉब पास हुआ। अगर किसी सर्वर को भेजी गई कमांड "Pending" में अटकी हैं, तो वह थ्रैश कर रहा है: उसे रीबूट करें; वह पहले ही रोटेशन से बाहर है।
हमने क्या सीखा
- "क्रैश न होना" का अर्थ "स्वस्थ" नहीं। थ्रैशिंग कोई एरर नहीं देती, केवल इतनी धीमी गति जो मशीन को जवाब देना बंद करने तक ले जाती है।
- छोटे सर्वरों को स्वैप चाहिए। थोड़ा ही सही, कम स्वैपिनेस के साथ, कर्नल को ठंडी मेमोरी रखने की जगह देता है।
- जिन मेमोरी सीमाओं का योग मशीन से ज़्यादा है, वह एक दाँव है। कभी-कभी आप उसे हारते हैं। जानें कि आपने कब दाँव लगाया है।
- जानें कि आपका ऑटोस्केलिंग किस हेल्थ चेक पर भरोसा करता है। जो हेल्थ चेक केवल यह पूछता है "क्या VM चालू है?", वह ऐसे सर्वर को नहीं बदलेगा जिसने काम करना बंद कर दिया हो।