systemd-এ process-এর memory ও CPU সীমিত করার নিয়ম
VPS freeze ঠেকাতে systemd unit-এ MemoryHigh, MemoryMax, CPUQuota ও TasksMax সেট করুন। OOM kill-এর পর ঠিক কী ঘটেছে, তা log থেকে যাচাই করুন।
systemd drop-in দিয়ে process-এর memory ও CPU সীমিত করুন
যে unit process চালায়, তাতে কয়েকটি line যোগ করে Linux VPS-এ process-এর memory ও CPU সীমিত করা যায়। MemoryMax= memory-এর কঠোর সর্বোচ্চ সীমা নির্ধারণ করে। CPUQuota= processor time-এর সর্বোচ্চ সীমা নির্ধারণ করে। উভয় সীমাই cgroup v2 (control groups, version 2) প্রয়োগ করে। এটি kernel-এর এমন একটি বৈশিষ্ট্য, যা systemd ইতিমধ্যেই সার্ভারের প্রতিটি service-এর resource usage হিসাব রাখতে ব্যবহার করে।
sudo systemctl edit myapp.serviceএতে comments-এ দেওয়া নির্দেশনা-সহ একটি drop-in file খোলে। নির্দেশনাগুলোর ওপরে এটি যোগ করুন:
[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMaxsystemctl show-কে kernel-এর নিজস্ব unit-এ আপনার নির্ধারিত সংখ্যাগুলো আবার দেখাতে হবে: MemoryMax=805306368 এবং CPUQuotaPerSecUSec=800ms। এটি MemoryMax=infinity দেখালে drop-in load হয়নি। File-টি /etc/systemd/system/myapp.service.d/override.conf-এ তৈরি হয়েছে কি না এবং এটি [Service] header দিয়ে শুরু হয়েছে কি না যাচাই করুন। কারণ এর ওপরে section না থাকলে settings line-এর কারণে systemd Assignment outside of section. Ignoring. log করে এবং কোনো limit ছাড়াই service চালু করে।
এই guide-এর বাকি অংশে ওই সংখ্যাগুলো কীভাবে নির্বাচন করবেন এবং সেগুলো নির্ধারণ করার পরও কী কী সমস্যা হতে পারে, তা ব্যাখ্যা করা হয়েছে।
একটি runaway process কীভাবে VPS-কে freeze করে, যদিও সেটি পুরো memory ভরে না
কোনো process hard memory cap-এ পৌঁছালে প্রায় এক সেকেন্ডের মধ্যে বন্ধ হয়ে যায় এবং service আবার চালু হয়। এটিই ভালো পরিস্থিতি। খারাপ পরিস্থিতিতে কোনো কিছুই বন্ধ হয় না: box ping-এর উত্তর দেয়, SSH connection গ্রহণ করে, কিন্তু shell prompt আর আসে না। মেশিনটি চালু থাকে এবং ব্যস্ত থাকে, কিন্তু সেই কাজের কোনোটি কার্যকর নয়।
কীভাবে এটি ঘটে তা বোঝা জরুরি, কারণ বিষয়টি স্বাভাবিকভাবে স্পষ্ট নয়। free memory কমে গেলে kernel নতুন page বরাদ্দ না করে বিদ্যমান page reclaim করে। Reclaim করার জন্য সবচেয়ে সহজ page হলো file-backed page, আর page cache-এ চলমান সবকিছুর executable code থাকে। তাই kernel sshd-এর text page সরিয়ে দেয়, এবং পরের instruction sshd চালানোর সময় page fault হয়; ওই byte-গুলো storage থেকে আবার পড়তে হয়। ফলে প্রতিটি process চলার বদলে disk-এর উত্তরের জন্য অপেক্ষা করতে থাকে। একই page বারবার সরানো ও ফিরিয়ে আনা হয়। এই অবস্থাকে thrashing বলা হয়।
Laptop-এর তুলনায় VPS-এ দুটি কারণে পরিস্থিতি আরও খারাপ হয়। Storage প্রায়ই network-attached বা shared থাকে। তাই local NVMe device-এর তুলনায় প্রতিটি fault-এ বেশি milliseconds লাগে। আর kernel সময় মাপে না; failure মাপে। Reclaim যতক্ষণ ধীর গতিতেও হোক একটি page ফিরিয়ে দিতে থাকে, kernel ধরে নেয় যে অগ্রগতি হচ্ছে এবং out of memory (OOM) killer চালু করে না। কোনো কিছু kill হওয়ার আগে box অনেক মিনিট এই অবস্থায় থাকতে পারে।
আপনি এটি সরাসরি monitor করতে পারেন। Linux 4.20 এবং পরবর্তী সংস্করণে kernel pressure stall information (PSI) প্রকাশ করে:
cat /proc/pressure/memory
cat /proc/pressure/iosome avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233full line-টিই গুরুত্বপূর্ণ। full avg10=48.15-এর অর্থ হলো, গত দশ সেকেন্ডে 48% সময় box-এর প্রতিটি runnable task memory-related কাজের জন্য stalled ছিল। তাই কোনো task চলেনি। একটি সুস্থ server-এ full-এর মান প্রায় zero থাকে। 10-এর বেশি হলে মানুষের কাছে system ধীর মনে হয়, আর 40 বা তার বেশি হলে মানুষ সাধারণত একে frozen বলে বর্ণনা করে।
এই কারণেই শুধু একটি limit নির্ধারণ করা কোনো নিশ্চয়তা নয়। MemoryHigh=-এর অধীনে থাকা unit-কে kill না করে throttle করা হয়। ফলে সেটি চালু থাকে এবং ধীর থাকে, আর systemd-এর দৃষ্টিতে সেটি কখনো failed না হওয়ায় কিছুই restart হয় না। Swap ব্যবহারের অনুমতি থাকা capped unit এমন read এবং write তৈরি করে, যার খরচ ওই unit-এর নামে হিসাব করা হলেও সেগুলো একটি shared device দ্বারা সম্পন্ন হয়। ফলে box-এর অন্য প্রতিটি service-এর জন্যও /proc/pressure/io বেড়ে যেতে পারে। Limit শুধু নির্ধারণ করে shortage-এর খরচ কে বহন করবে; এটি capacity তৈরি করতে পারে না।
আপনার VPS-এ cgroup v2 চলছে কি না পরীক্ষা করুন
stat -fc %T /sys/fs/cgroupcgroup2fs হলো unified hierarchy। নিচের প্রতিটি সেটিংয়ের জন্য এটি প্রয়োজন। tmpfs বোঝায় যে সিস্টেমটি পুরোনো v1 layout দিয়ে boot হয়েছে। সেই layout-এ MemoryHigh= এবং MemorySwapMax= থাকে না, এবং প্রতিটি unit-এর OOM আচরণ ভিন্ন। Ubuntu 22.04 এবং পরবর্তী সংস্করণ, এবং Debian 11 ও পরবর্তী সংস্করণ, ডিফল্টভাবে v2 ব্যবহার করে। পুরোনো image বা systemd.unified_cgroup_hierarchy=0 দিয়ে boot করা kernel v2 ব্যবহার করে না।
cgroup v2 সিস্টেমে systemd ডিফল্টভাবে প্রতিটি unit-এর জন্য memory accounting চালু করে। তাই সংখ্যাগুলো আগে থেকেই পাওয়া যায়:
systemd-cgtop -mএটি memory ব্যবহারের ভিত্তিতে cgroup-গুলো সাজিয়ে দেখায়। সার্ভার এখনও সাড়া দেওয়ার সময় “কোন প্রক্রিয়া এই সার্ভারের resources ব্যবহার করছে” দ্রুত বোঝার এটি সবচেয়ে কার্যকর উপায়। সার্ভারটি নতুন হলে, নতুন VPS-এ প্রথম দশ মিনিটে account এবং firewall সংক্রান্ত কাজ আগে করুন।
MemoryHigh throttles. MemoryMax kills.
দুটি memory setting-এর পার্থক্যই নির্ধারণ করে ব্যর্থতা কীভাবে দেখা দেবে।
MemoryHigh=একটি soft cap। এর বেশি হলে kernel ওই cgroup থেকে দ্রুততার সঙ্গে resource reclaim করে এবং ইচ্ছাকৃতভাবে allocation ধীর করে দেয়। ব্যবহার এই সীমা অতিক্রম করতে পারে, তবে কোনো process kill করা হয় না।MemoryMax=একটি hard cap। এই সীমার মধ্যে কোনো allocation পূরণ করা সম্ভব না হলে OOM killer ওই cgroup-এর ভেতরেই চালু হয় এবং ওই unit-এর নিজস্ব process-গুলোর একটি kill করে।
MemoryMax=-এর সীমা এমন যেকোনো কিছুর ওপর নির্ধারণ করার মূল কারণ এটাই, যেটিকে আপনি সম্পূর্ণভাবে বিশ্বাস করেন না। কোনো cap না থাকলে memory shortage পুরো server-এর সমস্যা হয়ে দাঁড়ায়, এবং global OOM killer oom_score অনুযায়ী victim বেছে নেয়; সাধারণত এর অর্থ হলো সবচেয়ে বড় process। সবচেয়ে বড় process সাধারণত আপনার database, leak ঘটানো script নয়। cap থাকলে kill সেই unit-এর ভেতরেই ঘটে, যেটি সমস্যার কারণ হয়েছে।
উভয়টি নির্ধারণ করুন। MemoryHigh=-কে MemoryMax=-এর চেয়ে প্রায় 20 থেকে 30 percent কম রাখুন। এই ব্যবধানটি একটি সতর্কতা অঞ্চল হিসেবে কাজ করে: ধীরগতির leak High অতিক্রম করলে service ধীর হয়ে গেছে বলে তা বোঝা যায়, আর হঠাৎ spike সরাসরি Max অতিক্রম করে এবং service বন্ধ হয়ে যায়।
Percentage value installed physical memory-এর ভিত্তিতে গণনা করা হয়। তাই 4 GB plan-এ MemoryMax=25% হলো 1 GB, এবং plan resize করার পরও এটি server-এর এক-চতুর্থাংশ হিসেবেই থাকে। MemorySwapMax=0 ওই unit-কে swap ব্যবহার থেকে সম্পূর্ণ বিরত রাখে। ফলে দীর্ঘ সময় ধরে ধীরগতির পরিবর্তে দ্রুত এবং স্পষ্টভাবে kill হয়।
কোনো cap-এর পাশে restart policy-ও নির্ধারণ করতে হয়। তা না হলে kill-এর পরে শুধু একটি stopped service পড়ে থাকে।
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5sStartLimit*-কে [Unit]-এ এবং Restart=-কে [Service]-এ রাখতে হবে। যেকোনো একটি ভুল section-এ রাখলে systemd সেটি উপেক্ষা করে। পাঁচ মিনিটে পাঁচবার restart হওয়া সাময়িক ত্রুটি নয়, বরং leak-এর লক্ষণ। তাই এরপর systemd আর চেষ্টা না করে unit-টিকে failed অবস্থায় রেখে দেয়। পরে যে অবস্থাটি খুঁজে পাওয়া দরকার, সেটি এটাই; সমস্যা আড়াল করা crash loop নয়।
CPUQuota দিয়ে CPU সীমাবদ্ধ করুন, অথবা CPUWeight দিয়ে ভাগ নির্ধারণ করুন
CPUQuota= একটি CPU-তে উপলভ্য সময়ের নির্দিষ্ট শতাংশ ব্যবহার করে। CPUQuota=50% একটি core-এর অর্ধেকের সমান। CPUQuota=200% দুটি core-এর সমতুল্য; unit যত ইচ্ছা তত thread-এর মধ্যে এই ক্ষমতা ভাগ করে নিতে পারে। 2 vCPU plan-এ CPUQuota=200% পুরো machine-এর সমান।
বেশিরভাগ service-এর জন্য CPUWeight=-ই ভালো default। এটি 1 থেকে 10000 পর্যন্ত একটি আপেক্ষিক share, এবং kernel-এর default হলো 100। কোনো service অন্য কিছুর সঙ্গে প্রতিযোগিতা করলেই এর প্রভাব দেখা যায়: load-এর সময় CPUWeight=20-এর একটি backup job 100-এ চলা web server-এর কাছে কম CPU সময় পাবে, কিন্তু machine idle থাকলে পুরো box ব্যবহার করতে পারবে। কঠোর quota দিলে idle capacity নষ্ট হয়।
CPU limit কী সুবিধা দেয়, সে বিষয়ে বাস্তবসম্মত থাকুন। CPU-bound process সাধারণত Linux-কে অচল করে না, কারণ scheduler সবাইকে পর্যায়ক্রমে CPU time দেয়। একটি box অচল করার প্রধান কারণ সাধারণত memory। নির্দিষ্ট ও পূর্বানুমানযোগ্য সীমা চাইলে CPUQuota= ব্যবহার করুন, যেমন এমন কোনো build বা agent-এর জন্য, যা না হলে এক ঘণ্টা ধরে সম্পূর্ণ CPU ব্যবহার করত। এই ধরনের workload-এর sizing আলাদা বিষয়; এটি একটি coding agent-এর VPS-এ কত RAM ও CPU প্রয়োজন অংশে ব্যাখ্যা করা হয়েছে।
আপনার কোনো process উল্লেখযোগ্য CPU ব্যবহার না করলেও CPU busy দেখালে কারণটি hypervisor-এর অপর পাশে থাকতে পারে। এটিই noisy neighbour-এর কারণে CPU steal time; আপনি যে quota নির্ধারণ করুন না কেন, এতে কোনো পরিবর্তন হবে না।
TasksMax fork loop থামায়
TasksMax= হলো একটি unit যে সংখ্যক process ও thread ধারণ করতে পারে। Thread-ও গণনায় ধরা হয়। তাই Java বা Go service-এর জন্য process list-এ দেখা সংখ্যার চেয়ে বেশি headroom প্রয়োজন। কোনো script loop-এর মধ্যে fork করতে থাকলে এটি সুরক্ষার সবচেয়ে কম খরচের উপায়। কারণ পুরো system-এর process ID শেষ হয়ে যাওয়ার বদলে fork unit-এর ভেতরেই ব্যর্থ হয়।
TasksMax=128কোনো unit এই সীমায় পৌঁছালে kernel cgroup-এর নাম উল্লেখ করে একটি log line লেখে:
cgroup: fork rejected by pids controller in /system.slice/myapp.serviceপ্রোগ্রামটি সাধারণত নিজে fork: retry: Resource temporarily unavailable রিপোর্ট করে। manager ডিফল্টভাবে কী প্রয়োগ করে তা 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 দেখিয়ে আপনার terminal-এ কমান্ডটি চালায়। Output আপনার screen-এই থাকে এবং কমান্ড শেষ হলে সীমাগুলো সরিয়ে নেওয়া হয়। systemd.resource-control-এর যেকোনো property -p-এর পরে কাজ করে।
দীর্ঘ সময়ের কাজের জন্য --scope বাদ দিন এবং একটি নাম দিন। তখন কাজটি background-এ transient service হিসেবে চলে এবং journal-এ log লেখে:
sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -froot না থাকলেও --user-এর সঙ্গে একই option কাজ করে। তবে আপনার user manager-এর কাছে কেবল delegate করা controller থাকে, তাই কোনো property সেখানে প্রত্যাখ্যাত হতে পারে। এমন হলে sudo দিয়ে এটি চালান। কোনো কাজের জন্য স্থায়ী unit প্রয়োজন হলে settings অপরিবর্তিত রেখে সেগুলো একটি বাস্তব unit-এ সরিয়ে নিন: systemd service ও timer হিসেবে একটি script চালানো দেখুন।
Swap নিয়ে সৎ উত্তর
Swap ব্যর্থতা ঠেকায় না; বরং ব্যর্থতার ধরন বদলে দেয়।
Swap না থাকলে memory leak সীমায় পৌঁছে কয়েক সেকেন্ডের মধ্যে কোনো একটি প্রক্রিয়া বন্ধ হয়ে যায়। outage তীব্র কিন্তু স্বল্পস্থায়ী হয়, এবং পরে journal দেখে কারণ বোঝা সহজ হয়। Swap থাকলে kernel ব্যবহৃত হচ্ছে না এমন anonymous page disk-এ লিখে সময় কিনে নেয়। প্রক্রিয়াটি যদি পরে স্থিতিশীল হওয়ার কথা থাকে, swap আপনাকে রক্ষা করে। কিন্তু এটি যদি নিয়ন্ত্রণহীনভাবে memory ব্যবহার করতে থাকে, swap পাঁচ সেকেন্ডের outage-কে বিশ মিনিটের stall-এ পরিণত করে। এই stall আরও খারাপ, কারণ বন্ধ হয়ে যাওয়া প্রক্রিয়ার পরেও আপনি কার্যকর shell পান, কিন্তু thrashing হওয়া সিস্টেমে তা পান না।
swapon --show
free -hছোট VPS-এর জন্য একটি কার্যকর মধ্যপন্থা হলো: এমন page-এর জন্য একটি পরিমিত swap file রাখুন, যেগুলো একবার বরাদ্দ হওয়ার পর আর ব্যবহার করা হয় না। একই সঙ্গে যেসব unit বন্ধ হয়ে গেলেও চলবে, সেগুলোতে MemorySwapMax=0 নির্ধারণ করুন। গুরুত্বপূর্ণ service-গুলো swap ব্যবহার করতে পারবে। অনির্দেশ্য service-গুলো দ্রুত সীমায় পৌঁছে restart হবে।
vm.swappiness কমানো খুব দুর্বল একটি নিয়ন্ত্রণপদ্ধতি। কেন, তা জানা গুরুত্বপূর্ণ। এটি শুধু page cache সরানো এবং anonymous page swap করার মধ্যকার ভারসাম্য বদলায়। পরে disk থেকে page পড়ার প্রয়োজন হলে উভয় পদ্ধতিরই খরচ আছে। এটি কোন page thrashing করবে তা বদলায়, কিন্তু সিস্টেমে thrashing হবে কি না, তা বদলায় না।
stall হওয়ার আগেই early OOM daemon process বন্ধ করে
Kernel reclaim সম্পূর্ণভাবে ব্যর্থ হওয়ার জন্য অপেক্ষা করে। ছোট VPS-এ এই অপেক্ষার সময়টিই এমন একটি সময়সীমা, যার মধ্যে আপনি মেশিনের নিয়ন্ত্রণ হারাতে পারেন। Userspace-এর দুটি daemon নিজেরাই memory পর্যবেক্ষণ করে এবং আরও দ্রুত process বন্ধ করে এই সময়সীমা কমিয়ে দেয়।
earlyoom available memory এবং free swap পর্যবেক্ষণ করে। যেকোনো একটি threshold-এর নিচে নেমে গেলে এটি সর্বোচ্চ score পাওয়া process বন্ধ করে।
sudo apt install earlyoom
systemctl status earlyoomDebian এবং Ubuntu package install হওয়ার সময় service চালু করে। এর option-গুলো /etc/default/earlyoom-এ থাকে:
EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid '^(sshd|systemd)$' --prefer '^(node|python3)$'"-m PERCENT available memory-এর minimum নির্ধারণ করে এবং -s PERCENT free swap-এর minimum নির্ধারণ করে। উভয়টির default মান 10 percent। প্রতিটি pair-এর দ্বিতীয় সংখ্যাটি SIGKILL point। প্রথম মানের নিচে নামলে earlyoom SIGTERM পাঠায়, আর দ্বিতীয় মানের নিচে নামলে SIGKILL পাঠায়। দ্বিতীয় মানটি default হিসেবে প্রথম মানের অর্ধেক। পরিবর্তন প্রয়োগ করতে sudo systemctl restart earlyoom চালান। কোন process বন্ধ হয়েছে এবং সেটি কত memory ব্যবহার করছিল তা দেখতে journalctl -u earlyoom পড়ুন।
systemd-oomd হলো অন্য option। এর manual page-এ এটিকে এভাবে বর্ণনা করা হয়েছে: "a system service that uses cgroups-v2 and pressure stall information (PSI) to monitor and take corrective action before an OOM occurs in the kernel space"। এটি একক process-এর পরিবর্তে সম্পূর্ণ cgroup-এর ওপর কাজ করে। তাই এটি কোনো বিচ্ছিন্ন child process নয়, একটি unit বন্ধ করে। Unit-গুলো ManagedOOMMemoryPressure=kill অথবা ManagedOOMSwap=kill ব্যবহার করে এই ব্যবস্থায় অংশ নেয়। Threshold-গুলো /etc/systemd/oomd.conf-এ থাকে।
systemctl status systemd-oomd
oomctlবর্তমানে কোন কোন বিষয় পর্যবেক্ষণ করা হচ্ছে তা oomctl দেখায়। Server image-এ এটি প্রায়ই কিছুই দেখায় না, কারণ প্রতিটি unit-কে আলাদাভাবে opt in করতে হয়। একটি daemon বেছে নিয়ে সেখানেই থামুন। উভয়টি চালালে কোন process বন্ধ হবে তা বেছে নেওয়ার জন্য দুটি daemon পরস্পরের সঙ্গে প্রতিযোগিতা করবে। ফলে কোনো process বন্ধ হওয়ার কারণ পরে নির্ধারণ করা আরও কঠিন হবে।
কোন unit দায়ী ছিল?
প্রথমে kernel দিয়ে শুরু করুন, কারণ 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:0anon-rss হলো process-টি মারা যাওয়ার সময় RAM-এ ধরে রাখা memory; এখানে তা প্রায় 1.8 GB। বন্ধনীর মধ্যে থাকা নামটি সতর্কতার সঙ্গে পড়ুন। এটিই kernel-এর নির্বাচিত victim। তবে kernel সবচেয়ে বড় process বেছে নেয়, আর shortage ঘটানোর জন্য সবসময় সেই process-ই দায়ী নয়।
cgroup limit-এর কারণে হওয়া kill-এর prefix আলাদা। এর উপরে দেখানো report-এ যে cgroup নিজের ceiling-এ পৌঁছেছে, তার নাম থাকে:
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-ই diagnosis-এর বেশিরভাগ তথ্য দেয়। Memory cgroup out of memory মানে একটি unit আপনার নির্ধারিত MemoryMax=-এ পৌঁছেছিল এবং পুরো box-এর বাকি অংশ স্বাভাবিক ছিল। সাধারণ Out of memory মানে পুরো machine-এর memory শেষ হয়ে গিয়েছিল। অর্থাৎ আপনার cap অনুপস্থিত ছিল, অথবা একসঙ্গে যোগ করলে তা অত্যন্ত উদার ছিল।
এরপর systemd কী দেখেছিল তা জিজ্ঞাসা করুন:
systemctl status myapp.service
journalctl -u myapp.service -n 50myapp.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 counter হলো তৃতীয় উৎস। throttling-এর রেকর্ড রাখে একমাত্র এটিই। Throttling কখনও log line তৈরি করে না:
cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peaklow 0
high 4213
max 118
oom 12
oom_kill 12high unit-টি কতবার MemoryHigh= অতিক্রম করে throttled হয়েছে তা গণনা করে। max unit-টি কতবার hard cap-এ পৌঁছেছে তা গণনা করে, আর oom_kill বাস্তবে কতটি process kill হয়েছে তা গণনা করে। oom_kill 0-এর সঙ্গে বড় high আগের নীরব পরিস্থিতিকে নির্দেশ করে: service চলছে, অত্যন্ত ধীর হয়ে গেছে, কিন্তু কাউকে কোনো failure জানায়নি। memory.peak (Linux 5.19 এবং পরবর্তী সংস্করণ) cgroup-এ পৌঁছানো সর্বোচ্চ usage ধরে রাখে। MemoryMax=-এর আকার নির্ধারণে এই সংখ্যাটি ব্যবহার করুন। Unit restart হলে উভয় file reset হয়, কারণ systemd আবার cgroup তৈরি করে।
এর নিচে একটি prerequisite রয়েছে। /var/log/journal না থাকলে journal RAM-এ থাকে। ফলে box পুনরুদ্ধারের জন্য প্রয়োজনীয় reboot-এর পর প্রতিটি line হারিয়ে যায়।
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-bootsjournalctl --list-boots-এ current boot-এর চেয়ে বেশি কিছু দেখালে বুঝবেন history এখন সংরক্ষিত থাকে। তখন journalctl -k -b -1 ব্যবহার করে যে boot ব্যর্থ হয়ে বন্ধ হয়েছিল, সেই boot-এর kernel message দেখা যাবে।
ছোট VPS-এর জন্য একটি প্রাথমিক বিন্যাস
2 GB plan-এ kernel ও page cache-এর জন্য 300 থেকে 400 MB বরাদ্দ রাখুন। cap-গুলোর মোট যোগফল যেন পুরো 2 GB না হয়। কারণ প্রতিটি unit একই সময়ে peak-এ পৌঁছাতে পারে। যে service সবচেয়ে গুরুত্বপূর্ণ, তাকে সবচেয়ে বড় অংশ দিন। এরপর তার চারপাশের অনিশ্চিত workload-গুলোর জন্য cap নির্ধারণ করুন।
[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5sসার্ভারে প্রবেশের একটি কার্যকর পথ রাখা আরও একটি setting-এর মূল্য রাখে। OOMScoreAdjust=-500-কে ssh.service-এর drop-in-এ সেট করলে global OOM killer আপনার SSH daemon-কে victim হিসেবে বেছে নেওয়ার সম্ভাবনা অনেক কমে যায়। এর ফলে control panel থেকে reboot না করে সার্ভারের সমস্যা সমাধান করা সম্ভব হয়। এটি শুধু kernel কোন victim বেছে নেবে তা পরিবর্তন করে। stall-এর সময় কমায় না।
Container runtime নিজস্ব cgroup তৈরি করে container চালায়। এই cgroup আপনার unit file দিয়ে তৈরি হয় না। তাই docker.service-এ নির্ধারিত limit কোনো একটি container-এর limit হয় না। MemoryMax= এবং CPUQuota=-এর per-container সমতুল্য বিষয় Docker Compose-এ memory ও CPU limit নির্ধারণ-এ ব্যাখ্যা করা হয়েছে।
FAQ
আমার VPS runaway process বন্ধ না করে কেন স্থবির হয়ে গিয়েছিল?
কারণ kernel অগ্রগতি বিচার করে reclaim-এর মাধ্যমে page ফেরত পাওয়া যাচ্ছে কি না তা দেখে, এতে কত সময় লাগছে তা দেখে নয়। Memory কম থাকলে এটি running program-এর executable page-সহ page cache সরিয়ে দেয়, তারপর পরবর্তী instruction-এর সময় সেগুলো আবার পড়ে। সবকিছু storage-এর জন্য অপেক্ষা করতে থাকে এবং কোনো allocation আনুষ্ঠানিকভাবে ব্যর্থ হয় না। তাই OOM killer কখনো চালু হয় না। ঘটনা চলার সময় /proc/pressure/memory পরীক্ষা করুন: full avg10 40-এর বেশি হলে গত দশ সেকেন্ডে প্রায় কোনো task-ই run করার সুযোগ পায়নি। earlyoom-এর মতো userspace daemon VPS এই অবস্থায় পৌঁছানোর আগেই process kill করতে পারে।
MemoryHigh এবং MemoryMax-এর মধ্যে পার্থক্য কী?
MemoryHigh= হলো একটি soft cap, যা throttling করে। Kernel unit থেকে জোরপূর্বক memory reclaim করে এবং allocation ধীর করে, তবে usage এই সংখ্যার বেশি হতে পারে এবং কোনো process kill হয় না। MemoryMax= হলো hard cap। এর অধীনে কোনো allocation পূরণ করা না গেলে ওই unit-এর নিজস্ব cgroup-এর ভেতরে OOM killer চালু হয়। ফলে সমস্যাটি তৈরি করা process-ই বন্ধ হয়, পুরো মেশিনের সবচেয়ে বড় process নয়। MemoryHigh=-কে MemoryMax=-এর নিচে রাখুন এবং দুটির মধ্যকার ব্যবধানকে warning zone হিসেবে বিবেচনা করুন।
OOM killer কোন service-এ আঘাত করেছে তা কীভাবে খুঁজে বের করব?
journalctl -k --grep "Killed process" --since "2 hours ago" চালান। Memory cgroup out of memory দিয়ে শুরু হওয়া একটি line-এর অর্থ হলো কোনো unit তার নিজস্ব MemoryMax=-এ পৌঁছেছে। অন্যদিকে সাধারণ Out of memory-এর অর্থ হলো পুরো মেশিনের memory শেষ হয়ে গেছে। এরপর journalctl -u <unit> -n 50 চালিয়ে Failed with result 'oom-kill' খুঁজুন। আপনার server-এ /var/log/journal না থাকলে journal RAM-এ রাখা হয়েছিল এবং reboot-এর সঙ্গে প্রমাণও হারিয়ে গেছে। তাই পরবর্তী ঘটনার আগে ওই directory তৈরি করুন।
ছোট VPS-এ কি swap যোগ করা উচিত?
একটি ছোট swap file এমন cold page-এর ক্ষেত্রে সহায়তা করে, যেগুলো একবার allocate হওয়ার পর আর ব্যবহার করা হয় না। এটি runaway process-এর ক্ষেত্রে সহায়তা করে না। বরং process kill হতে দেরি করায় এবং স্বল্প সময়ের outage-কে দীর্ঘস্থায়ী stall-এ পরিণত করে, যার সময় আপনি login করে সমস্যা ঠিক করতে পারেন না। Swap-এর আকার সীমিত রাখুন। যেসব unit বন্ধ হলেও সমস্যা হবে না, সেগুলোতে MemorySwapMax=0 সেট করুন। তাহলে সেগুলো নিজেদের ceiling-এ পৌঁছে দ্রুত restart হবে, আর গুরুত্বপূর্ণ service-গুলো তাদের swap ব্যবহার করতে পারবে।
Unit file না লিখে কি কোনো command সীমাবদ্ধ করা যায়?
হ্যাঁ। sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh আপনার terminal-এ command-টি ওই limit-গুলোসহ একটি transient scope-এর ভেতরে চালায়। Command শেষ হলে limit-গুলোও আর থাকে না। systemd.resource-control-এর প্রতিটি property -p-এর পরে ব্যবহার করা যায়। তাই MemorySwapMax=, TasksMax= এবং CPUWeight=-ও সেখানে কাজ করে। --scope বাদ দিয়ে --unit=name যোগ করলে job-টি background-এ চলবে এবং এর output journal-এ লেখা হবে।