SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-26

systemd দিয়ে প্রসেসের মেমরি ও CPU লিমিট করার নিয়ম

systemd ইউনিটে MemoryMax এবং CPUQuota ব্যবহার করে প্রসেস নিয়ন্ত্রণ করুন। একটি capped প্রসেসও কীভাবে পুরো VPS ফ্রিজ করতে পারে এবং OOM kill লগ চেক করার সঠিক পদ্ধতি এখানে জানুন।

systemd drop-in ব্যবহার করে প্রসেসের মেমরি এবং CPU সীমাবদ্ধ করা

আপনি একটি Linux VPS-এ প্রসেসের মেমরি এবং CPU সীমাবদ্ধ করতে পারেন, প্রসেসটি যে unit-এ চলে তাতে কয়েকটি লাইন যোগ করে। MemoryMax= হলো মেমরির হার্ড সিলিং বা সর্বোচ্চ সীমা। CPUQuota= হলো প্রসেসর সময়ের সিলিং। উভয়ই cgroup v2 (control groups, version 2) দ্বারা কার্যকর করা হয়, যা কার্নেলের একটি ফিচার এবং systemd এটি সার্ভারের প্রতিটি সার্ভিসের হিসাব রাখার জন্য ব্যবহার করে।

sudo systemctl edit myapp.service

এটি মন্তব্যের নির্দেশনাসহ একটি drop-in ফাইল খোলে। সেগুলোর উপরে এটি যোগ করুন:

