كلاود
بنية تحتية

الذاكرة في لينكس: swap وقاتل OOM ولماذا توقفت خدمتك فجأة

كلاود|٢٨ أكتوبر ٢٠٢٦|6 دقائق قراءة|١٠٢ مشاهدة
الذاكرة في لينكس: swap وقاتل OOM ولماذا توقفت خدمتك فجأة

اقرأ عمود available لا free، وراقب si وso في vmstat لتعرف هل swap يحميك أم يخفي المشكلة. احمِ قاعدة البيانات من OOM killer بإعداد systemd، وإذا كان الضغط نمواً حقيقياً فـ2GB RAM بـ600 ريال سنوياً أرخص من أسابيع الضبط.

إذا توقفت قاعدة بياناتك أو تطبيقك فجأة بلا رسالة خطأ في سجلاته، فأول أمر تشغله هو dmesg -T | grep -i "killed process". إن ظهر سطر فيه Out of memory: Killed process فالقاتل معروف: OOM killer، آلية في نواة لينكس تختار عملية وتنهيها بالقوة حين تنفد الذاكرة تماماً حتى لا يتجمد السيرفر كاملاً. لفهم ما حدث تحتاج قراءة ثلاثة أرقام بشكل صحيح. الأول عمود available في ناتج free -h، وهو الذاكرة المتاحة فعلاً للتطبيقات الجديدة، وليس عمود free الذي يبدو دائماً صغيراً لأن لينكس يستخدم الذاكرة الفارغة كـcache للقرص. الثاني نشاط swap في ناتج vmstat 1، فقيم مستمرة فوق الصفر في عمودي si وso تعني أن السيرفر يختنق. الثالث نمو استهلاك عملية معينة يوماً بعد يوم، وهذا علامة تسريب. الحل حسب السبب: swap صغير كشبكة أمان، وحماية قاعدة البيانات من OOM بإعداد systemd، وضبط حدود العمّال في PHP-FPM، وأحياناً يكون الحل الأرخص والأسرع إضافة 2GB RAM بـ600 ريال سنوياً بدل أسابيع من الضبط. هذا المقال يشرح كل خطوة بالأوامر والأرقام.

اقرأ free بشكل صحيح

هذا ناتج نموذجي لسيرفر بذاكرة 8GB:

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       3.1Gi       312Mi       180Mi       4.4Gi       4.3Gi
Swap:          2.0Gi        64Mi       1.9Gi

كثيرون ينظرون إلى عمود free فيرون 312MB فقط ويظنون أن السيرفر على وشك الانهيار. الحقيقة أن لينكس يضع الذاكرة غير المستخدمة في خدمة cache الملفات (عمود buff/cache) لأن الذاكرة الفارغة ذاكرة مهدرة، وحين يحتاج تطبيق ذاكرة تُحرر الـcache خلال أجزاء من الثانية. الرقم الذي يعنيك هو available، وهو هنا 4.3GB، أي أن السيرفر مرتاح تماماً.

نسبة available من الإجماليالحالةماذا تفعل
أكثر من 30%مريحةلا شيء، راقب الاتجاه فقط
من 10% إلى 30%تحتاج انتباهاًراجع أكبر المستهلكين وحدودهم
أقل من 10%خطرOOM أو اختناق swap قريب، تصرف الآن

لمعرفة أكبر المستهلكين مرتبين حسب الذاكرة الفعلية المستخدمة (RSS):

ps -eo pid,user,comm,rss --sort=-rss | head -10

قيمة RSS بالكيلوبايت، فـ1,048,576 تعني 1GB تقريباً.

swap: شبكة أمان أم غطاء يخفي المشكلة

