HeadlinesBriefing HeadlinesBriefing.com

انقطاع مؤقت لـ Go GC بسبب النطاق المبادل

Hacker News •
×

أكتب هذا لأنك لن تضرب جبينك مثلي تقريبًا عندما قررت تشغيل النطاق المبادل في الإنتاج لامتصاص ذروات الذاكرة. كان لدي cgroup يحتوي على عمليةين: الأولى هي عملية Go تستدعي io. ReadAll ثم proto. Unmarshal، وتنشئ blob ثم هيكل بيانات رسم بياني (وهو مُعلم ككائن فحص بواسطة مخصّص Go). العملية الأخرى هي خادم HTTP يظل هادئًا في معظم الأحيان. في كل مرة يعمل فيها المجمع، يقرأ تلك النطاقات القابلة للفحص، مؤشرًا بمؤشر، ويقرر ماذا يفعل بها. لذا فكرت: حسنًا، تحت ضغط الذاكرة، سيقوم النواة بإخلاء الصفحات إلى جهاز النطاق المبادل، لكن بما أن الإخلاء هو لكل cgroup، وليس لكل عملية، فإن صفحات كلا العمليتين سيتم إخلاؤها، لذا فرصة حدوث رقصة حزينة من التبادل بين النواة ومجمع النفايات ضعيفة. كنت مخطئًا. أثناء تجريبي لهذا الأمر، وجدت مشكلة قد تكون أذرتني: مجمع نفايات Go يقرأ بياناته الوصفية (خارج الكومة، في منطقة غير محررة) خلال إيقاف عالمي كامل، وهذه البيانات الوصفية قد تكون في النطاق المبادل. قمت بتشغيل تجريبي على جهاز Hetzner باستخدام نواة 6.8 مع تفعيل MGLRU. كانت الإيقافة المتوسطة حوالي 51 مايكروثانية. مع البيانات الوصفية على الذاكرة غير القابلة للبرمجة NVMe، كانت أسوأ إيقافة 40 مليثانية. لفحص أين ذهبت تلك 40 مليثانية، كتبت برنامج bpf صغير يحسب أخطاء الصفحات أثناء توقف العالم. هذه كانت الأسوأ: 39902 مايكروثانية، أخطاء خلالها 228، 39013 مايكروثانية في الأخطاء. 39 من تلك 40 مليثانية استغرقت في 228 خطأ صفحة. حدثت تلك الأخطاء داخل كتابة GC. هذه نمط فشل محتمل.

GC في Go يجب أن يوقف العالم في نقطتين: عندما ينهي المسح، وعندما ينهي التمثيل. كنا لدينا 312 من هذه الإيقافات في غضون 30 دقيقة. إليك لماذا يحدث ذلك: يخصص وقت التشغيل تلك الصفحات. لا تُحرر، لكن تُعاد استخدامها. تُقرأ هذه الصفحات في دورات GC. لأن النواة تُخلّي الصفحات حسب العمر، فإنها ترسل الصفحات الأقل حديثًا إلى النطاق المبادل. يعمل GC، يوقف العالم، يحاول قراءة تلك الصفحات، لكن الآن لدينا خطأ صفحة رئيسي. النواة بحاجة إلى قراءة الـ PTE، ثم استدعاء do_swap_page، العثور على إطار جديد، تحميله على cgroup، قراءة الصفحات، إرسال bio، الانتظار للقرص، ووضعها عائدة في الذاكرة — فقط لتلخيص الأمر. تلك 40 مليثانية تبدو خالية الخطر في البداية. لكننا نتحدث عن إيقاف عالمي كامل. تلك 40 مليثانية تعني أن كل شيء توقف — بمصطلحات Go، كل P توقف، لذا على سبيل المثال، إذا كانت goroutine تنتظر إدخال/إخراج، أثناء تلك الإيقافة قد يعود الإدخال/الإخراج ولن يكون هناك أحد لمعالجته. 40 مليثانية هي 800 مرة الإيقافة المتوسطة. يحدث ذان أو ثلاث مرات في كل ذروة ذاكرة أثناء الاختبار. إنه كثير.

المصدر: Hacker News · لخّصه HeadlinesBriefing