فجأة ، لا يكفي وجود نظام لجمع القمامة

إليك قصة قصيرة عن إخفاقات الخادم الغامضة التي اضطررت إلى تصحيحها قبل عام (مقال بتاريخ 05 ديسمبر 2018 ، تقريبًا لكل عام). عملت الخوادم بشكل جيد لبعض الوقت ، ثم في مرحلة ما بدأت في الانهيار. بعد ذلك ، فشلت محاولات تشغيل أي برنامج تقريبًا على الخوادم مع وجود أخطاء "لا توجد مساحة على الجهاز" ، على الرغم من أن نظام الملفات أبلغ عن عدد قليل من الجيجابايت المشغولة على أقراص 20 جيجابايت تقريبًا.

اتضح أن المشكلة كانت بسبب نظام التسجيل. كان هذا تطبيق Ruby يأخذ ملفات السجل ، ويرسل البيانات إلى خادم بعيد ، ويحذف الملفات القديمة. كان الخطأ أن ملفات السجل المفتوحة لم يتم إغلاقها بشكل صريح. بدلاً من ذلك ، سمح التطبيق لمجمع القمامة التلقائي في Ruby بتنظيف كائنات الملف. تكمن المشكلة في أن كائنات الملفات لا تستهلك الكثير من الذاكرة ، لذا يمكن لنظام التسجيل من الناحية النظرية أن يبقي ملايين السجلات مفتوحة قبل الحاجة إلى جمع البيانات المهملة.

* أنظمة ملفات Nix تفصل أسماء الملفات والبيانات في الملفات. يمكن أن تحتوي البيانات الموجودة على القرص على العديد من أسماء الملفات التي تشير إليها (أي الروابط الثابتة) ، ولا يتم حذف البيانات إلا عند حذف الارتباط الأخير. يعتبر واصف الملف المفتوح رابطًا ، لذلك إذا تم حذف الملف أثناء قراءة البرنامج ، يختفي اسم الملف من الدليل ، ولكن تظل بيانات الملف حية حتى يغلق البرنامج. هذا ما حدث للمسجل. يقوم الأمر du ("استخدام القرص") بالبحث عن الملفات باستخدام قائمة الدليل ، لذلك لم ير غيغابايت من بيانات الملف لآلاف ملفات السجل التي كانت لا تزال مفتوحة. تم اكتشاف هذه الملفات فقط بعد تشغيل lsof ("قائمة الملفات المفتوحة").

بالطبع ، يحدث خطأ مماثل في حالات أخرى مماثلة. قبل بضعة أشهر ، اضطررت إلى تشغيل تطبيق Java الذي تعطل بعد بضعة أيام بسبب تسرب في اتصالات الشبكة.

اعتدت أن أكتب معظم التعليمات البرمجية الخاصة بي في C ، ثم في C ++. في تلك الأيام ، اعتقدت أن إدارة الموارد اليدوية كانت كافية. ما مدى تعقيد ذلك؟ يحتاج كل malloc () إلى الدالة () المجانية ، ويحتاج كل فتح () إلى إغلاق (). ببساطة. باستثناء أنه ليست كل البرامج بسيطة ، لذا أصبحت إدارة الموارد اليدوية بمرور الوقت عقبة. ثم اكتشفت ذات يوم عد الروابط وجمع القمامة. اعتقدت أنها تحل جميع مشاكلي ، وتوقفت تمامًا عن الاهتمام بإدارة الموارد. مرة أخرى ، بالنسبة للبرامج البسيطة ، كان هذا طبيعيًا ، ولكن ليست كل البرامج بسيطة.

لا يمكنك الاعتماد على جمع القمامة ، لأنها تحل فقط مشكلة إدارة الذاكرة ، ويجب على البرامج المعقدة أن تتعامل مع أكثر بكثير من مجرد الذاكرة. هناك ميم شائع يجيب على ذلك بحقيقة أن الذاكرة هي 95 ٪ من مشاكل الموارد . يمكنك حتى أن تقول أن جميع الموارد هي 0 ٪ من مشاكلك - حتى تنفد واحدة منها. ثم يصبح هذا المورد 100٪ من مشاكلك.

لكن مثل هذا التفكير لا يزال ينظر إلى الموارد على أنها حالة خاصة. مشكلة أعمق هي أنه عندما تصبح البرامج أكثر تعقيدًا ، يميل كل شيء إلى أن يصبح موردًا. على سبيل المثال ، خذ برنامج التقويم. يسمح برنامج التقويم المعقد للعديد من المستخدمين بإدارة تقاويم مشتركة متعددة ، ومع الأحداث التي يمكن مشاركتها عبر تقاويم متعددة. أي جزء من البيانات سيؤثر في النهاية على عدة أجزاء من البرنامج ، ويجب أن يكون ذا صلة وصحيح. لذلك ، بالنسبة لجميع البيانات الديناميكية ، تحتاج إلى مالك ، وليس فقط لإدارة الذاكرة. مع إضافة ميزات جديدة ، سيتعين تحديث المزيد والمزيد من أجزاء البرنامج. إذا كنت عاقلًا ، فلن تسمح لك سوى بتحديث البيانات من جزء واحد من البرنامج في كل مرة ،بحيث يصبح الحق والمسؤولية لتحديث البيانات في حد ذاته موردًا محدودًا. لا تؤدي نمذجة البيانات المتحولة باستخدام الهياكل الثابتة إلى اختفاء هذه المشاكل ، ولكنها تترجمها فقط إلى نموذج آخر.

يعد تخطيط ملكية الموارد وعمرها جزءًا لا مفر منه في تصميم البرامج المعقدة. هذا أسهل إذا كنت تستخدم بعض الأنماط الشائعة. أحد الأنماط هو موارد قابلة للتبديل. مثال على ذلك هو السلسلة غير الثابتة "foo" ، والتي هي نفس دلالة مثل أي "foo" غير قابلة للتغيير. هذا النوع من الموارد لا يحتاج إلى عمر محدد أو حيازة. في الواقع ، من أجل جعل النظام بسيطًا قدر الإمكان ، من الأفضل ألا يكون لديك عمر أو ملكية محددة مسبقًا (مرحبًا Rust ، تقريبًا لكل). نمط آخر هو الموارد التي لا يمكن استبدالها ، ولكن لها عمر محدد. يتضمن هذا اتصالات الشبكة ، بالإضافة إلى المزيد من المفاهيم المجردة ، مثل الحق في التحكم في جزء من البيانات.الشيء الأكثر منطقية هو ضمان عمر هذه الأشياء بشكل واضح عند الترميز.

لاحظ أن جمع القمامة التلقائي جيد حقًا لتنفيذ النمط الأول ، ولكن ليس الثاني ، في حين أن تقنيات إدارة الموارد اليدوية (مثل RAII) رائعة لتنفيذ النمط الثاني ، ولكنها رهيبة للنمط الأول. يصبح هذان النهجان متكاملين في البرامج المعقدة.

Source: https://habr.com/ru/post/undefined/


All Articles