تحسين استخدام موارد الخادم من خلال تتبع تسربات الذاكرة


كانت الفرضية الأولى التي بحثناها هي أن نمط استخدام الذاكرة في الألعاب كان يتغير. نحن نشجع المطورين على تخطي حدود المنصة، واستخدام الموارد المتاحة لهم لإنشاء ألعاب رائعة ومبتكرة. كان من السهل التحقق من ذلك، حيث كان بإمكاننا تجميع استخدام الذاكرة لجميع الألعاب ومعرفة ما إذا كان قد ارتفع. لكن لم يحدث ذلك، فقد بقي متوسط الذاكرة والنسب المئوية المجمعة لكل لعبة ثابتة نسبيًا بينما كانت سعتنا تتناقص بشكل مطرد.
نقوم بتخزين تيرابايتات من بيانات الأداء واستخدام الموارد شهريًا، والتي يمكن تجميعها وتصفيتها للمساعدة في العثور على السبب الجذري لمشاكل مثل هذه. حاولنا عزل المشكلة في منطقة جغرافية معينة أو نوع معين من الأجهزة أو إصدار معين من البرامج، لكن للأسف كانت المشكلة موجودة في كل مكان. ثم قررنا إجراء فحص عشوائي لعدد قليل من خوادم الألعاب وإجراء بعض التحقيقات الدقيقة. في البداية، أضللني مفهوم الذاكرة "الحرة" في أنظمة Linux. وهي مشكلة شائعة جدًا لدرجة أنها دفعت شخصًا ما إلى تسجيل نطاق وإنشاء موقع ويب لشرح فئات الذاكرة: https://www.linuxatemyram.com.

TL;DR: إن استخدام معظم الذاكرة أمر جيد، والذاكرة الخالية هي ذاكرة مهدرة. لا داعي للقلق إلا عندما تقترب الذاكرة المتاحة من 0.
بمجرد أن تأكدنا من أننا نتابع الذاكرة بشكل صحيح، بدأنا في إجراء مجموعة من التجارب لتحديد أين يتم استخدام الذاكرة. كان نهجنا هو تتبع فئات فرعية محددة من الذاكرة حتى نتمكن من التوصل إلى حل محدد.

