أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Memory Leak في Node.js: ازاي تكشفها بـ Heap Snapshot قبل ما السيرفر يقع

📅 ٢٧ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Memory Leak في Node.js: ازاي تكشفها بـ Heap Snapshot قبل ما السيرفر يقع

المستوى المطلوب: متوسط — يفترض إنك مرتاح مع Node.js و JavaScript، وعملت debugging بـ console.log أو DevTools قبل كده على الأقل.

Memory Leak في Node.js: ازاي تكشفها بـ Heap Snapshot قبل ما السيرفر يقع

لو RAM السيرفر عندك بيكبر من 240MB لـ 2.8GB في 4 أيام، الـ Heap Snapshot هيوريك بالظبط الـ object اللي بيتراكم في الذاكرة. المقال ده هياخدك من فهم الـ leak، لقراءة الـ snapshot، لحد إصلاحها بمثال من إنتاج فعلي.

شاشة طرفية تعرض مخرجات Node.js مع منحنى استخدام الذاكرة في الخلفية

المشكلة باختصار

Node.js شغّال على V8، والـ V8 فيه garbage collector بيحرر الذاكرة الخاصة بأي object مفيش حد بيشاور عليه. لو في object فضل متشاور عليه (حتى لو انت مش محتاجه فعلاً)، الـ GC مش هيلمسه. مع الوقت، عدد الـ objects المعلّقة دي بيكبر، استهلاك الذاكرة بيطلع، ولحد ما السيرفر يضرب OOM (Out Of Memory) ويقع.

المشكلة دي شائعة في الحالات دي:

  • Caches بدون حد أقصى ولا TTL.
  • Event listeners بتتسجّل في كل request وما بتتشالش.
  • Closures بتمسك references لـ data كبير بدون داعي.
  • Global Maps أو Arrays بتتراكم فيها entries على طول.

تخيّل الموضوع كده الأول

تخيّل إنك عندك دولاب في المطبخ، وكل ما تطبخ بتحط فيه طبق جديد بس عمرك ما بتغسل القديم. أول أسبوع الدولاب فيه مكان. تاني أسبوع بدأ يضيق. الشهر التالت، الباب مش بيقفل، وفي الآخر الدولاب نفسه بيقع. الـ memory leak بنفس الفكرة بالظبط: انت بتحفظ objects جديدة في الذاكرة، بس مفيش حد بيشيل القديمة، حتى لو ولا حد محتاجها.

التعريف العلمي: Memory leak هي حالة بتفضل فيها مساحة من الـ heap محجوزة لـ object مش هيُستخدم مرة تانية، لأن في reference نشط (من root) بيمنع الـ garbage collector من تحريرها. في V8، الـ roots بتشمل الـ global object، الـ stack، والـ closures المعلّقة.

السيناريو الفعلي

سيرفر Express بيخدم 50K request/يوم على instance بـ 2GB RAM. أول يوم: الذاكرة 240MB. اليوم الرابع: 2.8GB والسيرفر بيتعاد تشغيله أوتوماتيك كل ساعتين بسبب OOM. الـ logs نظيفة، الـ CPU 12% بس، والـ requests كلها 200 OK. المشكلة في الذاكرة لوحدها، ومش ظاهرة في أي metric غير الـ RSS.

الكود اللي بيسبّب التسريب

ده مثال مبسّط لكود حقيقي بيسرّب ذاكرة:

JavaScript

const cache = new Map();

app.get('/user/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  // المشكلة: الـ key بيضم timestamp الطلب، يعني عمره ما هيتعاد استخدامه
  cache.set(`${req.params.id}-${Date.now()}`, user);
  res.json(user);
});

كل request بيضيف entry جديد في الـ Map. مفيش cache.delete، ومفيش حد أقصى. بعد 50K request، فيه 50K user object معلّقين في الذاكرة بدون أي داعي. ده اللي بيحصل فعلاً في كثير من الـ codebases اللي شفتها.

خطوات أخذ Heap Snapshot

  1. شغّل تطبيقك مع flag الـ inspector: node --inspect=0.0.0.0:9229 server.js.
  2. افتح Chrome وادخل على chrome://inspect، واضغط "inspect" تحت الـ target بتاعك.
  3. روح Memory tab واضغط "Take heap snapshot". ده اللقطة الأولى (baseline).
  4. سيب التطبيق يستقبل 5K-10K request (production-like load). ممكن تستخدم autocannon: autocannon -c 50 -d 60 http://localhost:3000/user/1.
  5. خد لقطة تانية. هتلاقي الحجم زاد بشكل واضح.
  6. من الـ dropdown فوق، اختار "Comparison" وقارن snapshot 2 بـ snapshot 1. الـ #Delta هيوريك أنواع الـ objects اللي زادت بالظبط.