[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 অবশ্যই আপনার সংখ্যাগুলোকে কার্নেলের নিজস্ব ইউনিটে ফেরত দেখাবে: MemoryMax=805306368 এবং CPUQuotaPerSecUSec=800ms। যদি এটি MemoryMax=infinity প্রিন্ট করে, তবে drop-in ফাইলটি লোড হয়নি। ফাইলটি /etc/systemd/system/myapp.service.d/override.conf-এ সঠিকভাবে তৈরি হয়েছে কি না এবং এটি [Service] হেডার দিয়ে শুরু হয়েছে কি না তা পরীক্ষা করুন, কারণ উপরে কোনো সেকশন ছাড়া সেটিংস লাইন থাকলে systemd Assignment outside of section. Ignoring. লগ করে এবং কোনো সীমাবদ্ধতা ছাড়াই সার্ভিসটি চালু করে।

এই গাইডের বাকি অংশে এই সংখ্যাগুলো কীভাবে নির্বাচন করবেন এবং সেট করার পরেও কী কী সমস্যা হতে পারে তা আলোচনা করা হয়েছে।

কেন একটি runaway process VPS-কে ফ্রিজ করে দেয় যদিও তা মেমোরি পূর্ণ করে না

একটি process যখন মেমোরির হার্ড লিমিটে পৌঁছায়, তখন সেটি প্রায় এক সেকেন্ডের মধ্যে বন্ধ হয়ে যায় এবং service-টি রিস্টার্ট হয়। এটি ভালো পরিস্থিতি। খারাপ পরিস্থিতি হলো যখন কিছুই বন্ধ হয় না: সার্ভার ping-এর উত্তর দেয়, SSH কানেকশন গ্রহণ করে, কিন্তু shell prompt আর আসে না। মেশিনটি সচল এবং ব্যস্ত থাকে, কিন্তু সেই কাজের কোনোটিই কার্যকর নয়।

এর পেছনের মেকানিজমটি নিচে দেওয়া হলো, কারণ এটি সহজে বোঝা যায় না। যখন free memory কমে যায়, তখন kernel নতুন মেমোরি দেওয়ার পরিবর্তে existing page-গুলো reclaim করতে শুরু করে। reclaim করার জন্য সবচেয়ে সহজ হলো file-backed page-গুলো, আর page cache-এ চলমান সবকিছুর executable code জমা থাকে। তাই kernel sshd-এর text page-গুলো সরিয়ে ফেলে, এবং sshd-এর পরবর্তী instruction যখন রান করে, তখন একটি page fault ঘটে যা স্টোরেজ থেকে সেই byte-গুলো পুনরায় রিড করতে বাধ্য করে। প্রতিটি process তখন রান করার পরিবর্তে ডিস্কের জন্য অপেক্ষা করতে থাকে। একই page বারবার বের হয়ে আবার ফিরে আসে, যাকে thrashing বলা হয়।

ল্যাপটপের তুলনায় VPS-এ দুটি কারণে এটি আরও খারাপ হয়। স্টোরেজ প্রায়শই network attached বা shared থাকে, তাই প্রতিটি fault-এর জন্য local NVMe ডিভাইসের চেয়ে অনেক বেশি মিলিসেকেন্ড খরচ হয়। এছাড়া kernel সময়ের হিসাব করে না, এটি ব্যর্থতার হিসাব করে: যতক্ষণ reclaim প্রক্রিয়া কোনোভাবে একটি page ফেরত দিতে থাকে, kernel মনে করে যে কাজ এগোচ্ছে এবং এটি out of memory (OOM) killer-কে কল করে না। কোনো কিছু kill হওয়ার আগে একটি সার্ভার অনেক মিনিট এই অবস্থায় থাকতে পারে।

আপনি এটি ঘটতে দেখতে পারেন। Linux 4.20 এবং তার পরবর্তী ভার্সনগুলোতে kernel 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 মানে হলো গত দশ সেকেন্ডে, সার্ভারের প্রতিটি runnable task মেমোরি সংক্রান্ত কাজের জন্য অপেক্ষা করতে গিয়ে 48% সময় স্থবির ছিল, তাই কোনো কাজই সম্পন্ন হয়নি। একটি সুস্থ সার্ভারে full-এর মান শূন্যের কাছাকাছি থাকে। 10-এর উপরে গেলে মানুষের কাছে এটি ধীর মনে হয়, এবং 40 বা তার বেশি হলে সার্ভারটি ফ্রিজ হয়ে গেছে বলে মনে হয়।

এ কারণেই শুধুমাত্র একটি limit কোনো নিশ্চয়তা দেয় না। MemoryHigh=-এর অধীনে থাকা একটি unit-কে kill করার পরিবর্তে throttle করা হয়, ফলে এটি সচল থাকে কিন্তু ধীর হয়ে যায়, এবং systemd-এর দৃষ্টিতে এটি ব্যর্থ না হওয়ায় কোনো কিছু একে রিস্টার্ট করে না। একটি capped unit যদি swap ব্যবহারের অনুমতি পায়, তবে সেটি এমন সব read এবং write তৈরি করে যা সেই unit-এর ওপর চার্জ করা হয় কিন্তু একটি shared ডিভাইসের মাধ্যমে সার্ভ করা হয়, ফলে এটি সার্ভারের অন্য সব service-এর জন্য /proc/pressure/io বাড়িয়ে দিতে পারে। লিমিট নির্ধারণ করে কে ঘাটতির মূল্য দেবে, কিন্তু এগুলো নতুন সক্ষমতা তৈরি করতে পারে না।

আপনার VPS cgroup v2 চালাচ্ছে কি না তা যাচাই করুন

stat -fc %T /sys/fs/cgroup

cgroup2fs হলো ইউনিফাইড হায়ারার্কি, যা নিচের প্রতিটি সেটিংসের জন্য প্রয়োজন। tmpfs এর অর্থ হলো সার্ভারটি পুরনো v1 লেআউটে বুট হয়েছে, যেখানে MemoryHigh= এবং MemorySwapMax= বিদ্যমান নেই এবং প্রতি-ইউনিট OOM আচরণ ভিন্ন। Ubuntu 22.04 এবং পরবর্তী সংস্করণ, এবং Debian 11 এবং পরবর্তী সংস্করণ ডিফল্টভাবে v2 ব্যবহার করে। পুরনো ইমেজ, অথবা systemd.unified_cgroup_hierarchy=0 ফ্ল্যাগ দিয়ে বুট করা কার্নেল এটি ব্যবহার করে না।

cgroup v2-তে systemd ডিফল্টভাবে প্রতিটি ইউনিটের জন্য মেমরি অ্যাকাউন্টিং চালু রাখে, তাই সংখ্যাগুলো আগেই সেখানে থাকে:

systemd-cgtop -m

এটি মেমরি ব্যবহারের ভিত্তিতে cgroup-গুলোকে সাজিয়ে দেখায়, যা সার্ভার সচল থাকা অবস্থায় "কোনটি সার্ভারের মেমরি দখল করছে" তা জানার দ্রুততম উপায়। সার্ভারটি নতুন হলে, নতুন VPS-এ প্রথম দশ মিনিট-এর অ্যাকাউন্ট এবং ফায়ারওয়াল সংক্রান্ত কাজগুলো এর আগেই সম্পন্ন করতে হবে।

MemoryHigh থ্রটলিং করে। MemoryMax কিল করে।

এই দুটি মেমরি সেটিংসের পার্থক্য নির্ধারণ করে যে একটি ফেইলিয়র বা ব্যর্থতা দেখতে কেমন হবে।

  • MemoryHigh= একটি সফট ক্যাপ। এর উপরে গেলে কার্নেল সেই cgroup থেকে আক্রমণাত্মকভাবে মেমরি রিক্লেইম করে এবং ইচ্ছাকৃতভাবে এর অ্যালোকেশন ধীর করে দেয়। ব্যবহার এই সংখ্যার উপরে যেতে পারে এবং কোনো প্রসেস কিল করা হয় না।
  • MemoryMax= একটি হার্ড ক্যাপ। যখন এর নিচে কোনো অ্যালোকেশন পূরণ করা সম্ভব হয় না, তখন OOM কিলার সেই cgroup-এর ভেতরে চলে এবং সেই ইউনিটের নিজস্ব প্রসেসগুলোর একটিকে কিল করে।

দ্বিতীয় অংশটিই হলো আপনার সম্পূর্ণ বিশ্বাসযোগ্য নয় এমন যেকোনো কিছুর জন্য MemoryMax= সেট করার আসল কারণ। কোনো ক্যাপ না থাকলে, মেমরির ঘাটতি পুরো সিস্টেমের সমস্যা হয়ে দাঁড়ায় এবং গ্লোবাল OOM কিলার oom_score অনুযায়ী তার শিকার বেছে নেয়, যার অর্থ সাধারণত সবচেয়ে বড় প্রসেসটি। সবচেয়ে বড় প্রসেসটি সাধারণত আপনার ডাটাবেস হয়, সেই স্ক্রিপ্টটি নয় যা মেমরি লিক করছে। ক্যাপ থাকলে, কিল করার ঘটনাটি সেই ইউনিটের ভেতরেই ঘটে যা এর জন্য দায়ী।

উভয়ই সেট করুন, যেখানে MemoryHigh= থাকবে MemoryMax=-এর চেয়ে 20 থেকে 30 শতাংশ নিচে। এই ব্যবধানটি একটি সতর্কীকরণ অঞ্চল: একটি ধীরগতির লিক High অতিক্রম করলে সার্ভিসটি ধীর হয়ে যায়, আর হঠাৎ কোনো স্পাইক সরাসরি Max ভেদ করে সার্ভিসটিকে কিল করে দেয়।

শতাংশ মানগুলো ইনস্টল করা ফিজিক্যাল মেমরির সাপেক্ষে পড়া হয়, তাই 4 GB প্ল্যানে MemoryMax=25% মানে 1 GB এবং আপনি প্ল্যান রিসাইজ করার পরেও এটি বক্সের এক-চতুর্থাংশই থাকে। MemorySwapMax=0 সেই ইউনিটকে পুরোপুরি সোয়াপ থেকে দূরে রাখে, যা একটি দীর্ঘ ধীরগতিকে দ্রুত এবং স্পষ্ট কিল-এ পরিণত করে।

কিছু সার্ভিস পরিমাপ করার পরিবর্তে আগে থেকেই তাদের চাহিদা নির্ধারণ করতে দেয়: একটি 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 পর্যন্ত একটি আপেক্ষিক শেয়ার এবং কার্নেলের ডিফল্ট মান হলো 100। এটি কেবল তখনই কার্যকর হয় যখন কোনো কিছুর মধ্যে প্রতিযোগিতা থাকে: লোড থাকা অবস্থায় CPUWeight=20-এ থাকা একটি ব্যাকআপ জব 100-তে থাকা একটি ওয়েব সার্ভারের কাছে জায়গা ছেড়ে দেয়, আবার সার্ভারটি অলস (idle) থাকলে পুরো বক্সটিই ব্যবহার করে। একটি হার্ড কোটা সেই অলস ক্ষমতাকে নষ্ট করে ফেলে।

CPU লিমিট আপনাকে কী সুবিধা দেয় সে সম্পর্কে বাস্তববাদী হোন। একটি CPU-বাউন্ড প্রসেস খুব কমই Linux-কে ফ্রিজ করে, কারণ শিডিউলার সবাইকে সময় দিতে থাকে। মেমোরিই মূলত একটি বক্সকে অচল করে দেয়। যখন আপনি একটি নির্দিষ্ট সীমা চান, তখন CPUQuota= ব্যবহার করুন; যেমন কোনো বিল্ড বা এজেন্টের ক্ষেত্রে যা অন্যথায় এক ঘণ্টা ধরে সর্বোচ্চ ক্ষমতায় চলতে পারে। সেই ধরনের ওয়ার্কলোডের আকার নির্ধারণ করা একটি আলাদা বিষয়, যা কোডিং এজেন্ট VPS-এর জন্য কতটুকু RAM এবং CPU প্রয়োজন-এ আলোচনা করা হয়েছে।

যদি আপনার প্রসেসগুলো খুব বেশি কাজ না করা সত্ত্বেও CPU-কে ব্যস্ত দেখায়, তবে এর কারণ হাইপারভাইজারের অন্য পাশে থাকতে পারে। এটি হলো নয়েজি নেইবার থেকে CPU স্টিল টাইম, এবং আপনার সেট করা কোনো কোটাই এটি পরিবর্তন করতে পারবে না।

TasksMax একটি fork loop থামিয়ে দেয়

TasksMax= হলো একটি ইউনিটের ধারণক্ষমতা বা প্রসেস ও থ্রেডের সর্বোচ্চ সংখ্যা। থ্রেডগুলোও এই সংখ্যার অন্তর্ভুক্ত, তাই Java বা Go সার্ভিসের ক্ষেত্রে প্রসেস তালিকার চেয়ে বেশি headroom প্রয়োজন হয়। কোনো স্ক্রিপ্ট যদি লুপের মধ্যে fork করতে থাকে, তবে এটি তার বিরুদ্ধে সবচেয়ে সাশ্রয়ী সুরক্ষা প্রদান করে। কারণ এতে পুরো সার্ভারের প্রসেস আইডি (PID) শেষ হওয়ার আগেই ইউনিটের ভেতরেই fork প্রক্রিয়াটি ব্যর্থ হয়।

TasksMax=128

যখন কোনো ইউনিট এই সীমার (limit) পৌঁছায়, তখন কার্নেল cgroup-এর নাম উল্লেখ করে একটি লগ তৈরি করে:

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

প্রোগ্রামটি সাধারণত fork: retry: Resource temporarily unavailable ত্রুটি প্রদর্শন করে। সিস্টেম ম্যানেজার ডিফল্টভাবে কী প্রয়োগ করছে তা দেখতে systemctl show -p DefaultTasksMax কমান্ডটি ব্যবহার করুন।

systemd-run দিয়ে একটি ওয়ান-অফ জব সীমাবদ্ধ করা

এর জন্য আপনার কোনো unit file-এর প্রয়োজন নেই। systemd-run একটি একক কমান্ডের চারপাশে অস্থায়ী 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। আউটপুট আপনার স্ক্রিনেই থাকে এবং কমান্ডটি শেষ হয়ে গেলে সীমাবদ্ধতাগুলো মুছে যায়। systemd.resource-control-এর যেকোনো property -p-এর পরে কাজ করে।

দীর্ঘ সময়ের কোনো জবের জন্য, --scope বাদ দিয়ে সেটিকে একটি নাম দিন। এটি তখন ব্যাকগ্রাউন্ডে একটি transient service হিসেবে চলে এবং 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-এর কাছে শুধুমাত্র সেই controller-গুলোই থাকে যা তাকে অর্পণ (delegate) করা হয়েছে, তাই সেখানে কোনো property প্রত্যাখ্যাত হতে পারে। এমনটি ঘটলে sudo দিয়ে এটি চালান। যখন কোনো জব স্থায়ী রূপ পায়, তখন সেটিংসগুলো অপরিবর্তিত অবস্থায় একটি প্রকৃত unit-এ স্থানান্তরিত হয়: দেখুন running a script as a systemd service and timer।

সোয়াপ (swap) সংক্রান্ত প্রশ্নের সৎ উত্তর

সোয়াপ কোনো ব্যর্থতা প্রতিরোধ করে না, বরং ব্যর্থতার ধরন পরিবর্তন করে।

সোয়াপ না থাকলে, মেমোরি লিক সর্বোচ্চ সীমায় পৌঁছালে কয়েক সেকেন্ডের মধ্যেই কোনো একটি প্রসেস বন্ধ হয়ে যায়। এই বিভ্রাটটি স্পষ্ট, স্বল্পস্থায়ী এবং পরবর্তীতে জার্নাল দেখে সহজেই বোঝা যায়। সোয়াপ থাকলে, কার্নেল অব্যবহৃত anonymous pages ডিস্কে সরিয়ে দিয়ে কিছুটা সময় কিনে নেয়। যদি প্রসেসটির মেমোরি ব্যবহার স্থিতিশীল হওয়ার সম্ভাবনা থাকে, তবে সোয়াপ আপনাকে রক্ষা করবে। কিন্তু যদি এটি অনিয়ন্ত্রিতভাবে বাড়তে থাকে, তবে সোয়াপ পাঁচ সেকেন্ডের একটি বিভ্রাটকে বিশ মিনিটের স্থবিরতায় পরিণত করবে। এই স্থবিরতা আরও খারাপ, কারণ একটি মৃত প্রসেস আপনাকে অন্তত একটি সচল শেল (shell) ব্যবহারের সুযোগ দেয়, কিন্তু থ্র্যাশিং (thrashing) অবস্থায় থাকা সার্ভার তা দেয় না।

swapon --show
free -h

ছোট VPS-এর ক্ষেত্রে একটি কার্যকর মধ্যপন্থা হলো: একবার বরাদ্দ হওয়ার পর আর ব্যবহৃত হয় না এমন পেজগুলোর জন্য একটি ছোট সোয়াপ ফাইল রাখা এবং যে ইউনিটগুলো বন্ধ হয়ে গেলেও সমস্যা নেই সেগুলোতে MemorySwapMax=0 সেট করা। গুরুত্বপূর্ণ সার্ভিসগুলো তাদের সোয়াপ ধরে রাখবে। আর অনির্ভরযোগ্য সার্ভিসগুলো দ্রুত মেমোরি সীমার মুখে পড়বে এবং রিস্টার্ট হবে।

vm.swappiness কমানো একটি দুর্বল কৌশল, এবং কেন তা জানা প্রয়োজন। এটি কেবল পেজ ক্যাশ (page cache) মুছে ফেলা এবং anonymous pages সোয়াপ করার মধ্যে ভারসাম্য পরিবর্তন করে, যার উভয়ই পরবর্তীতে ডিস্ক রিডের খরচ বাড়ায়। এটি কেবল কোন পেজগুলো থ্র্যাশিং করবে তা পরিবর্তন করে, সার্ভারটি থ্র্যাশিং করবে কি না তা নয়।

একটি আর্লি OOM ডেমোন স্টল হওয়ার আগেই কিল করে

কার্নেল মেমোরি রিক্লেইম সম্পূর্ণ ব্যর্থ না হওয়া পর্যন্ত অপেক্ষা করে, আর ছোট VPS-এর ক্ষেত্রে সেই অপেক্ষার সময়টুকুতেই আপনি মেশিনটির নিয়ন্ত্রণ হারান। দুটি ইউজারস্পেস ডেমোন নিজেরাই মেমোরি পর্যবেক্ষণ করে এবং দ্রুত কিল করার মাধ্যমে এই সমস্যার সমাধান করে।

earlyoom উপলব্ধ মেমোরি এবং ফ্রি সোয়াপ পর্যবেক্ষণ করে, এবং যখনই কোনোটি নির্ধারিত সীমার নিচে নেমে যায়, এটি সর্বোচ্চ স্কোরধারী প্রসেসটিকে কিল করে দেয়।

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 হলো অন্য একটি বিকল্প। এর ম্যানুয়াল পেজ এটিকে "একটি সিস্টেম সার্ভিস যা cgroups-v2 এবং pressure stall information (PSI) ব্যবহার করে কার্নেল স্পেসে OOM ঘটার আগেই পর্যবেক্ষণ ও সংশোধনমূলক ব্যবস্থা গ্রহণ করে" হিসেবে বর্ণনা করে। এটি একক প্রসেসের পরিবর্তে পুরো cgroup-এর ওপর কাজ করে, তাই এটি কোনো বিচ্ছিন্ন চাইল্ড প্রসেসের বদলে একটি ইউনিটকে কিল করে। ইউনিটগুলো ManagedOOMMemoryPressure=kill বা ManagedOOMSwap=kill-এর মাধ্যমে এতে যুক্ত হতে পারে এবং থ্রেশহোল্ডগুলো /etc/systemd/oomd.conf-এ থাকে।

systemctl status systemd-oomd
oomctl

oomctl বর্তমানে কী পর্যবেক্ষণ করছে তা প্রিন্ট করে, যা সার্ভার ইমেজে প্রায়শই কিছুই দেখায় না, কারণ সেটিংটি প্রতি ইউনিটের জন্য আলাদাভাবে যুক্ত করতে হয়। একটি ডেমোন বেছে নিন এবং সেখানেই সীমাবদ্ধ থাকুন। উভয়ই একসাথে চালালে দুটি সার্ভিস ভিকটিম বেছে নেওয়ার প্রতিযোগিতায় লিপ্ত হয় এবং কোনো কিল হওয়ার কারণ খুঁজে বের করা কঠিন হয়ে পড়ে।

কোন ইউনিটটি দায়ী ছিল?

কার্নেল থেকে শুরু করুন, কারণ এটি তার করা প্রতিটি kill রেকর্ড করে রাখে।

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

গ্লোবাল 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 হলো সেই মেমরি যা প্রসেসটি মারা যাওয়ার সময় RAM-এ ধরে রেখেছিল, এখানে প্রায় 1.8 GB। ব্র্যাকেটের ভেতরের নামটিকে সন্দেহের চোখে দেখুন। এটি সেই ভিকটিম যাকে কার্নেল বেছে নিয়েছে, এবং কার্নেল সাধারণত সবচেয়ে বড় প্রসেসটিকে বেছে নেয়, যা সবসময় মেমরি সংকটের জন্য দায়ী প্রসেস হয় না।

cgroup লিমিট থেকে আসা একটি kill-এর প্রিফিক্স ভিন্ন হয় এবং এর উপরে প্রিন্ট হওয়া রিপোর্টে সেই 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

এই প্রিফিক্সটিই হলো রোগ নির্ণয়ের মূল চাবিকাঠি। Memory cgroup out of memory মানে হলো একটি ইউনিট আপনার দেওয়া 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 কাউন্টারগুলো হলো তথ্যের তৃতীয় উৎস, এবং এটিই একমাত্র উৎস যা throttling রেকর্ড করে, যা কোনো লগ লাইন তৈরি করে না:

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 গণনা করে কতবার ইউনিটটিকে MemoryHigh=-এর উপরে ঠেলে দেওয়া হয়েছে এবং throttle করা হয়েছে। max গণনা করে কতবার এটি হার্ড ক্যাপে পৌঁছেছে, এবং oom_kill গণনা করে কতগুলো প্রসেস প্রকৃতপক্ষে kill করা হয়েছে। একটি বড় high এবং সাথে oom_kill 0 থাকা মানে হলো আগের সেই নীরব সমস্যা: সার্ভিসটি চলছে, কিন্তু অত্যন্ত ধীরগতিতে, এবং কাউকে কোনো ব্যর্থতার রিপোর্ট দিচ্ছে না। memory.peak (Linux 5.19 এবং নতুন ভার্সন) cgroup-এর সর্বোচ্চ ব্যবহারের পরিমাণ ধরে রাখে, যা MemoryMax=-এর আকার নির্ধারণের জন্য ব্যবহার করা উচিত। ইউনিট রিস্টার্ট হলে উভয় ফাইলই রিসেট হয়ে যায়, কারণ systemd পুনরায় cgroup তৈরি করে।

এই সবকিছুর নিচে একটি পূর্বশর্ত রয়েছে। যদি /var/log/journal বিদ্যমান না থাকে, তবে জার্নালটি RAM-এ থাকে, এবং সার্ভার রিকভার করার জন্য রিবুট করার পর প্রতিটি লাইন মুছে যায়।

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 যদি বর্তমান বুটের চেয়ে বেশি দেখায়, তার মানে হিস্ট্রি এখন সংরক্ষিত আছে, তাই journalctl -k -b -1 আপনাকে সেই বুটের কার্নেল মেসেজগুলো দেখাতে পারবে যা ক্র্যাশ করেছিল।

একটি ছোট VPS-এর জন্য প্রাথমিক ধাপ

2 GB র‍্যামের প্ল্যানে, কার্নেল এবং পেজ ক্যাশের জন্য 300 থেকে 400 MB জায়গা খালি রাখুন। সব সার্ভিসের মেমরি লিমিটের যোগফল যেন 2 GB না হয়, কারণ সব ইউনিট একই সময়ে সর্বোচ্চ মেমরি ব্যবহার করতে পারে। যে সার্ভিসটি সবচেয়ে গুরুত্বপূর্ণ সেটিকে বেশি রিসোর্স বরাদ্দ করুন এবং বাকি অপ্রয়োজনীয় সার্ভিসগুলোর জন্য লিমিট নির্ধারণ করে দিন।

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

সার্ভারে প্রবেশের পথ খোলা রাখা অত্যন্ত জরুরি। ssh.service-এর একটি ড্রপ-ইন ফাইলে OOMScoreAdjust=-500 সেট করলে, সিস্টেমের OOM killer আপনার SSH daemon-কে বন্ধ করার সম্ভাবনা অনেক কমে যায়। এটি সার্ভার ঠিক করার সুযোগ এবং কন্ট্রোল প্যানেল থেকে রিবুট করার ঝামেলার মধ্যে পার্থক্য গড়ে দেয়। এটি শুধুমাত্র কার্নেলের ভিকটিম নির্বাচনের সিদ্ধান্ত পরিবর্তন করে, কিন্তু সিস্টেমের স্থবিরতা (stall) কমায় না।

কন্টেইনারগুলো নিজস্ব cgroup-এ চলে, যা আপনার ইউনিট ফাইল নয় বরং কন্টেইনার রানটাইম তৈরি করে। তাই docker.service-এর লিমিট কোনো একটি কন্টেইনারের ওপর কার্যকর হয় না। প্রতিটি কন্টেইনারের জন্য MemoryMax= এবং CPUQuota=-এর সমতুল্য সেটিংস সম্পর্কে জানতে Docker Compose-এ মেমরি এবং CPU লিমিট সেট করা দেখুন।

FAQ

কেন আমার VPS runaway process বন্ধ করার পরিবর্তে ফ্রিজ হয়ে যায়?

কারণ কার্নেল মেমোরি রিক্লেইম (reclaim) সফল হচ্ছে কি না তার ওপর ভিত্তি করে প্রসেসের অগ্রগতি বিচার করে, এটি সম্পন্ন হতে কত সময় লাগছে তার ওপর নয়। মেমোরি কম থাকলে কার্নেল পেজ ক্যাশ থেকে ডেটা সরিয়ে ফেলে, যার মধ্যে চলমান প্রোগ্রামের এক্সিকিউটেবল পেজও থাকে। এরপর পরবর্তী ইন্সট্রাকশনের সময় সেগুলো আবার রিড করতে হয়। সবকিছু স্টোরেজের ওপর নির্ভর করে আটকে থাকে এবং কারিগরিভাবে কোনো অ্যালোকেশন ব্যর্থ হয় না, তাই OOM killer সক্রিয় হয় না। এমন পরিস্থিতিতে /proc/pressure/memory চেক করুন: full avg10 যদি 40-এর বেশি হয়, তবে এর অর্থ গত দশ সেকেন্ডে প্রায় কোনো টাস্কই রান করতে পারেনি। earlyoom-এর মতো ইউজারস্পেস ডেমোন ব্যবহার করলে সার্ভার সেই অবস্থায় পৌঁছানোর আগেই প্রসেস কিল করা সম্ভব।

MemoryHigh এবং MemoryMax-এর মধ্যে পার্থক্য কী?

MemoryHigh= হলো একটি সফট ক্যাপ যা প্রসেসকে থ্রটল (throttle) করে। কার্নেল ইউনিট থেকে মেমোরি রিক্লেইম করার চেষ্টা করে এবং অ্যালোকেশন ধীর করে দেয়, কিন্তু ব্যবহার এই সীমার চেয়ে বেশি হতে পারে এবং কোনো প্রসেস কিল করা হয় না। MemoryMax= হলো একটি হার্ড ক্যাপ: এর অধীনে কোনো অ্যালোকেশন ব্যর্থ হলে তা ওই ইউনিটের নিজস্ব cgroup-এর ভেতর OOM killer সক্রিয় করে। ফলে পুরো সিস্টেমের সবচেয়ে বড় প্রসেসটি না মরে, যে প্রসেসটি সমস্যার সৃষ্টি করেছে সেটিই বন্ধ হয়ে যায়। MemoryHigh=-কে MemoryMax=-এর নিচে সেট করুন এবং এদের মধ্যবর্তী গ্যাপকে ওয়ার্নিং জোন হিসেবে বিবেচনা করুন।

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 ডিরেক্টরি না থাকে, তবে জার্নাল র‍্যামে ছিল এবং রিবুটের সাথে সাথে তথ্য মুছে গেছে। তাই পরবর্তী ঘটনার আগেই এই ডিরেক্টরি তৈরি করে রাখুন।

ছোট VPS-এ কি swap যোগ করা উচিত?

একটি ছোট সোয়াপ ফাইল এমন কোল্ড পেজগুলোর জন্য সহায়ক যা একবার অ্যালোকেট হওয়ার পর আর ব্যবহৃত হয় না। এটি runaway process-এর ক্ষেত্রে কোনো কাজে আসে না; বরং এটি কিল করার প্রক্রিয়াকে বিলম্বিত করে এবং একটি ছোট বিভ্রাটের পরিবর্তে দীর্ঘ সময়ের জন্য সিস্টেমকে স্থবির করে দেয়, যা ঠিক করার জন্য আপনি লগইনও করতে পারবেন না। সোয়াপ সীমিত রাখুন এবং যে ইউনিটগুলো বন্ধ হয়ে গেলেও সমস্যা নেই সেগুলোতে MemorySwapMax=0 সেট করুন। এতে গুরুত্বপূর্ণ সার্ভিসগুলো তাদের সোয়াপ ধরে রাখতে পারবে এবং অন্যগুলো তাদের সীমার শীর্ষে পৌঁছে দ্রুত রিস্টার্ট হবে।

ইউনিট ফাইল না লিখে কি কোনো কমান্ড লিমিট করা সম্ভব?

হ্যাঁ। sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh আপনার টার্মিনালে একটি ট্রানজিয়েন্ট স্কোপের ভেতর কমান্ডটি রান করে এবং কমান্ডটি শেষ হওয়ার সাথে সাথে লিমিটগুলো মুছে যায়। systemd.resource-control-এর প্রতিটি প্রপার্টি -p-এর পরে ব্যবহার করা যায়, তাই MemorySwapMax=, TasksMax= এবং CPUWeight= সেখানেও কাজ করবে। --scope বাদ দিন এবং জবটিকে ব্যাকগ্রাউন্ডে রান করে এর আউটপুট জার্নালে পাওয়ার জন্য --unit=name যোগ করুন।