SSD Nodes Learn Hosting plans →
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-08-23

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

systemd unit में MemoryMax और CPUQuota का उपयोग करके process को सीमित करना सीखें। जानें कि कैसे गलत कॉन्फ़िगरेशन से VPS फ्रीज हो सकता है और 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 पहले से ही box पर मौजूद हर 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 को आपकी संख्याओं को kernel की अपनी इकाइयों में वापस दोहराना चाहिए: 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 का शेष भाग यह है कि उन संख्याओं को कैसे चुनें, और एक बार सेट हो जाने के बाद भी क्या गलत हो सकता है।

रनअवे प्रोसेस VPS को फ्रीज क्यों कर देती है जबकि वह उसे भरती नहीं है

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

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

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

आप इसे होते हुए देख सकते हैं। kernel Linux 4.20 और उसके बाद के वर्ज़न पर pressure stall information (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 के आधार पर अपना शिकार चुनता है, जिसका अर्थ आमतौर पर सबसे बड़ी प्रोसेस होता है। सबसे बड़ी प्रोसेस अक्सर आपका डेटाबेस होता है, न कि वह स्क्रिप्ट जिसमें मेमोरी लीक हुई है। कैप के साथ, किल उसी यूनिट के भीतर होता है जिसने समस्या पैदा की है।

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

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

कुछ सर्विसेज आपको मापने के बजाय पहले से ही उनकी खपत तय करने देती हैं: एक Ollama यूनिट आपके द्वारा दिए गए कॉन्टेक्स्ट विंडो से अपना KV कैश आकार निर्धारित करती है, इसलिए RAM में num_ctx बढ़ाने की लागत क्या है को पढ़ें, इससे पहले कि आप किसी एक के लिए सीमा चुनें।

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

[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% दो कोर के बराबर है, जिसे यूनिट अपनी इच्छा अनुसार कई थ्रेड्स में फैला सकती है। 2 vCPU वाले प्लान पर, CPUQuota=200% पूरी मशीन के बराबर है।

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

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

यदि CPU व्यस्त दिखाई देता है जबकि आपकी कोई भी प्रक्रिया ज्यादा काम नहीं कर रही है, तो इसका कारण हाइपरवाइजर के दूसरी तरफ हो सकता है। यह 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 होने पर सीमा तक पहुँचते ही कुछ seconds में process बंद हो जाती है। यह outage स्पष्ट, संक्षिप्त होती है और बाद में journal में आसानी से पढ़ी जा सकती है। Swap होने पर, kernel ठंडे anonymous pages को disk पर लिख देता है और समय खरीद लेता है। यदि process का memory usage स्थिर होने वाला था, तो swap आपको बचा लेता है। यदि यह runaway process है, तो swap पांच second की outage को बीस minute के ठहराव (stall) में बदल देता है। यह ठहराव अधिक हानिकारक है, क्योंकि एक मृत process के बाद भी आपको working shell मिल जाता है, जबकि thrashing कर रहा box पूरी तरह अनुत्तरदायी हो जाता है।

swapon --show
free -h

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

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

OOM daemon का समय से पहले सक्रिय होना

Kernel reclaim प्रक्रिया के पूरी तरह विफल होने का इंतज़ार करता है। एक छोटे VPS पर, यह इंतज़ार वह सटीक समय है जब आप सर्वर का एक्सेस खो देते हैं। दो userspace daemons मेमोरी की निगरानी करके और समय से पहले process को kill करके इस समस्या को हल करते हैं।

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

sudo apt install earlyoom
systemctl status earlyoom

Debian और Ubuntu के package install होते ही service को start कर देते हैं। इसके विकल्प /etc/default/earlyoom में स्थित होते हैं:

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

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

systemd-oomd दूसरा विकल्प है। इसका manual page इसे "एक system service के रूप में वर्णित करता है जो cgroups-v2 और pressure stall information (PSI) का उपयोग करके kernel space में OOM होने से पहले निगरानी करता है और सुधारात्मक कार्रवाई करता है"। यह एकल process के बजाय पूरे cgroups पर कार्य करता है, इसलिए यह किसी stray child के बजाय एक unit को kill करता है। Units ManagedOOMMemoryPressure=kill या ManagedOOMSwap=kill के साथ opt-in करती हैं, और thresholds /etc/systemd/oomd.conf में स्थित होते हैं।

systemctl status systemd-oomd
oomctl

oomctl यह प्रिंट करता है कि वह वर्तमान में किसकी निगरानी कर रहा है, जो अक्सर सर्वर image पर कुछ भी नहीं होता है, क्योंकि यह सेटिंग प्रति unit opt-in होती है। किसी एक daemon को चुनें और वहीं रुकें। दोनों को एक साथ चलाने का मतलब है कि दो चीजें victim चुनने की दौड़ में हैं, और किसी भी 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 वह मेमोरी है जिसे 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= तक पहुँच गई और बाकी मशीन ठीक थी। एक साधारण Out of 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 लाइन नहीं बनती:

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 और उससे नया) उस उच्चतम उपयोग को दर्शाता है जहाँ तक cgroup पहुँचा था, और यही वह संख्या है जिसके आधार पर MemoryMax= को size करना चाहिए। जब unit restart होती है तो दोनों फाइलें reset हो जाती हैं, क्योंकि systemd cgroup को फिर से बनाता है।

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

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 दिखा सकता है जिसमें सिस्टम क्रैश हुआ था।

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

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

[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 फ्रीज क्यों हो गया?

क्योंकि कर्नल यह देखता है कि क्या मेमोरी वापस मिल रही है, न कि इसमें कितना समय लग रहा है। जब मेमोरी कम होती है, तो यह पेज कैश को हटा देता है, जिसमें चल रहे प्रोग्राम के निष्पादन योग्य पेज (executable pages) भी शामिल होते हैं, और फिर अगले निर्देश पर उन्हें वापस पढ़ता है। सब कुछ स्टोरेज पर निर्भर हो जाता है और तकनीकी रूप से कोई एलोकेशन विफल नहीं होता, इसलिए OOM killer कभी सक्रिय नहीं होता। जब ऐसा हो रहा हो तो /proc/pressure/memory की जाँच करें: 40 से ऊपर का full avg10 मतलब है कि पिछले दस सेकंड में लगभग कोई भी टास्क पूरा नहीं हुआ। earlyoom जैसा एक यूजरस्पेस डेमन बॉक्स के उस स्थिति में पहुँचने से पहले ही प्रोसेस को किल कर देता है।

MemoryHigh और MemoryMax के बीच क्या अंतर है?

MemoryHigh= एक सॉफ्ट कैप है जो थ्रॉटलिंग (throttling) करता है। कर्नल यूनिट से मेमोरी वापस लेने के लिए दबाव डालता है और उसके एलोकेशन को धीमा कर देता है, लेकिन उपयोग इस संख्या से अधिक हो सकता है और कुछ भी किल नहीं होता। MemoryMax= एक हार्ड कैप है: इसके तहत यदि कोई एलोकेशन पूरा नहीं हो पाता, तो वह उस यूनिट के cgroup के भीतर OOM killer को सक्रिय कर देता है। इससे समस्या पैदा करने वाली प्रोसेस ही समाप्त होती है, न कि मशीन की सबसे बड़ी प्रोसेस। MemoryHigh= को MemoryMax= से नीचे सेट करें और उनके बीच के अंतर को चेतावनी क्षेत्र (warning zone) के रूप में मानें।

मैं कैसे पता लगाऊँ कि OOM killer ने किस सर्विस को हिट किया है?

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

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

एक छोटा swap फाइल उन कोल्ड पेजों के लिए मददगार है जो एक बार एलोकेट होते हैं और फिर कभी इस्तेमाल नहीं होते। यह रनवे प्रोसेस के मामले में मदद नहीं करता: यह किल होने में देरी करता है और एक छोटे आउटेज को एक लंबे स्टॉल में बदल देता है जिसे आप फिक्स करने के लिए लॉग इन भी नहीं कर पाएंगे। swap को सीमित रखें, और उन यूनिट्स पर MemorySwapMax=0 सेट करें जिन्हें आप खोने के लिए तैयार हैं, ताकि वे अपनी सीमा तक पहुँचें और जल्दी रीस्टार्ट हो जाएँ जबकि महत्वपूर्ण सेवाएं अपना swap बनाए रखें।

क्या मैं बिना यूनिट फाइल लिखे किसी कमांड को सीमित कर सकता हूँ?

हाँ। sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh आपके टर्मिनल में उस कमांड को उन सीमाओं के साथ एक ट्रांजिएंट स्कोप (transient scope) में चलाता है, और बाहर निकलने पर सीमाएं खत्म हो जाती हैं। systemd.resource-control की हर प्रॉपर्टी -p के बाद उपलब्ध होती है, इसलिए MemorySwapMax=, TasksMax= और CPUWeight= वहाँ भी काम करते हैं। --scope को हटाएँ और जॉब को बैकग्राउंड में चलाने के लिए --unit=name जोड़ें, जिससे उसका आउटपुट जर्नल में दिखाई दे।