swap مساحة على القرص يستخدمها النظام حين تمتلئ الذاكرة، فينقل إليها صفحات قليلة الاستخدام. هي مفيدة في حالتين: امتصاص ذروة قصيرة غير متوقعة دون أن يتدخل OOM killer، ونقل صفحات خاملة فعلاً (مثل أجزاء من خدمات تعمل مرة في اليوم) لتحرير الذاكرة للـcache. لكنها تتحول لمشكلة حين يعتمد عليها السيرفر باستمرار، لأن القراءة من القرص أبطأ من الذاكرة بمئات إلى آلاف المرات حتى على SSD.

الأمر الذي يكشف الفرق:

vmstat 1 10

راقب عمودي si (ما يُقرأ من swap) وso (ما يُكتب إليه). قيمة swap مستخدمة ثابتة مثل 64MB مع si وso صفراً معظم الوقت وضع طبيعي تماماً. أما قيم مستمرة بالمئات أو الآلاف كل ثانية فهي ما يسمى thrashing، والسيرفر يقضي وقته في نقل الصفحات بدل العمل، فتصبح صفحة كانت تفتح في 200ms تحتاج 8 ثوانٍ. هنا swap لم يحمِك، بل أخفى نقص الذاكرة وحوّل انهياراً سريعاً إلى بطء طويل مؤلم.

توصيتنا لخادم سحابي بذاكرة من 2GB إلى 16GB: ملف swap بحجم 1GB إلى 2GB كشبكة أمان، مع خفض vm.swappiness من القيمة الافتراضية 60 إلى 10، حتى يفضّل النظام تحرير الـcache قبل نقل ذاكرة التطبيقات إلى القرص:

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

sysctl vm.swappiness=10
echo 'vm.swappiness=10' > /etc/sysctl.d/99-swappiness.conf

كيف يختار OOM killer ضحيته

حين تنفد الذاكرة وswap معاً، تحسب النواة لكل عملية رقماً اسمه oom_score من 0 إلى 1000، يعتمد أساساً على نسبة الذاكرة التي تستهلكها العملية، ثم تقتل صاحبة الرقم الأعلى. المشكلة واضحة: قاعدة البيانات عادة أكبر مستهلك للذاكرة على السيرفر، لأن InnoDB يحجز buffer pool كبيراً عن قصد، فتكون الضحية الأولى غالباً، مع أنها آخر ما تريد خسارته.

لرؤية الأرقام الحالية:

for p in $(pgrep -x mysqld) $(pgrep -f php-fpm | head -3); do
  echo "$p $(cat /proc/$p/comm) $(cat /proc/$p/oom_score)"
done

للتأثير في الاختيار يوجد oom_score_adj بقيم من -1000 إلى 1000. القيمة -1000 تعني لا تقتل هذه العملية أبداً، وهذه قيمة خطرة لأنها قد تدفع النظام لقتل خدمات أساسية مثل sshd فتفقد الوصول للسيرفر. الطريقة الصحيحة أن تجعل قاعدة البيانات أقل أولوية في القتل دون حصانة كاملة، عبر ملف إضافي لـsystemd:

systemctl edit mysql

[Service]
OOMScoreAdjust=-800

بعدها systemctl restart mysql. في المقابل يمكنك رفع القيمة لعمّال الطوابير أو PHP-FPM إلى 300 مثلاً، فإن اضطرت النواة للقتل قتلت عاملاً يعيد الإقلاع خلال ثانية بدل قاعدة بيانات تحتاج دقائق لاستعادة حالتها.

الحماية الأهم من كل ذلك ألا تصل لـOOM أصلاً، بحساب الحدود. على سيرفر بذاكرة 8GB يعمل عليه تطبيق PHP وMySQL معاً، توزيع معقول يكون: 3GB لـinnodb_buffer_pool_size، ونحو 1GB للنظام والخدمات الصغيرة، والباقي 4GB لعمّال PHP-FPM. إذا كان العامل الواحد يستهلك 80MB في المتوسط، فالحد الآمن لـpm.max_children هو 4,096 مقسومة على 80، أي نحو 50 عاملاً. الخطأ الشائع أن تُترك القيمة 200 أو أكثر، فيعمل كل شيء بهدوء حتى أول ذروة حقيقية.

