SSD Nodes Learn 🎉 VPS החל מ־$5.50/חודש
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-13

הגבלת צריכת משאבים לתהליך ב-systemd: זיכרון ומעבד

למדו כיצד להגביל MemoryMax, MemoryHigh ו-CPUQuota ב-systemd כדי למנוע קריסת VPS. המדריך מסביר כיצד להגדיר קובץ drop-in, לוודא טעינה תקינה ולמנוע מצב שבו תהליך חורג גורם לקיפאון המערכת.

הגבלת זיכרון ומעבד של תהליך באמצעות drop-in של systemd

ניתן להגביל את צריכת הזיכרון והמעבד של תהליך בשרת VPS מבוסס Linux על ידי הוספת מספר שורות לקובץ ה-unit שמריץ את התהליך. MemoryMax= הוא הרף העליון הקשיח לזיכרון. CPUQuota= הוא הרף העליון לזמן מעבד. שניהם נאכפים על ידי cgroup v2 (קבוצות בקרה, גרסה 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. ולהפעיל את השירות ללא כל הגבלה.

שאר המדריך עוסק בבחירת המספרים הללו ובבעיות שעלולות להתרחש גם לאחר הגדרתם.

מדוע תהליך "בורח" גורם לקפיאה של VPS גם כשאינו ממלא את הזיכרון

תהליך שמגיע למכסת זיכרון קשיחה קורס בתוך כשנייה והשירות מופעל מחדש. זהו המקרה התקין. המקרה הבעייתי הוא זה שבו שום דבר לא קורס: השרת מגיב ל-ping, חיבור SSH מתקבל, אך שורת הפקודה (shell prompt) לעולם לא מופיעה. המכונה פעילה ועסוקה, אך אף אחת מהפעולות אינה מועילה.

להלן המנגנון, שכן הוא אינו מובן מאליו. כאשר הזיכרון הפנוי אוזל, ה-kernel מבצע פינוי (reclaim) של דפי זיכרון במקום להקצות חדשים. הדפים הזולים ביותר לפינוי הם אלו המגובים בקבצים, ו-page cache מחזיק את קוד ההרצה של כל מה שרץ. לכן, ה-kernel מפנה את דפי ה-text של sshd, והפקודה הבאה ש-sshd מנסה להריץ גורמת ל-page fault, שמחייב קריאה חוזרת של אותם בתים מהאחסון. כל תהליך בסופו של דבר ממתין לדיסק במקום לרוץ. אותם דפים יוצאים וחוזרים בלולאה, מצב הנקרא thrashing.

שני גורמים מחמירים זאת ב-VPS בהשוואה למחשב נייד. האחסון לרוב מחובר ברשת או משותף, לכן כל fault עולה במילי-שניות רבות יותר מאשר בהתקן NVMe מקומי. בנוסף, ה-kernel אינו מודד זמן, אלא כישלונות: כל עוד תהליך הפינוי מצליח להחזיר דף, לא משנה כמה לאט, ה-kernel סבור שהוא מתקדם ולא מפעיל את ה-out of memory (OOM) killer. שרת יכול להישאר במצב זה דקות ארוכות לפני שמשהו נהרג.

ניתן לצפות בתהליך זה בזמן אמת. ה-kernel מייצא מידע על לחץ במערכת (PSI) בגרסאות Linux 4.20 ומעלה:

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 ומעלה הוא המצב שאנשים מתארים כקפוא.

זו גם הסיבה שמכסה (limit) כשלעצמה אינה הבטחה. יחידה המוגבלת תחת MemoryHigh= עוברת throttling במקום להיהרג, לכן היא נשארת פעילה ואיטית, ושום דבר לא מפעיל אותה מחדש מכיוון שמבחינת systemd היא מעולם לא נכשלה. יחידה מוגבלת שעדיין מורשית להשתמש ב-swap מייצרת פעולות קריאה וכתיבה שנספרות על חשבון אותה יחידה אך מוגשות על ידי התקן משותף, ולכן היא יכולה להקפיץ את /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 מפעיל ניהול זיכרון (memory accounting) עבור כל יחידה כברירת מחדל, לכן הנתונים כבר זמינים:

systemd-cgtop -m

פקודה זו מציגה את ה-cgroups ממוינים לפי צריכת זיכרון, וזו הדרך המהירה ביותר לענות על השאלה "מה צורך את משאבי השרת" כל עוד המערכת מגיבה. אם מדובר בשרת חדש, הגדרת החשבון וה-firewall כפי שמתואר ב-עשר הדקות הראשונות ב-VPS חדש צריכה להתבצע לפני שלב זה.

הגדרות MemoryHigh ו-MemoryMax.

ההבדל בין שתי הגדרות הזיכרון הללו קובע כיצד ייראה כשל במערכת.

  • MemoryHigh= הוא רף רך (soft cap). מעבר לו, ה-kernel מבצע פינוי זיכרון אגרסיבי מאותו cgroup ומאט בכוונה את הקצאות הזיכרון שלו. השימוש בזיכרון יכול לעבור את הערך המוגדר, ושום תהליך לא יסתיים.
  • MemoryMax= הוא רף קשיח (hard cap). כאשר הקצאת זיכרון אינה יכולה להתבצע תחת מגבלה זו, ה-OOM killer מופעל בתוך אותו cgroup ומסיים את אחד התהליכים השייכים ליחידה זו.

החלק השני הוא הסיבה האמיתית להגדיר MemoryMax= לכל שירות שאינכם סומכים עליו לחלוטין. ללא מגבלה, מחסור בזיכרון הופך לבעיה של השרת כולו, וה-OOM killer הגלובלי בוחר את הקורבן לפי oom_score, מה שבדרך כלל אומר התהליך הגדול ביותר. התהליך הגדול ביותר הוא לרוב מסד הנתונים שלכם, ולא הסקריפט שסובל מדליפת זיכרון. עם מגבלה, ה-kill מתבצע בתוך היחידה שגרמה לבעיה.

הגדירו את שניהם, כאשר MemoryHigh= נמוך בכ-20 עד 30 אחוזים מ-MemoryMax=. הפער משמש כאזור התראה: דליפה איטית תחצה את High ותתבטא בשירות איטי, בעוד זינוק פתאומי יעבור ישירות דרך Max ויגרום לסיום התהליך.

ערכי אחוזים מחושבים ביחס לזיכרון הפיזי המותקן, לכן MemoryMax=25% בתוכנית של 4 GB הם 1 GB, וערך זה יישאר רבע מהשרת גם לאחר שתשדרגו את התוכנית. MemorySwapMax=0 מונע מאותה יחידה להשתמש ב-swap לחלוטין, מה שהופך זחילה איטית לסיום מהיר וברור.

מגבלה מחייבת מדיניות הפעלה מחדש (restart policy) לצידה, אחרת ה-kill ישאיר אתכם עם שירות עצור.

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

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

StartLimit* שייך ל-[Unit] ו-Restart= ל-[Service]. אם תציבו אחד מהם בסעיף הלא נכון, systemd יתעלם ממנו. חמש הפעלות מחדש בחמש דקות מעידות על דליפה ולא על תקלה רגעית, לכן לאחר מכן systemd מוותר ומשאיר את היחידה במצב failed; זהו המצב שתרצו למצוא מאוחר יותר, במקום לולאת קריסות שמסתירה את הבעיה.

הגבלת CPU באמצעות CPUQuota, או הקצאת משאבים באמצעות CPUWeight

CPUQuota= מגדיר אחוז מהזמן הזמין בליבת CPU אחת. CPUQuota=50% שווה למחצית מליבה אחת. CPUQuota=200% שווה ערך לשתי ליבות, שהיחידה יכולה לפרוס על פני כמה תהליכונים שתרצה. בתוכנית של 2 vCPU, הערך CPUQuota=200% מהווה את מלוא כוח העיבוד של השרת.

CPUWeight= הוא ברירת המחדל העדיפה עבור רוב השירותים. מדובר בחלק יחסי בטווח שבין 1 ל-10000, כאשר ברירת המחדל של ה-kernel היא 100. הגבלה זו נכנסת לתוקף רק כאשר יש תחרות על המשאבים: משימת גיבוי עם CPUWeight=20 תפנה משאבים לשרת אינטרנט המוגדר כ-100 תחת עומס, אך תשתמש בכל עוצמת המעבד כשהשרת אינו עמוס. מכסה קשיחה (hard quota) גורמת לאובדן של קיבולת פנויה זו.

יש להיות מודעים למה שבאמת משיגים באמצעות הגבלת CPU. תהליך שצורך CPU אינו גורם בדרך כלל לקפיאה של Linux, כיוון שה-scheduler ממשיך להקצות זמן לכל התהליכים. זיכרון (RAM) הוא המשאב שגורם לקריסת שרת. השתמשו ב-CPUQuota= כאשר דרושה תקרה צפויה מראש, למשל עבור תהליך בנייה (build) או סוכן (agent) שאחרת היה רץ במלוא העוצמה במשך שעה. תכנון הגודל של עומס עבודה כזה הוא נושא בפני עצמו, המכוסה ב-כמה RAM ו-CPU דרושים ל-VPS של סוכן פיתוח.

אם ה-CPU מדווח כעמוס בעוד שאף תהליך שלכם אינו פעיל במיוחד, ייתכן שהסיבה נמצאת בצד השני של ה-hypervisor. זהו מצב של זמן גניבת CPU משכן רועש, ושום מכסה שתגדירו לא תשנה זאת.

הגדרת TasksMax עוצרת לולאת fork

TasksMax= הוא מספר התהליכים וה־threads שיחידה (unit) רשאית להחזיק. מאחר ש־threads נספרים גם הם, שירות שנכתב ב־Java או ב־Go זקוק למרווח פעולה גדול יותר ממה שרשימת התהליכים עשויה לרמוז. זוהי ההגנה הזולה ביותר מפני סקריפט שמבצע fork בלולאה, כיוון שה־fork נכשל בתוך היחידה במקום שהשרת כולו יאזל ממזהי תהליכים (PIDs).

TasksMax=128

כאשר יחידה מגיעה למכסה, הליבה (kernel) מתעדת שורה המציינת את ה־cgroup:

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

התוכנית עצמה מדווחת בדרך כלל על fork: retry: Resource temporarily unavailable. בדקו מהו הערך שמנהל השירותים מחיל כברירת מחדל באמצעות systemctl show -p DefaultTasksMax.

הגבלת משימה חד-פעמית באמצעות systemd-run

אינכם זקוקים לקובץ unit כדי להשתמש בכל זה. systemd-run בונה יחידה זמנית סביב פקודה בודדת.

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 עובד לאחר -p.

עבור משימה ארוכה, השמיטו את --scope ותנו לה שם. היא תרוץ ברקע כשירות זמני ותתעד לתוך ה-journal:

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

אותן אפשרויות עובדות עם --user כאשר אינכם משתמשי root, אם כי למנהל השירותים של המשתמש שלכם יש רק את הבקרים שהוקצו לו, לכן מאפיין מסוים עלול להידחות שם. הריצו זאת עם sudo אם זה קורה. כאשר משימה מקבלת בית קבוע, ההגדרות עוברות ללא שינוי ליחידה אמיתית: ראו הרצת סקריפט כשירות וטיימר ב-systemd.

שאלת ה-swap, תשובה כנה

ה-swap משנה את אופי הכשל במקום למנוע אותו.

ללא swap, דליפת זיכרון מגיעה לתקרה ותהליך כלשהו קורס בתוך שניות. ההשבתה רועשת, קצרה וקלה לאבחון ביומני המערכת לאחר מכן. עם swap, ה-kernel כותב דפי זיכרון אנונימיים "קרים" לדיסק וקונה זמן. אם התהליך היה אמור להתייצב, ה-swap מציל אתכם. אם מדובר בבריחה של זיכרון, ה-swap הופך השבתה של חמש שניות לקיפאון של עשרים דקות. הקיפאון גרוע יותר, כיוון שתהליך שקרס עדיין משאיר לכם shell מתפקד, בעוד שרת שסובל מ-thrashing אינו מגיב.

swapon --show
free -h

פשרה מעשית ב-VPS קטן: החזיקו קובץ swap צנוע עבור דפים שהוקצו פעם אחת ולא ניגשים אליהם שוב, והגדירו MemorySwapMax=0 על היחידות שאתם מוכנים לאבד. השירותים החשובים ישמרו על ה-swap שלהם. הבלתי צפויים ייתקלו בקיר במהירות ויאותחלו.

הנמכת vm.swappiness היא מנוף חלש, וכדאי לדעת מדוע. היא רק משנה את האיזון בין פינוי ה-page cache לבין העברת דפים אנונימיים ל-swap, ושתי הפעולות הללו יעלו בקריאת דיסק מאוחר יותר. היא משנה אילו דפים יגרמו ל-thrashing, לא אם השרת יסבול ממנו.

דמון OOM מוקדם מבצע עצירה לפני הקיפאון

הקרנל ממתין עד לכישלון מוחלט של תהליך ה-reclaim, ובשרת VPS קטן, זמן ההמתנה הזה הוא בדיוק הרגע שבו השרת קורס. שני דמונים במרחב המשתמש (userspace) מונעים זאת על ידי ניטור עצמי של הזיכרון וביצוע פעולת עצירה מוקדמת יותר.

earlyoom מנטר את הזיכרון הזמין ואת ה-swap הפנוי, ומבצע SIGKILL לתהליך בעל הניקוד הגבוה ביותר כאשר אחד מהם יורד מתחת לסף מסוים.

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 את המינימום ל-swap פנוי; שניהם עומדים כברירת מחדל על 10 אחוזים. המספר השני בכל זוג הוא נקודת ה-SIGKILL: ה-earlyoom שולח SIGTERM ברגע שיורדים מתחת לערך הראשון, ולאחר מכן SIGKILL מתחת לערך השני, שערכו כברירת מחדל הוא מחצית מהערך הראשון. החילו שינויים באמצעות sudo systemctl restart earlyoom, וקראו את journalctl -u earlyoom כדי לראות איזה תהליך הופסק וכמה זיכרון הוא תפס.

systemd-oomd הוא האפשרות השנייה. דף ה-man שלו מתאר אותו כ"שירות מערכת המשתמש ב-cgroups-v2 ובמידע על לחץ (PSI) כדי לנטר ולנקוט פעולות מתקנות לפני שמתרחש OOM במרחב הקרנל". הוא פועל על cgroups שלמים במקום על תהליכים בודדים, ולכן הוא עוצר יחידה (unit) שלמה ולא תהליך בן בודד. יחידות מצטרפות לניטור באמצעות ManagedOOMMemoryPressure=kill או ManagedOOMSwap=kill, והספים מוגדרים ב-/etc/systemd/oomd.conf.

systemctl status systemd-oomd
oomctl

oomctl מציג את מה שהוא מנטר כרגע, שלעיתים קרובות אינו כולל דבר ב-image של שרת, כיוון שההגדרה דורשת הצטרפות אקטיבית (opt-in) לכל יחידה. בחרו דמון אחד והסתפקו בו. הרצת שניהם גורמת לשני תהליכים להתחרות על בחירת ה"קורבן", והסיבה לכל עצירה הופכת לקשה יותר לשחזור.

איזו יחידה הייתה אחראית?

התחילו בבדיקת ה-kernel, כיוון שהוא מתעד כל תהליך שהוא מסיים (kill).

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

סיום תהליך על ידי ה-OOM killer הגלובלי נראה כך:

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 בדוגמה זו. התייחסו בחשדנות לשם המופיע בסוגריים. זהו הקורבן שה-kernel בחר, וה-kernel בוחר תמיד את התהליך הגדול ביותר, שלא תמיד הוא זה שגרם למחסור במשאבים.

סיום תהליך עקב חריגה ממגבלת cgroup מופיע עם תחילית שונה, והדיווח שמעליו מציין את ה-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= ועברה השהיה. max סופר כמה פעמים היא הגיעה למגבלה הקשיחה (hard cap), ו-oom_kill סופר תהליכים שנסגרו בפועל. ערך high גבוה לצד oom_kill 0 מעיד על המקרה השקט שצוין קודם: השירות רץ, איטי מאוד, ולא דיווח על כשל לאף גורם. memory.peak (ב-Linux 5.19 ומעלה) מכיל את השימוש הגבוה ביותר אליו הגיע ה-cgroup, וזהו המספר לפיו יש לקבוע את MemoryMax=. שני הקבצים מתאפסים עם אתחול היחידה, כיוון ש-systemd יוצר את ה-cgroup מחדש.

תנאי מקדים אחד עומד בבסיס כל אלו. אם /var/log/journal אינו קיים, ה-journal נשמר ב-RAM, וכל שורה נמחקת לאחר ה-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 הנוכחי מעיד על כך שההיסטוריה נשמרת, ולכן journalctl -k -b -1 יכול להציג לכם את הודעות ה-kernel מה-boot שקרס.

נקודת התחלה עבור VPS קטן

בתוכנית של 2 GB, הקצו 300 עד 400 MB עבור ה-kernel ו-page cache, ואל תתנו למגבלות להצטבר לכדי 2 GB מלאים, כיוון שכל יחידה עלולה להגיע לשיא צריכה בו-זמנית. הקצו לשירות החשוב ביותר את החלק הגדול ביותר, ולאחר מכן הגבילו את כל השירותים המשניים סביבו.

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

שמירה על דרך גישה לשרת מצדיקה הגדרה נוספת. OOMScoreAdjust=-500 בתוך קובץ drop-in עבור ssh.service הופכת את ה-OOM killer הגלובלי להרבה פחות סביר לבחור ב-SSH daemon שלכם כקורבן, מה שמהווה את ההבדל בין תיקון השרת לבין אתחולו מלוח הבקרה. הגדרה זו משנה רק את בחירת הקורבן של ה-kernel. היא אינה מקצרת את זמן הקיפאון.

מכולות רצות בתוך cgroups משל עצמן, הנוצרים על ידי ה-container runtime ולא על ידי קובצי ה-unit שלכם, לכן מגבלה על docker.service אינה הופכת למגבלה על מכולה בודדת. המקבילות לכל מכולה של MemoryMax= ו-CPUQuota= מכוסות ב-הגדרת מגבלות זיכרון ומעבד ב-Docker Compose.

FAQ

מדוע ה-VPS שלי קפא במקום להרוג את התהליך שצורך משאבים?

הסיבה היא שה-kernel בוחן התקדמות לפי החזרת דפים (pages) מהזיכרון, ולא לפי משך הזמן שלוקח לבצע זאת. כאשר הזיכרון דל, המערכת מפנה את ה-page cache, כולל דפי הרצה של תוכניות פעילות, ואז קוראת אותם שוב בפקודה הבאה. הכל ממתין לאחסון ואף הקצאה לא נכשלה טכנית, לכן ה-OOM killer לא מופעל. בדקו את /proc/pressure/memory בזמן שהבעיה מתרחשת: ערך full avg10 הגבוה מ-40 אומר שכמעט אף משימה לא הצליחה לרוץ בעשר השניות האחרונות. daemon במרחב המשתמש כמו earlyoom מבצע הריגה לפני שהשרת מגיע למצב זה.

מה ההבדל בין MemoryHigh לבין MemoryMax?

MemoryHigh= הוא רף רך שמבצע ויסות (throttling). ה-kernel מפנה משאבים באגרסיביות מה-unit ומאט את ההקצאות שלו, אך השימוש יכול לחרוג מהמספר ושום דבר לא נהרג. MemoryMax= הוא רף קשיח: הקצאה שלא ניתן לעמוד בה תחת רף זה מפעילה את ה-OOM killer בתוך ה-cgroup של אותו unit, כך שהתהליך שגרם לבעיה הוא זה שנהרג, במקום התהליך הגדול ביותר בשרת. הגדירו את MemoryHigh= מתחת ל-MemoryMax= והתייחסו למרווח ביניהם כאזור אזהרה.

איך אוכל למצוא איזה שירות ה-OOM killer הרג?

הריצו את journalctl -k --grep "Killed process" --since "2 hours ago". שורה שמתחילה ב-Memory cgroup out of memory מציינת ש-unit מסוים הגיע ל-MemoryMax= שלו, בעוד ש-Out of memory פשוט מציין שהזיכרון של כל המכונה אזל. לאחר מכן הריצו את journalctl -u <unit> -n 50 וחפשו את Failed with result 'oom-kill'. אם /var/log/journal לא קיים בשרת שלכם, ה-journal נשמר ב-RAM והראיות אבדו עם ה-reboot; לכן, צרו את התיקייה הזו לפני האירוע הבא.

האם כדאי להוסיף swap ל-VPS קטן?

קובץ swap קטן עוזר עם דפים "קרים" שהוקצו פעם אחת ולא נעשה בהם שימוש נוסף. הוא לא עוזר עם תהליך שבורח: הוא רק מעכב את ההריגה ומחליף הפסקה קצרה בקיפאון ממושך שלא ניתן להתחבר אליו כדי לתקן. שמרו על swap צנוע, והגדירו MemorySwapMax=0 ב-units שאתם מוכנים לאבד, כך שהם יגיעו לתקרה שלהם ויאותחלו במהירות בעוד השירותים החשובים ישמרו על ה-swap שלהם.

האם ניתן להגביל פקודה בלי לכתוב קובץ unit?

כן. sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh מריץ את הפקודה ב-terminal שלכם בתוך transient scope עם המגבלות הללו, והמגבלות נעלמות כשהיא מסתיימת. כל מאפיין מ-systemd.resource-control זמין לאחר -p, כך ש-MemorySwapMax=, TasksMax= ו-CPUWeight= עובדים גם שם. השמיטו את --scope והוסיפו את --unit=name כדי להריץ את המשימה ברקע עם הפלט שלה ב-journal.