almessadi.
العودة إلى الفهرس

كيفية العثور على تسريبات الذاكرة في Node.js باستخدام لقطات الذاكرة_

لقطات الذاكرة، والحجم المحتفظ به، وأنماط مستمع الأحداث التي تبقي الطلبات القديمة حية لفترة طويلة بعد أن كان يجب أن يتم جمعها كقمامة.

تاريخ النشر15 أبريل 2024
وقت القراءة10 min read

تعتبر تسربات الذاكرة البطيئة في Node.js مملة عادةً بأبشع الطرق الممكنة.

تبدأ الخدمة بشكل جيد. يبدو أن الإنتاجية طبيعية. استهلاك وحدة المعالجة المركزية مقبول. ثم تتزايد الذاكرة بشكل ثابت لساعات حتى يتم إعادة تشغيل الحاوية وتبدأ الدورة مرة أخرى.

تلك النمط هو هدية. يعني أنه يمكنك التحقيق بشكل منهجي.

ابدأ بإثبات أنها تسرب

رسم بياني للذاكرة يرتفع وحده ليس كافيًا.

اسأل:

  • هل تنخفض الذاكرة بعد انخفاض الحركة؟
  • هل النمو في heapUsed، rss، أو كليهما؟
  • هل تغير معدل الطلب أو حجم الحمولة في نفس الوقت؟

إذا كانت الذاكرة تستمر في الارتفاع دون العودة، ابدأ في البحث عن مراجع محتفظ بها.

لقطات الذاكرة هي أسرع أداة جدية

إذا كان التسرب في كائنات JavaScript، خذ لقطات للذاكرة:

import { writeHeapSnapshot } from "node:v8";

const path = writeHeapSnapshot();
console.log(`heap snapshot: ${path}`);

افتحها في أدوات التطوير (DevTools) وقم بالفرز حسب الحجم المحتفظ به.

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

نمط تسرب شائع جداً

يظهر هذا الشكل في الخدمات الحقيقية طوال الوقت:

app.use((req, _res, next) => {
  db.on("queryCompleted", () => {
    logger.info({ userId: req.user.id }, "query completed");
  });

  next();
});

المشكلة ليست فقط في عدد المستمعين. إنها ما يغلق عليه المستمع.

يضيف كل طلب رد نداء جديد إلى emitter طويل العمر. كل رد نداء يحتفظ بـ req. إذا لم يقم أي شيء بإزالة المستمع، فإن كائنات الطلب القديمة تظل قابلة للوصول ولا يمكن لجمع القمامة استعادتها.

الإصلاح عادةً هو الانضباط في دورة الحياة

يفضل أحد هذه الأنماط:

  • تسجيل مستمع واحد طويل العمر خارج مسار الطلب
  • استخدام .once(...) إذا كان يجب أن يتم إزالة المستمع تلقائيًا
  • إزالة المستمعين صراحة في مسارات التنظيف

على سبيل المثال:

function onQueryCompleted(event) {
  logger.info({ queryId: event.id }, "query completed");
}

db.on("queryCompleted", onQueryCompleted);

هذا الإصدار لم يعد يلتقط بيانات خاصة بالطلب عن طريق الخطأ.

ماذا تبحث عنه في اللقطة

أكثر العلامات فائدة هي:

  • مصفوفات أو خرائط كبيرة متعلقة بالكائنات العالمية
  • إغلاق يحتفظ بحالة محددة للطلب
  • ذاكرات بدون إخلاء
  • قوائم المستمعين التي تنمو مع الحركة

أنت لا تبحث فقط عن "الكبير". أنت تبحث عن "لماذا ما زال هذا حياً؟"

منع القادم

تساعد بعض العادات في اكتشاف العديد من تسريبات Node المبكرة:

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

يبدو أن تصحيح تسرب الذاكرة غامض حتى ترى أول واحدة بوضوح. بعد ذلك، يتعلق معظمها بدورة حياة الكائن.

القراءة الإضافية