كشف التسريب التدريجي

أصعب حالات OOM هي التي تحدث كل 9 أو 12 يوماً بانتظام، دون أي ذروة في الزيارات. هذا نمط تسريب: عملية تستهلك ذاكرة أكثر كل يوم ولا تعيدها. للكشف عنه سجّل استهلاك أكبر العمليات كل ساعة:

# في crontab
0 * * * * ps -eo pid,comm,rss --sort=-rss | head -5 | sed "s/^/$(date +\%F_\%H) /" >> /var/log/rss-track.log

بعد ثلاثة أيام افتح الملف وابحث عن عملية بنفس الرقم PID يرتفع RSS لها خطياً، مثل 400MB ثم 520MB ثم 650MB، بلا انخفاض. هذا تسريب مؤكد تقريباً. أشهر المصادر:

  • عمّال طوابير طويلة العمر: عامل Laravel أو Node.js يعمل أسابيع ويراكم كائنات في الذاكرة. الحل حد أقصى لعدد المهام أو للذاكرة ثم إعادة تشغيل تلقائية، مثل --max-jobs=1000 أو --memory=256.
  • PHP-FPM: الإعداد pm.max_requests = 500 يجعل كل عامل يعيد الإقلاع بعد 500 طلب، فيتخلص من أي تسريب في إضافات PHP.
  • cache داخل التطبيق بلا حد: مصفوفة أو Map تُضاف لها مفاتيح ولا تُحذف أبداً. الحل حد أعلى للحجم أو نقل الـcache إلى Redis بسياسة maxmemory.

للتأكد بعد الإصلاح، راقب نفس الملف أسبوعاً كاملاً، فالمطلوب خط مستوٍ يتذبذب حول قيمة ثابتة.

متى يكون الحل ذاكرة إضافية

الضبط مهم، لكن له حد اقتصادي. افترض أن خادمك السحابي من باقة نمو بذاكرة 4GB، وأن available ينزل تحت 10% في ساعات الدوام كل يوم رغم أن الإعدادات معقولة ولا يوجد تسريب. أمامك طريقان:

الخيارالتكلفةالوقتالمخاطرة
أسابيع من ضبط الاستعلامات وتقليص العمّالساعات عمل مطور، وقد تكون أكثر من 10 أياممن أسبوعين إلى أربعةتوقفات متكررة أثناء التجربة
+2GB RAM600 ريال سنوياً، أي نحو 1.64 ريال يومياًدقائقشبه معدومة

القاعدة التي نستخدمها: إذا كان الضغط ناتجاً عن نمو حقيقي في الاستخدام وليس تسريباً، فالذاكرة الإضافية أرخص من يوم واحد من وقت مطور. أما إذا كان تسريباً، فإضافة الذاكرة تؤجل الانهيار فقط من 9 أيام إلى 14، والحل إصلاح المصدر.

خطة عمل من خمس خطوات

  1. نفّذ dmesg -T | grep -i oom وjournalctl -k | grep -i "out of memory" لتعرف هل حدث OOM ومن كانت ضحيته.
  2. اقرأ available في free -h وراقب si وso في vmstat 1 خلال ساعة الذروة.
  3. احسب حدود قاعدة البيانات وPHP-FPM والطوابير بحيث لا يتجاوز مجموعها 85% من الذاكرة.
  4. احمِ قاعدة البيانات بـOOMScoreAdjust=-800، وأضف swap من 1GB إلى 2GB مع swappiness بقيمة 10.
  5. سجّل RSS كل ساعة لثلاثة أيام لاستبعاد التسريب، ثم قرر بين الإصلاح والذاكرة الإضافية.

على الخادم السحابي من كلاود تضيف 2GB RAM بـ600 ريال سنوياً دون إعادة بناء السيرفر أو نقل بياناتك، والسيرفرات في الرياض بزمن استجابة نحو 8ms على أقراص SSD. كل الباقات والإضافات في صفحة الأسعار.

مشاركة