یکی از ترسناکترین لحظه ها برای هر مهندس بکاند یا DevOps این است که سرویس اصلی ناگهان بدون هیچ خطای واضحی در لاگ اپلیکیشن از کار بیفتد. بعد از بررسی متوجه میشوید که process توسط سیستمعامل Kill شده است. در بیشتر موارد مقصر اصلی OOM Killer است.
OOM مخفف Out Of Memory است. وقتی حافظه سیستم به حدی کم شود که کرنل لینوکس دیگر نتواند حافظه آزاد کند، مکانیزمی به نام OOM Killer فعال میشود و یکی از processها را انتخاب و با سیگنال SIGKILL از بین میبرد. این کار برای جلوگیری از قفل کامل سیستم انجام میشود، اما انتخاب قربانی همیشه منطقی به نظر نمیرسد و گاهی مهمترین سرویس های شما قربانی میشوند.
OOM Killer چگونه کار میکند؟
کرنل برای هر process یک امتیاز به نام oom_score محاسبه میکند. این امتیاز بر اساس میزان حافظه ای که process مصرف کرده، مدت زمان اجرای آن و چند فاکتور دیگر تعیین میشود. process با بالاترین امتیاز، اولین کاندیدای کشته شدن است. شما میتوانید این امتیاز را از طریق فایل /proc//oom_score ببینید و حتی با نوشتن در /proc//oom_score_adj آن را تنظیم کنید.
مقدار ۱۰۰۰- به معنای «این process را هرگز نکش» است و ۱۰۰۰ یعنی «اولویت بالا برای کشتن». در محیطهای production، سرویسهای حیاتی معمولا با oom_score_adj منفی محافظت میشوند.
دلایل رایج فعال شدن OOM Killer در سرویسهای بکاند:
۱. Memory Leak در اپلیکیشن
شایعترین علت، اگر آبجکتها، connectionها، cacheها یا bufferها آزاد نشوند، مصرف حافظه به مرور افزایش مییابد تا جایی که سیستم به مرز OOM میرسد.
۲. تنظیم نادرست Heap یا Cache:
در جاوا، Node.js، Python یا Go، اگر حداکثر حافظه (مثل -Xmx در JVM یا --max-old-space-size در Node) خیلی بالا تنظیم شده باشد، اپلیکیشن میتواند تقریبا تمام RAM سرور را مصرف کند و فضای کمی برای سیستمعامل و سایر processها باقی بگذارد.
۳. عدم وجود Limit در Container:
در Docker یا Kubernetes اگر memory limit تعیین نشده باشد، یک container میتواند تمام حافظه نود را بگیرد و باعث OOM شدن کل ماشین شود.
۴. Cacheهای بدون محدودیت:
Redis، Memcached یا حتی cache داخل اپلیکیشن اگر اندازه مشخصی نداشته باشند، میتوانند حافظه را پر کنند.
۵. ترافیک ناگهانی یا Queryهای سنگین:
یک گزارش سنگین یا افزایش ناگهانی درخواست ها میتواند باعث allocation موقت ولی بزرگ حافظه شود.
چگونه تشخیص دهیم OOM Killer مقصر بوده؟
دستور dmesg | grep -i "killed process" یا dmesg | grep -i oom را اجرا کنید. معمولا پیام واضحی را میبینید که نشان میدهد کدام process کشته شده و چرا.
در سیستم های جدیدتر میتوانید از journalctl -k | grep -i oom استفاده کنید.
متریک های حافظه را بررسی کنید: اگر قبل از حادثه، available memory به شدت کاهش یافته و swapهم پر شده، احتمال OOM بسیار بالاست.
در Kubernetes، eventهای نود را چک کنید (kubectl describe node). معمولا پیام OOMKilled برای pod ثبت میشود.
راه های پیشگیری عملی:
در سطح اپلیکیشن:
Memory leak را جدی بگیرید. از ابزارهای profiling (مثل pprof در Go، heap snapshot در Node، یا VisualVM در جاوا) به طور منظم استفاده کنید.
برای cacheها حتما اندازه حداکثر و سیاست eviction تعریف کنید.
Timeout و محدودیت اندازه برای درخواست ها و پاسخ ها بگذارید تا allocationهای بزرگ غیرمنتظره رخ ندهد.
در سطح سیستم عامل و Container:
همیشه برای سرویسهای مهم memory limit و memory request تعریف کنید.
از oom_score_adj برای محافظت از processهای حیاتی استفاده کنید.
Swap را کاملاً غیرفعال نکنید مگر اینکه دلیل قوی داشته باشید. وجود مقدار کمی swap میتواند فرصت بازیابی بدهد (هرچند در containerها معمولاً توصیه نمیشود).
متریکهای حافظه را مانیتور کنید و روی memory usage نزدیک به limit آلارم بگذارید، نه فقط وقتی process کشته شده.
یک عادت خوب
به جای اینکه بعد از هر OOM فقط limit حافظه را بالا ببرید، اول علت مصرف را پیدا کنید. بالا بردن limit بدون رفع نشت حافظه، فقط زمان وقوع مشکل بعدی را عقب میاندازد و در نهایت باعث میشود نودهای بزرگتر و گرانتری نیاز داشته باشید.
OOM Killer دشمن شما نیست؛ در واقع آخرین خط دفاع سیستم عامل است. اما اگر مرتب فعال میشود، یعنی طراحی یا پیاده سازی حافظه در سیستم شما نیاز به بازنگری دارد.
