هذا المقال يتطلب مستوى: متوسط
لو Node أو Nginx بتاعك بدأ فجأة يرمي EMFILE: too many open files والاتصالات بترفض، والـ CPU والـ RAM لسه مرتاحين، المشكلة مش في الكود ولا في السيرفر. انت خلّصت واصفات الملفات المسموح بيها للعملية. المقال ده هيوريك تكتشفها بأمر واحد وتصلّحها صح من غير ما تخبّي مشكلة أكبر.
لما السيرفر يقول Too many open files وهو مرتاح
المشكلة باختصار
كل عملية في لينكس ليها سقف لعدد الملفات اللي تقدر تفتحها في نفس اللحظة. والمصيبة إن "الملف" هنا مش بس ملف على الديسك. أي اتصال شبكة، أي socket، أي pipe، وحتى الـ log اللي بتكتب فيه، كله بيحجز واصف ملف واحد. السقف الافتراضي على أغلب التوزيعات هو 1024. يعني تطبيق بيستقبل اتصالات كتير ممكن يضرب في السقف ده وهو لسه بيستهلك 40% من الميموري بس.
يعني إيه "واصف ملف" أصلًا؟
تخيّل معايا لوحة مفاتيح في ريسبشن فندق فيها 1024 خطّاف. كل ضيف بيدخل بياخد مفتاح من خطّاف فاضي. طول ما فيه خطاطيف فاضية، أي ضيف جديد ياخد مفتاح ويدخل عادي. أول ما آخر خطّاف يتشغل، الضيف اللي بعده بيقف على الباب مهما كان الفندق نفسه مليان بالغرف الفاضية. المشكلة مش في عدد الغرف، المشكلة في عدد الخطاطيف.
ده بالظبط اللي بيحصل مع واصفات الملفات. الضيف = اتصال أو ملف مفتوح، والخطّاف = واصف الملف، ولوحة الخطاطيف = حد الـ nofile بتاع العملية.
بالتعريف الدقيق: واصف الملف (file descriptor) هو رقم صحيح صغير بيرجّعه الكيرنل للعملية لما تفتح ملف أو socket. الرقم ده مؤشّر لمدخل في جدول الملفات المفتوحة الخاص بالعملية. الكيرنل بيحدّ حجم الجدول ده عبر RLIMIT_NOFILE، وله قيمتان: soft limit (السقف الفعّال اللي بتصطدم بيه) وhard limit (أقصى قيمة تقدر ترفع لها الـ soft من غير صلاحيات root).
سيناريو واقعي
عندك سيرفر WebSocket للإشعارات اللحظية، كل مستخدم متصل بيفتح اتصال دائم واحد. الاتصال ده = واصف ملف. لو الـ soft limit عندك 1024، أول ما توصل لحوالي 1000 مستخدم متزامن (فيه واصفات محجوزة أصلًا لـ stdin وstdout والـ logs وملفات أخرى)، السيرفر بيبدأ يرمي EMFILE ويرفض أي مستخدم جديد. المستخدمين المتصلين خلاص شغّالين، بس اللي بيحاول يدخل بعد كده بياخد فشل اتصال. النتيجة: شكاوى عشوائية "مش بيفتح معايا" بينما الـ dashboard بيقول إن السيرفر مرتاح.
اكتشفها في 30 ثانية
شوف الحد الحالي للعملية نفسها مش للـ shell بتاعك، لأنهم ممكن يكونوا مختلفين:
PID=$(pgrep -o nginx)
cat /proc/$PID/limits | grep 'open files'
ls /proc/$PID/fd | wc -l
cat /proc/sys/fs/file-nr
لو cat /proc/$PID/limits رجّعلك في عمود الـ Soft Limit، وعدد الواصفات المفتوحة قرّب منه، ده تأكيد إن ده هو الخنّاق.