يتم حساب الذاكرة المتاحة تقريبًا على أنها مجموع MemFree + Active(file) + Inactive(file) + SReclaimable.
تتتبع MemFree الذاكرة غير المستخدمة.
تتتبع Active(file) و Inactive(file) ذاكرة ذاكرة التخزين المؤقت للصفحة. تخزن ذاكرة التخزين المؤقت للصفحة البيانات التي تم الوصول إليها في الذاكرة لتقليل كمية عمليات الإدخال/الإخراج للقرص.
تتتبع SReclaimable ذاكرة slab القابلة للاسترداد. تُستخدم ذاكرة slab لحفظ ذاكرات التخزين المؤقتة للكائنات المُهيأة التي يستخدمها النواة بشكل شائع.
كانت التجربة الأولى هي تعديل ضبط ذاكرة التخزين المؤقت، وتحديدًا: vm.vfs_cache_pressure و vm.dirty_background_ratio.
سيؤدي زيادة vfs_cache_pressure إلى زيادة احتمالية استعادة الكائنات من ذاكرة التخزين المؤقت القابلة للاسترداد. وهذا يؤثر على الأداء (سواء في حالات فقدان ذاكرة التخزين المؤقت أو وقت البحث للعثور على الكائنات القابلة للتحرير).
dirty_background_ratio هي النسبة المئوية (لذاكرة ذاكرة التخزين المؤقت للصفحة التي تكون قذرة) عندما نبدأ الكتابة على القرص بطريقة غير معطلة.
أجرينا التغييرات التالية ولاحظنا التأثيرات في الذاكرة و slabinfo و cgroups.
vm.vfs_cache_pressure= 100 ==> 10000<br>vm.dirty_background_ratio= 10 ==> 5كانت الطريقة غير التدخلية لتتبع النتيجة هي تشغيل مهمة cron بشكل دوري لأخذ لقطة لحالة الذاكرة. شيء مثل:
#!/bin/bash<br>now=`date +%Y-%m-%d.%H:%M`
# create test dir<br>mkdir -p ~/memtest # log meminfo<br>sudo cat /proc/meminfo
~/memtest/meminfo_$now# log slabinfo<br>sudo cat /proc/slabinfo
~/memtest/slabinfo_$now # log cgroups<br>sudo cat /proc/cgroups
~/memtest/cgroups_$now بعد تطبيق تغييرات ضغط ذاكرة التخزين المؤقت والمراقبة لبضع ساعات، بدأنا التحليل. خلصنا إلى أننا اكتسبنا حوالي 8 جيجابايت من الذاكرة الحرة، لكن تلك الذاكرة جاءت مباشرة من ذاكرة التخزين المؤقت للصفحات والأقراص، وفئتي Active(file) و Inactive(file). كانت هذه نتيجة مخيبة للآمال، فلم تكن هناك زيادة صافية كبيرة في الذاكرة المتاحة، بالإضافة إلى أننا لم نعد نستخدم هذه الذاكرة بشكل مثمر. كان علينا استعادة الذاكرة من مكان آخر.
بعد فشلنا في زيادة الذاكرة المتاحة مباشرةً، حاولنا تقليل الفئات المتنافسة. لاحظنا أن فئة الذاكرة SUnreclaim كانت كبيرة، حيث تضخمت في بعض الحالات إلى 60 جيجابايت على مدى بضعة أشهر. تتعقب فئة SUnreclaim الذاكرة المستخدمة لمجموعات الكائنات من قبل نظام التشغيل والتي لا يمكن استردادها تحت ضغط الذاكرة. كانت أول علامة على وجود مشكلة هي العدد المتزايد باستمرار لمجموعات cgroups. كنا نتوقع وجود بضع مئات من مجموعات cgroups على الأكثر من تشغيل عملياتنا التي تعمل على Docker، لكننا كنا نرى مجموعات cgroups بالمئات الآلاف. لحسن حظنا، يبدو أن مهندسًا آخر هو رومان غوشين من Facebook قد اكتشف مؤخرًا هذه المشكلة بالذات وأصلحها على مستوى النواة https://patchwork.kernel.org/cover/10943797/. ويقول:
المشكلة الأساسية بسيطة للغاية: أي صفحة محملة على مجموعة cgroup تحتفظ بمرجع إليها، لذا لا يمكن استعادة مجموعة cgroup ما لم تختفِ جميع الصفحات المحملة. إذا كان كائن slab مستخدمًا بشكل نشط من قبل مجموعات cgroup أخرى، فلن يتم استعادته، وسيمنع استعادة مجموعة cgroup الأصلية.
بدا أن هذه هي مشكلتنا بالضبط، لذا انتظرنا بفارغ الصبر إصدار النواة 5.3 للتحقق من صحة الإصلاح.
أعدنا استخدام البرنامج النصي لتتبع الذاكرة من تجربة ضغط ذاكرة التخزين المؤقت، ولكن بالنسبة لتجربة النواة، أردنا إنشاء مجموعات تحكم ومجموعات تجريبية. قمنا بتفريغ حركة مرور الإنتاج من رفين من الخوادم، ثم قمنا بترقية النواة إلى الإصدار 5.3 على أحد الرفين وأبقينا النواة 5.0 على الرف الآخر. ثم أعدنا تشغيل كلا الرفين وفتحناهما لحركة مرور الإنتاج مرة أخرى. بعد حوالي أسبوع، قمنا بتتبع كيفية تغير مجموعات cgroups وذاكرة slab غير القابلة للاسترداد بمرور الوقت. وإليكم النتائج:


يتميز النواة 5.0.0 بنمو مستمر في مجموعات cgroups، ويكتسب خلال أسبوع واحد 4 غيغابايت من ذاكرة slab غير القابلة للاسترداد، ليصل المجموع إلى 6 غيغابايت. من ناحية أخرى، تشهد النواة 5.3.7 انخفاضات يومية كبيرة في مجموعات cgroups، كما أن نمو ذاكرة slab غير القابلة للاسترداد بطيء للغاية. بعد أسبوع، تبلغ ذاكرة slab غير القابلة للاسترداد حوالي 2 جيجابايت. مع النواة الجديدة، تستقر ذاكرة slab غير القابلة للاسترداد عند حوالي 4 جيجابايت، حتى بعد عدة أشهر من التشغيل.
المشكلة الأساسية التي أردنا حلها هي أن خوادم ألعابنا كانت تفقد سعتها بمرور الوقت. كان هذا بسبب انخفاض الذاكرة المتاحة، والذي كان بسبب النمو المستمر للذاكرة غير القابلة للاسترداد. لذا، بمجرد أن تمكنا من السيطرة على ذلك نسبيًا بفضل إصلاح النواة، ماذا كان التأثير على سعة الخادم؟

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