لوحة بيانات تعرض رسومًا بيانية لاستهلاك الذاكرة قبل وبعد إصلاح تسريب في Node.js

قراءة النتيجة

في المثال اللي فوق، (string) و Object هيظهروا في الأعلى مع +50,000 entry. اضغط على النوع، وفي الـ Retainers panel تحت هتلاقي السلسلة الكاملة: cache → Map → {user object}. كده انت عرفت الـ root اللي بيمسك الذاكرة بالظبط، ومش بتخمّن.

الإصلاح

الحل أبسط مما تتخيّل. استخدم lru-cache مع حد أقصى وTTL، أو على الأقل شيل الـ timestamp من الـ key:

JavaScript

const { LRUCache } = require('lru-cache');

const cache = new LRUCache({
  max: 5000,            // أقصى 5000 entry
  ttl: 1000 * 60 * 10   // 10 دقائق
});

app.get('/user/:id', async (req, res) => {
  const cached = cache.get(req.params.id);
  if (cached) return res.json(cached);

  const user = await db.users.findById(req.params.id);
  cache.set(req.params.id, user);
  res.json(user);
});

بعد الإصلاح، الذاكرة استقرت عند 280MB حتى مع 50K request/يوم. التحسّن: من نمو غير محدود لـ ceiling ثابت، ومن إعادة تشغيل كل ساعتين لـ uptime مفتوح.

الـ Trade-offs

الحل ده مش مجاني. بتكسب: استقرار في الذاكرة وعدم سقوط السيرفر، وuptime متواصل. بتخسر: cache hit rate أقل (لأن الـ entries القديمة بتتشال أوتوماتيك)، و overhead بسيط في الـ LRU bookkeeping (حوالي 5–8% فوق زمن cache.set العادي حسب benchmarks الـ lru-cache الرسمية).

الافتراض هنا إن عندك ≤ 100K user مختلف نشطين في النافذة الزمنية. لو عندك مليون user كلهم نشطين، الـ max: 5000 هيتطلب توسيع، أو نقل الـ cache بالكامل لـ Redis بدل ما يعيش جوا الـ process نفسه.

متى لا تستخدم Heap Snapshot

الأداة دي مش الحل لكل مشاكل الذاكرة:

  • لو الذاكرة بتطلع وبتنزل بشكل طبيعي: ده مش leak، ده الـ GC شغّال عادي. متشغّلش snapshot في الحالة دي، هتضيع وقتك.
  • على instance إنتاج بـ heap حجمه 8GB+: أخذ snapshot بيوقف الـ event loop لثوانٍ. استخدم --heapsnapshot-near-heap-limit لتسجيل تلقائي قبل OOM بدل الأخذ اليدوي على الإنتاج المباشر.
  • لو المشكلة في native memory (Buffer ضخم، sharp، native addons): heap snapshot مش هيوريها. راقب process.memoryUsage().external و arrayBuffers بدلها.

الخطوة التالية

لو عندك سيرفر Node.js شغّال دلوقتي، شغّل process.memoryUsage() كل 5 دقائق وسجّل الـ heapUsed في monitoring (Prometheus / Datadog / CloudWatch). لو شفت trend متصاعد على مدى ساعتين بدون نزول، خد heap snapshot وقارنه بـ baseline. لو الفرق ضخم في نوع object معين، عندك leak مؤكد. ابعتلي screenshot من الـ Comparison view وأنا هساعدك تقرأه.

مصادر

  • Node.js Diagnostics: Memory — التوثيق الرسمي عن أدوات تشخيص الذاكرة.
  • Chrome DevTools: Heap Snapshots — شرح الـ Retainers والـ Comparison view.
  • V8 Blog: Trash talk — Orinoco GC — إزاي V8 بيحدد الـ objects القابلة للتحرير.
  • lru-cache (npm/GitHub) — توثيق المكتبة المستخدمة في الإصلاح.
  • Node CLI: --heapsnapshot-near-heap-limit — flag للتسجيل التلقائي قبل OOM.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة