SSD Nodes Learn 🎉 VPS $5.50/माह से
गाइड Matt Connorलेखक: Matt Connor

systemd unit में CPU और Memory limit कैसे सेट करें

Linux VPS पर systemd unit के जरिए MemoryMax और CPUQuota सेट करना सीखें। जानें कि कैसे MemoryHigh और TasksMax का उपयोग करके आप अपनी प्रोसेस को कंट्रोल कर सकते हैं और OOM kill को समझें।

systemd drop-in के साथ process memory और CPU को सीमित करना

आप Linux VPS पर किसी process की memory और CPU को उस unit में कुछ लाइनें जोड़कर सीमित कर सकते हैं जो उस process को चलाती है। MemoryMax= memory पर एक सख्त सीमा (hard ceiling) है। CPUQuota= processor time पर एक सीमा है। दोनों को cgroup v2 (control groups, version 2) द्वारा लागू किया जाता है, जो कि kernel का वह feature है जिसका उपयोग systemd पहले से ही server पर मौजूद हर service का हिसाब रखने के लिए करता है।

sudo systemctl edit myapp.service

यह टिप्पणियों (comments) में निर्देशों के साथ एक drop-in file खोलता है। उनके ऊपर इसे जोड़ें:

[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax

systemctl show को आपके द्वारा दिए गए numbers को kernel की अपनी इकाइयों (units) में वापस दोहराना चाहिए: MemoryMax=805306368 और CPUQuotaPerSecUSec=800ms। यदि यह MemoryMax=infinity प्रिंट करता है, तो drop-in लोड नहीं हुआ है। जाँचें कि file /etc/systemd/system/myapp.service.d/override.conf पर स्थित है, और यह [Service] header के साथ शुरू होती है, क्योंकि बिना किसी section वाली settings line के कारण systemd Assignment outside of section. Ignoring. लॉग करता है और service को बिना किसी सीमा के शुरू कर देता है।

इस guide का शेष भाग यह बताता है कि उन numbers को कैसे चुनें, और उन्हें सेट करने के बाद भी क्या गलत हो सकता है।

रनअवे प्रोसेस के कारण VPS के फ्रीज होने का कारण, जबकि मेमोरी पूरी तरह नहीं भरती

जब कोई प्रोसेस अपनी हार्ड मेमोरी लिमिट तक पहुँचती है, तो वह लगभग एक सेकंड में समाप्त हो जाती है और सर्विस रीस्टार्ट हो जाती है। यह एक सामान्य स्थिति है। खराब स्थिति वह है जहाँ कुछ भी समाप्त नहीं होता: मशीन ping का जवाब देती है, SSH कनेक्शन स्वीकार करता है, लेकिन शेल प्रॉम्प्ट कभी नहीं आता। मशीन चालू और व्यस्त है, लेकिन वह कोई उपयोगी काम नहीं कर रही है।

इसका तंत्र यहाँ दिया गया है, क्योंकि यह स्पष्ट नहीं है। जब फ्री मेमोरी कम हो जाती है, तो कर्नेल नई मेमोरी देने के बजाय पेजों को पुनः प्राप्त (reclaim) करता है। पुनः प्राप्त करने के लिए सबसे सस्ते पेज फाइल-बैक्ड होते हैं, और पेज कैश में चल रहे हर प्रोग्राम का एक्जीक्यूटेबल कोड होता है। इसलिए कर्नेल sshd के टेक्स्ट पेजों को हटा देता है, और अगला निर्देश जिसे sshd चलाता है, वह एक पेज फॉल्ट है जिसे उन बाइट्स को स्टोरेज से वापस पढ़ना पड़ता है। हर प्रोसेस अंततः चलने के बजाय डिस्क का इंतजार करती है। वही पेज एक लूप में बाहर जाते हैं और वापस आते हैं, जिसे थ्रैशिंग (thrashing) कहा जाता है।

VPS पर लैपटॉप की तुलना में दो चीजें इसे और खराब बना देती हैं। स्टोरेज अक्सर नेटवर्क से जुड़ी या साझा होती है, इसलिए प्रत्येक फॉल्ट में स्थानीय NVMe डिवाइस की तुलना में अधिक मिलीसेकंड लगते हैं। और कर्नेल समय नहीं मापता, वह विफलता मापता है: जब तक रीक्लेम पेज वापस देता रहता है, चाहे वह कितना भी धीमा क्यों न हो, कर्नेल का मानना है कि वह प्रगति कर रहा है और वह आउट ऑफ मेमोरी (OOM) किलर को कॉल नहीं करता है। कोई मशीन किसी भी चीज के मारे जाने से पहले कई मिनटों तक उस स्थिति में रह सकती है।

आप इसे होते हुए देख सकते हैं। कर्नेल Linux 4.20 और उसके बाद के वर्ज़न पर प्रेशर स्टॉल इंफॉर्मेशन (PSI) एक्सपोर्ट करता है:

cat /proc/pressure/memory
cat /proc/pressure/io
some avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233

full लाइन महत्वपूर्ण है। full avg10=48.15 का मतलब है कि पिछले दस सेकंड में, बॉक्स पर हर चलने योग्य टास्क का 48% समय मेमोरी कार्य की प्रतीक्षा में रुका हुआ था, इसलिए कुछ भी नहीं चला। एक स्वस्थ सर्वर full पर शून्य के करीब रहता है। 10 से ऊपर यह इंसान को धीमा महसूस होता है, और 40 या उससे अधिक वह स्थिति है जिसे लोग फ्रीज होना कहते हैं।

यही कारण है कि केवल एक लिमिट होना कोई गारंटी नहीं है। MemoryHigh= के तहत रखी गई यूनिट को समाप्त करने के बजाय थ्रॉटल (throttle) किया जाता है, इसलिए वह जीवित रहती है और धीमी बनी रहती है, और कोई भी इसे रीस्टार्ट नहीं करता क्योंकि systemd के दृष्टिकोण से यह कभी विफल नहीं हुई। एक कैप्ड यूनिट जिसे स्वैप करने की अनुमति है, वह ऐसे रीड और राइट उत्पन्न करती है जो उस यूनिट पर चार्ज किए जाते हैं लेकिन एक साझा डिवाइस द्वारा सर्व किए जाते हैं, इसलिए यह बॉक्स पर मौजूद हर दूसरी सर्विस के लिए /proc/pressure/io को बढ़ा सकती है। लिमिट यह तय करती है कि कमी की कीमत कौन चुकाएगा, और वे क्षमता पैदा नहीं कर सकतीं।

जांचें कि आपका VPS cgroup v2 चला रहा है

stat -fc %T /sys/fs/cgroup

cgroup2fs एकीकृत पदानुक्रम (unified hierarchy) है, जिसकी आवश्यकता नीचे दी गई प्रत्येक सेटिंग के लिए होती है। tmpfs का अर्थ है कि सिस्टम पुराने v1 लेआउट के साथ बूट हुआ है, जहाँ MemoryHigh= और MemorySwapMax= मौजूद नहीं होते और प्रति-यूनिट OOM व्यवहार अलग होता है। Ubuntu 22.04 और उसके बाद के संस्करण, तथा Debian 11 और उसके बाद के संस्करण, डिफ़ॉल्ट रूप से v2 का उपयोग करते हैं। एक पुरानी इमेज, या systemd.unified_cgroup_hierarchy=0 के साथ बूट किया गया कर्नल, इसका उपयोग नहीं करता है।

cgroup v2 पर systemd डिफ़ॉल्ट रूप से प्रत्येक यूनिट के लिए मेमोरी अकाउंटिंग चालू कर देता है, इसलिए आंकड़े पहले से ही वहां मौजूद होते हैं:

systemd-cgtop -m

यह मेमोरी उपयोग के आधार पर cgroups को क्रमबद्ध करता है, जो यह पता लगाने का सबसे तेज़ तरीका है कि "इस बॉक्स की मेमोरी कौन खा रहा है", जबकि सिस्टम अभी भी प्रतिक्रिया देने में सक्षम है। यदि सर्वर नया है, तो नए VPS पर पहले दस मिनट में किए जाने वाले अकाउंट और फायरवाल संबंधी कार्य इससे पहले आते हैं।

MemoryHigh थ्रॉटलिंग करता है। MemoryMax प्रोसेस को समाप्त (kill) कर देता है।

इन दो मेमोरी सेटिंग्स के बीच का अंतर यह तय करता है कि विफलता (failure) किस तरह की होगी।

  • MemoryHigh= एक सॉफ्ट कैप है। इसके ऊपर जाने पर कर्नल उस cgroup से आक्रामक रूप से मेमोरी वापस लेता है और जानबूझकर उसके एलोकेशन को धीमा कर देता है। उपयोग इस संख्या से आगे जा सकता है, और कोई भी प्रोसेस समाप्त नहीं होती है।
  • MemoryMax= एक हार्ड कैप है। जब इसके अंतर्गत मेमोरी की मांग पूरी नहीं हो पाती है, तो OOM killer उस cgroup के भीतर चलता है और उस यूनिट की अपनी किसी एक प्रोसेस को समाप्त कर देता है।

यह दूसरा भाग ही वह मुख्य कारण है कि आप जिस पर पूरी तरह भरोसा नहीं करते, उस पर MemoryMax= सेट करें। यदि कोई कैप नहीं है, तो मेमोरी की कमी पूरी मशीन की समस्या बन जाती है, और ग्लोबल OOM killer अपना शिकार oom_score के आधार पर चुनता है, जिसका अर्थ आमतौर पर सबसे बड़ी प्रोसेस होता है। सबसे बड़ी प्रोसेस अक्सर आपका डेटाबेस होता है, न कि वह स्क्रिप्ट जिसमें मेमोरी लीक हुई है। कैप होने पर, kill उसी यूनिट के भीतर होती है जिसने समस्या पैदा की है।

दोनों को सेट करें, जिसमें MemoryHigh= को MemoryMax= से लगभग 20 से 30 प्रतिशत नीचे रखें। यह अंतर एक चेतावनी क्षेत्र है: एक धीमा लीक High को पार करता है और एक ऐसी सर्विस के रूप में दिखाई देता है जो धीमी हो गई है, जबकि अचानक आया स्पाइक सीधे Max को पार कर जाता है और सर्विस समाप्त हो जाती है।

प्रतिशत मानों को इंस्टॉल की गई फिजिकल मेमोरी के आधार पर पढ़ा जाता है, इसलिए 4 GB के प्लान पर MemoryMax=25% का मतलब 1 GB है और प्लान का आकार बदलने के बाद भी यह मशीन का एक चौथाई हिस्सा बना रहता है। MemorySwapMax=0 उस यूनिट को पूरी तरह से स्वैप (swap) से बाहर रखता है, जो एक लंबी सुस्ती को एक तेज़ और स्पष्ट kill में बदल देता है।

एक कैप के साथ-साथ एक रीस्टार्ट पॉलिसी की आवश्यकता होती है, अन्यथा kill के बाद आपके पास केवल एक रुकी हुई सर्विस रह जाएगी।

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Restart=on-failure
RestartSec=5s

StartLimit* को [Unit] में और Restart= को [Service] में होना चाहिए। यदि आप किसी को गलत सेक्शन में रखते हैं, तो systemd उसे अनदेखा कर देता है। पांच मिनट में पांच बार रीस्टार्ट होना एक छोटी समस्या के बजाय एक लीक है, इसलिए उसके बाद systemd प्रयास करना छोड़ देता है और यूनिट को failed स्थिति में छोड़ देता है। यह वही स्थिति है जिसे आप बाद में देखना चाहेंगे, बजाय इसके कि एक क्रैश लूप समस्या को छिपाए रखे।

CPUQuota के साथ CPU को सीमित करें, या CPUWeight के साथ इसे साझा करें

CPUQuota= एक CPU पर उपलब्ध समय का प्रतिशत लेता है। CPUQuota=50% एक कोर का आधा हिस्सा है। CPUQuota=200% दो कोर के बराबर है, जिसे unit अपनी इच्छा अनुसार कई threads में फैला सकती है। 2 vCPU वाले प्लान पर, CPUQuota=200% पूरी मशीन के बराबर है।

CPUWeight= अधिकांश services के लिए बेहतर डिफ़ॉल्ट है। यह 1 से 10000 तक का एक सापेक्ष हिस्सा (relative share) है, और kernel का डिफ़ॉल्ट मान 100 है। यह केवल तब प्रभावी होता है जब संसाधन के लिए प्रतिस्पर्धा हो: लोड होने पर CPUWeight=20 पर चल रहा बैकअप जॉब 100 पर चल रहे वेब सर्वर को प्राथमिकता देता है, और जब सर्वर खाली (idle) होता है तो यह पूरे संसाधन का उपयोग कर सकता है। एक हार्ड कोटा उस खाली क्षमता को व्यर्थ कर देता है।

ईमानदार रहें कि CPU लिमिट आपको क्या देती है। CPU-बाउंड प्रक्रिया शायद ही कभी Linux को फ्रीज करती है, क्योंकि scheduler हर किसी को समय देता रहता है। मेमोरी वह चीज है जो सर्वर को डाउन कर देती है। जब आप एक अनुमानित सीमा (predictable ceiling) चाहते हैं, तो CPUQuota= का उपयोग करें, उदाहरण के लिए किसी ऐसे बिल्ड या एजेंट पर जो अन्यथा एक घंटे तक पूरी क्षमता पर चलता रहेगा। उस प्रकार के वर्कलोड का आकार निर्धारित करना अपने आप में एक अलग प्रश्न है, जिसे coding agent VPS को कितनी RAM और CPU की आवश्यकता है में कवर किया गया है।

यदि CPU व्यस्त दिखाई देता है जबकि आपकी कोई भी प्रक्रिया ज्यादा काम नहीं कर रही है, तो इसका कारण hypervisor के दूसरी तरफ हो सकता है। यह noisy neighbour से CPU steal time है, और आपके द्वारा सेट किया गया कोई भी कोटा इसे नहीं बदलेगा।

TasksMax एक fork loop को रोकता है

TasksMax= वह संख्या है जो दर्शाती है कि एक unit में अधिकतम कितने processes और threads हो सकते हैं। Threads की भी गिनती होती है, इसलिए Java या Go service को process list के अनुमान से अधिक headroom की आवश्यकता होती है। यह एक ऐसी script के खिलाफ सबसे सस्ता बचाव है जो loop में fork करती है, क्योंकि fork प्रक्रिया unit के भीतर ही विफल हो जाती है, बजाय इसके कि पूरे box के process IDs समाप्त हो जाएं।

TasksMax=128

जब कोई unit इस limit तक पहुँचती है, तो kernel cgroup का नाम बताते हुए एक line log करता है:

cgroup: fork rejected by pids controller in /system.slice/myapp.service

Program आमतौर पर स्वयं fork: retry: Resource temporarily unavailable की सूचना देता है। यह जाँचने के लिए कि manager डिफ़ॉल्ट रूप से क्या लागू करता है, systemctl show -p DefaultTasksMax का उपयोग करें।

systemd-run के साथ एक बार चलने वाले जॉब को सीमित करना

इसके लिए आपको किसी unit file की आवश्यकता नहीं है। systemd-run एक कमांड के चारों ओर एक अस्थायी (transient) unit बनाता है।

sudo systemd-run --scope -p MemoryMax=1G -p MemorySwapMax=0 -p CPUQuota=50% -p TasksMax=64 ./import-data.sh

--scope कमांड को आपके टर्मिनल में चलाता है, और पहले Running scope as unit: run-r7c1a....scope प्रिंट करता है। आउटपुट आपकी स्क्रीन पर रहता है और कमांड के समाप्त होते ही सीमाएं (limits) हट जाती हैं। systemd.resource-control की कोई भी प्रॉपर्टी -p के बाद काम करती है।

लंबे समय तक चलने वाले जॉब के लिए, --scope को हटा दें और उसे एक नाम दें। यह बैकग्राउंड में एक अस्थायी सर्विस के रूप में चलता है और journal में लॉग करता है:

sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -f

जब आप root नहीं होते हैं, तब भी यही विकल्प --user के साथ काम करते हैं, हालांकि आपके user manager के पास केवल वही controllers होते हैं जो उसे सौंपे (delegate) गए हैं, इसलिए वहां कोई प्रॉपर्टी अस्वीकार की जा सकती है। यदि ऐसा होता है, तो इसे sudo के साथ चलाएं। जब किसी जॉब को स्थायी स्थान मिल जाता है, तो सेटिंग्स बिना किसी बदलाव के एक वास्तविक unit में चली जाती हैं: देखें running a script as a systemd service and timer

Swap का प्रश्न, ईमानदारी से उत्तर

Swap विफलता को रोकने के बजाय उसके स्वरूप को बदल देता है।

यदि swap न हो, तो memory leak होने पर सीमा तक पहुँचते ही कोई प्रक्रिया कुछ ही सेकंड में समाप्त हो जाती है। यह outage स्पष्ट, संक्षिप्त होती है और बाद में journal में आसानी से पढ़ी जा सकती है। Swap होने पर, kernel ठंडे anonymous pages को disk पर लिख देता है और समय खरीद लेता है। यदि प्रक्रिया का उपयोग स्थिर होने वाला था, तो swap आपको बचा लेता है। यदि यह एक runaway प्रक्रिया है, तो swap पाँच सेकंड की outage को बीस मिनट के ठहराव में बदल देता है। यह ठहराव अधिक बुरा है, क्योंकि एक मृत प्रक्रिया के बाद भी आपको एक कार्यशील shell मिल जाता है, जबकि thrashing कर रहा box कोई प्रतिक्रिया नहीं देता।

swapon --show
free -h

एक छोटे VPS पर व्यावहारिक समाधान यह है: उन pages के लिए एक छोटा swap file रखें जो एक बार allocate होने के बाद दोबारा उपयोग नहीं होते, और उन units पर MemorySwapMax=0 सेट करें जिन्हें आप खोने के लिए तैयार हैं। महत्वपूर्ण services अपना swap बनाए रखती हैं। अनिश्चित services जल्दी ही सीमा तक पहुँचकर restart हो जाती हैं।

vm.swappiness को कम करना एक कमजोर उपाय है, और यह जानना महत्वपूर्ण है कि ऐसा क्यों है। यह केवल page cache को हटाने और anonymous pages को swap करने के बीच के संतुलन को बदलता है, और दोनों ही स्थितियों में बाद में disk read की लागत चुकानी पड़ती है। यह केवल यह बदलता है कि कौन से pages thrash होंगे, यह नहीं कि box thrash होगा या नहीं।

स्टॉल (stall) से पहले OOM डेमन का सक्रिय होना

कर्नेल (kernel) तब तक प्रतीक्षा करता है जब तक कि मेमोरी रिकलेम (reclaim) पूरी तरह विफल न हो जाए। एक छोटे VPS पर, यह प्रतीक्षा अवधि ही वह समय है जब आप मशीन का नियंत्रण खो देते हैं। दो यूजरस्पेस डेमन स्वयं मेमोरी की निगरानी करके और जल्दी प्रोसेस को समाप्त (kill) करके इस समस्या को हल करते हैं।

earlyoom उपलब्ध मेमोरी और फ्री स्वैप (swap) पर नजर रखता है। जब इनमें से कोई भी निर्धारित सीमा से नीचे गिरता है, तो यह सबसे अधिक स्कोर वाली प्रोसेस को समाप्त कर देता है।

sudo apt install earlyoom
systemctl status earlyoom

Debian और Ubuntu पैकेज इंस्टॉलेशन के समय ही सर्विस को स्टार्ट कर देते हैं। इसके विकल्प /etc/default/earlyoom में स्थित होते हैं:

EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid '^(sshd|systemd)$' --prefer '^(node|python3)$'"

-m PERCENT उपलब्ध मेमोरी का न्यूनतम स्तर निर्धारित करता है और -s PERCENT फ्री स्वैप का न्यूनतम स्तर, जो डिफ़ॉल्ट रूप से 10 प्रतिशत होता है। प्रत्येक जोड़ी में दूसरी संख्या SIGKILL का बिंदु है: जब आप पहले मान से नीचे गिरते हैं तो earlyoom SIGTERM भेजता है, और दूसरे मान से नीचे गिरने पर SIGKILL भेजता है, जो डिफ़ॉल्ट रूप से पहले मान का आधा होता है। बदलाव लागू करने के लिए sudo systemctl restart earlyoom का उपयोग करें, और यह देखने के लिए कि इसने किस प्रोसेस को समाप्त किया और वह कितनी मेमोरी ले रही थी, journalctl -u earlyoom को पढ़ें।

systemd-oomd दूसरा विकल्प है। इसका मैनुअल पेज इसे "एक सिस्टम सर्विस के रूप में वर्णित करता है जो कर्नेल स्पेस में OOM होने से पहले निगरानी करने और सुधारात्मक कार्रवाई करने के लिए cgroups-v2 और प्रेशर स्टॉल इंफॉर्मेशन (PSI) का उपयोग करती है"। यह एकल प्रोसेस के बजाय पूरे cgroups पर कार्य करता है, इसलिए यह किसी एक भटकी हुई चाइल्ड प्रोसेस के बजाय पूरी यूनिट को समाप्त करता है। यूनिट्स ManagedOOMMemoryPressure=kill या ManagedOOMSwap=kill के साथ इसमें शामिल होती हैं, और थ्रेशोल्ड (thresholds) /etc/systemd/oomd.conf में निर्धारित होते हैं।

systemctl status systemd-oomd
oomctl

oomctl यह प्रिंट करता है कि वर्तमान में क्या मॉनिटर किया जा रहा है। सर्वर इमेज पर अक्सर कुछ भी मॉनिटर नहीं हो रहा होता है, क्योंकि यह सेटिंग प्रति यूनिट ऑप्ट-इन (opt-in) होती है। किसी एक डेमन को चुनें और वहीं रुक जाएं। दोनों को एक साथ चलाने का मतलब है कि दो चीजें शिकार चुनने की दौड़ में लगी हैं, जिससे किसी भी किल (kill) का कारण पता लगाना कठिन हो जाता है।

कौन सी unit जिम्मेदार थी?

kernel से शुरुआत करें, क्योंकि यह अपने द्वारा किए गए प्रत्येक kill को रिकॉर्ड करता है।

journalctl -k --grep "Killed process" --since "2 hours ago"

global OOM killer द्वारा किया गया kill कुछ इस तरह दिखता है:

Out of memory: Killed process 4127 (node) total-vm:2731084kB, anon-rss:1874232kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:4212kB oom_score_adj:0

anon-rss वह memory है जिसे process ने मरने के समय RAM में होल्ड किया था, यहाँ लगभग 1.8 GB है। कोष्ठक में दिए गए नाम को संदेह की दृष्टि से देखें। यह वह victim है जिसे kernel ने चुना है, और kernel सबसे बड़ी process को चुनता है, जो हमेशा वह नहीं होती जिसने कमी पैदा की हो।

cgroup limit से होने वाले kill का prefix अलग होता है, और उसके ऊपर प्रिंट की गई रिपोर्ट उस cgroup का नाम बताती है जो अपनी सीमा तक पहुँच गया था:

Memory cgroup out of memory: Killed process 8811 (python3) total-vm:1044320kB, anon-rss:769112kB, file-rss:0kB, shmem-rss:0kB, UID:998 pgtables:1720kB oom_score_adj:0

वह prefix ही निदान का मुख्य हिस्सा है। Memory cgroup out of memory का अर्थ है कि एक unit उस MemoryMax= तक पहुँच गई जो आपने उसे दिया था और बाकी box ठीक था। एक साधारण Out of memory का अर्थ है कि पूरी machine में memory खत्म हो गई थी, इसलिए आपकी सीमाएँ या तो गायब थीं या इतनी अधिक थीं कि उनका योग बहुत ज्यादा हो गया।

फिर systemd से पूछें कि उसने क्या देखा:

systemctl status myapp.service
journalctl -u myapp.service -n 50
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.

systemctl status एक पंक्ति में वही बात कहता है, जैसे Active: failed (Result: oom-kill)

cgroup counters तीसरा स्रोत हैं, और एकमात्र ऐसा स्रोत जो throttling को रिकॉर्ड करता है, जो कभी भी log line उत्पन्न नहीं करता:

cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peak
low 0
high 4213
max 118
oom 12
oom_kill 12

high गिनता है कि unit को कितनी बार MemoryHigh= के ऊपर धकेला गया और throttle किया गया। max गिनता है कि यह कितनी बार hard cap तक पहुँची, और oom_kill उन processes को गिनता है जो वास्तव में kill की गईं। oom_kill 0 के साथ एक बड़ा high पहले बताया गया silent case है: service चल रही है, बहुत धीमी हो गई है, और उसने किसी को कोई failure रिपोर्ट नहीं किया है। memory.peak (Linux 5.19 और नए) उस उच्चतम usage को रखता है जहाँ तक cgroup पहुँचा था, जो कि MemoryMax= को size करने के लिए उपयोग की जाने वाली संख्या है। unit के restart होने पर दोनों files reset हो जाती हैं, क्योंकि systemd cgroup को फिर से बनाता है।

इन सबके नीचे एक पूर्व-शर्त है। यदि /var/log/journal मौजूद नहीं है, तो journal RAM में रहता है, और box को recover करने के लिए आवश्यक reboot के बाद हर line गायब हो जाती है।

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-boots

journalctl --list-boots का वर्तमान boot से अधिक दिखाना यह दर्शाता है कि history अब सुरक्षित है, इसलिए journalctl -k -b -1 आपको उस boot के kernel messages दिखा सकता है जो crash हुआ था।

छोटे VPS के लिए एक शुरुआती बिंदु

2 GB के प्लान पर, kernel और page cache के लिए 300 से 400 MB जगह छोड़ दें। सुनिश्चित करें कि सभी caps का योग कुल 2 GB तक न पहुँचे, क्योंकि हर unit एक ही समय पर peak load ले सकती है। सबसे महत्वपूर्ण service को सबसे बड़ा हिस्सा दें, और उसके आसपास की अन्य speculative services को सीमित रखें।

[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5s

सर्वर का एक्सेस बनाए रखना एक अतिरिक्त सेटिंग के लायक है। ssh.service के लिए एक drop-in में OOMScoreAdjust=-500 सेट करने से global OOM killer द्वारा आपके SSH daemon को victim के रूप में चुने जाने की संभावना काफी कम हो जाती है। यही वह अंतर है जो सर्वर को ठीक करने और control panel से उसे reboot करने के बीच का निर्णय लेता है। यह केवल kernel द्वारा victim चुनने की प्राथमिकता को बदलता है। यह stall की अवधि को कम नहीं करता है।

Containers अपने स्वयं के cgroups में चलते हैं, जिन्हें आपके unit files के बजाय container runtime द्वारा बनाया जाता है, इसलिए docker.service पर लगाई गई सीमा एक container पर लागू नहीं होती है। MemoryMax= और CPUQuota= के प्रति-container समकक्षों को Docker Compose में memory और CPU सीमाएं सेट करना में कवर किया गया है।

FAQ

Runaway process के दौरान मेरा VPS freeze क्यों हो गया, उसे kill क्यों नहीं किया गया?

इसका कारण यह है कि kernel यह देखता है कि क्या reclaim ने pages वापस किए हैं, न कि इसमें कितना समय लगा है। जब memory कम होती है, तो यह page cache को हटा देता है, जिसमें चल रहे programs के executable pages भी शामिल होते हैं, और फिर अगली instruction पर उन्हें वापस read करता है। सब कुछ storage पर निर्भर हो जाता है और तकनीकी रूप से कोई allocation fail नहीं होता, इसलिए OOM killer कभी active नहीं होता। जब ऐसा हो रहा हो, तो /proc/pressure/memory की जाँच करें: यदि full avg10 40 से ऊपर है, तो इसका मतलब है कि पिछले दस seconds में लगभग कोई भी task run नहीं हो पाया। earlyoom जैसा userspace daemon इस स्थिति तक पहुँचने से पहले ही process को kill कर देता है।

MemoryHigh और MemoryMax में क्या अंतर है?

MemoryHigh= एक soft cap है जो throttle करता है। Kernel unit से memory reclaim करने के लिए दबाव डालता है और उसके allocations को धीमा कर देता है, लेकिन usage इस संख्या से अधिक हो सकता है और कुछ भी kill नहीं होता। MemoryMax= एक hard cap है: इसके तहत यदि कोई allocation पूरा नहीं हो पाता, तो वह उस unit के cgroup के भीतर OOM killer को invoke कर देता है। इससे पूरी machine की सबसे बड़ी process के बजाय वही process kill होती है जिसने समस्या पैदा की है। MemoryHigh= को MemoryMax= से नीचे set करें और उनके बीच के अंतर को warning zone की तरह इस्तेमाल करें।

मैं यह कैसे पता लगाऊं कि OOM killer ने किस service को hit किया है?

journalctl -k --grep "Killed process" --since "2 hours ago" run करें। यदि कोई line Memory cgroup out of memory से शुरू होती है, तो इसका मतलब है कि एक unit ने अपना MemoryMax= hit किया है, जबकि साधारण Out of memory का मतलब है कि पूरी machine की memory खत्म हो गई थी। इसके बाद journalctl -u <unit> -n 50 run करें और Failed with result 'oom-kill' को देखें। यदि आपके server पर /var/log/journal मौजूद नहीं है, तो journal RAM में था और reboot के साथ ही सबूत मिट गए, इसलिए अगली घटना से पहले वह directory बना लें।

क्या मुझे छोटे VPS पर swap जोड़ना चाहिए?

एक छोटा swap file उन cold pages के लिए उपयोगी है जो एक बार allocate होकर फिर कभी इस्तेमाल नहीं होते। यह runaway process के मामले में मदद नहीं करता: यह kill होने की प्रक्रिया को delay करता है और एक छोटे outage को एक लंबे stall में बदल देता है, जिसे आप login करके ठीक भी नहीं कर पाएंगे। Swap को सीमित रखें और जिन units को आप खोने के लिए तैयार हैं, उन पर MemorySwapMax=0 set करें, ताकि वे अपनी सीमा तक पहुँचते ही जल्दी restart हो जाएं और महत्वपूर्ण services अपना swap सुरक्षित रख सकें।

क्या मैं unit file लिखे बिना किसी command को limit कर सकता हूँ?

हाँ। sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh उस command को आपके terminal में एक transient scope के भीतर उन limits के साथ run करता है, और command के exit होते ही वे limits खत्म हो जाती हैं। systemd.resource-control की हर property -p के बाद उपलब्ध होती है, इसलिए MemorySwapMax=, TasksMax= और CPUWeight= वहाँ भी काम करते हैं। --scope को हटा दें और job को background में run करने के लिए --unit=name जोड़ें, जिससे उसका output journal में दिखाई दे।