حامت قلیزاده
حامت قلیزاده
خواندن ۷ دقیقه·۶ سال پیش

مموری لیک، کابوس هر برنامه نویس! (قسمت دوم)

در قسمت قبلی یاد گرفتیم چطور میتوان فهمید که پروسس اپ‌مان مموری لیک دارد یا نه؟ خب حالا که مثل لوله کشان کاور پست‌مان فهمیدیم که پروسس‍مان مموری لیک دارد، چکار باید کنیم؟


نشتی از کجاست؟

خب، قبل از اینکه خودمان را درگیر دیباگر نود جی اس کنیم، ابتدا مواردی که ممکن است باعث مموری لیک اپلیکیشن‌مان شود را حدس بزنیم:

  • استفاده از ورژن‌های قدیمی نود‌جی‌اس (دریافت آخرین ورژن)
  • استفاده از ماژول‎های قدیمی یا تست نشده!
  • بکارگیری توابع ناخالص (بیشتر در ادامه توضیح دادم)
  • اجرا کردن اپ با پروسس منیجر‎های غیراستاندارد یا نسخه قدیمی آن‎ها ( پیشنهاد من آخرین ورژن pm2)
  • پروسس‎های بلاک شده!
  • و هزاران دلیل دیگر

بکارگیری توابع ناخالص (impure)

این توابع همان تابع‌های ساده‌ی خودمون هستند ولی با فرق اینکه یک سری نکات رو رعایت کنیم بهش میگن pure و در غیر اینصورت تابع impure نامیده میشود.

تابع خالص چیست:

  1. تابعی که با توجه به ورودی‏‌های داده شده همیشه همان خروجی را برمی‌گرداند. این توابع به هیچ متغیر یا حالت خارج از تابع بستگی ندارد و فقط تغییر در ورودی باعث تغییر در خروجی میشود.
  2. تابعی که هیچ عوارض جانبی قابل مشاهده مثل تغییر داده (متغیر)، ارسال درخواست شبکه و ارسال درخواست به دستگاه‎‎های ورودی و خروجی ندارد.

تابع نا‌خالص چیست: توابعی که شروط بالا رو رعایت نکنند، ناخالص‌ند :)

مثال تابع خالص:

function priceAfterTax(productPrice) { return (productPrice * 0.20) + productPrice; }


مثال تابع ناخالص:

var tax = 20; function calculateTax(productPrice) { return (productPrice * (tax/100)) + productPrice; }

برای اطلاعات بیشتر در مورد این نوع توابع این مقاله رو مطالعه کنید.


پروسس‎های بلاک شده!

شاید دلیل اصلی بسیاری از توسعه‌دهندگانی که از نود استفاده می‏‎کنند همین تکنولوژی non-blocking باشه، که میتوان گفت یکی از شاهکار‏های نود میباشد. اما خب برای توسعه دهندگانی که به‌تازگی با نود آشنا شدند درک مفهوم آن ممکن است سخت باشد. از آنجا که نمیخوام این بحث را سخت‎ترش کنم با یک مثال ساده شرح میدم:

Blocking I/O

let data = db.users.find(very slow query) // stopped here by await OR yield console.log(data) console.log('im waiting for database and it is not good')

در این روش متد find تا زمانی که کوئری به صورت کامل اجرا نشده و خروجی را به متغیر data انتقال نداده بقیه کد‎ها با اینکه به خروجی این کوئری احتیاجی ندارند بلاک شده و اجرا نخواهند شد

Non-Blocking I/O

db.users.find(very slow query, callback(data) { console.log(data) }) console.log(' i not waiting for database and it is awesome')

یکی از بهترین روش‏‎ها برای بهره مندی از امکان non-blocking نود‌جی‌اس همین استفاده از توابع برگشتی و Promise ها است، که در این تکه کد به آن اشاره شد.

سعی کنید تا میتوانید از non-blocking ها استفاده کنید و با استفاده از توابع برگشتی و یا Promise ها کارایی اپ نود‌جی‌استان رو بالا ببرید


کار با دیباگر!

اگر روش‌های مختلفی را امتحان کردید اما مموری لیک پروسس شما از بین نرفت. پس تنها یک راه نهایی مانده تا بفهمید مشکل از کجاست. قبل از اینکه کارو شروع کنید ابتدا با چند اصطلاح آشنا بشیم.

هیپ‎دامپ (Heapdump)

یک ابزار عالی برای گرفتن اسنپ شات از پشته (heap) است، که بعدا می‌توانید توسط اینسپکتور (inspector) گوگل کروم آن را آنالیز کنید.

اینسپکتور نود (Node-inspector)

این ابزار به شما اجازه میده که به پروسس نود‌جی‌اس در حال اجرا وصل شده و اسنپ شات هیپ گرفته و یا کدهای پروسستان را ویرایش کرده و دیباگ کنید ( بدون نیاز به ریستارت پروسس)


شروع کار با نود اینسپکتور

برای اینکه بتوانیم مموری لیک را لمس کنیم و نود اینسپکتور را در عمل مشاهده کنیم یک اپ کوچکی با فریم ورک اکسپرس نوشتم ولی با این تفاوت که این اپ کوچک دارای مموری لیک است!

memoryLeak.js:

const express = require('express') const app = express() const port = 6363 let globalArray = [];
app.get('/', (req, res) => {
globalArray.push(function thisIsTestFunc() { return req.headers; }) return res.send('hi, this is memory leak!') }) app.listen(port) console.log('process is running on port: ', port)

این تکه کد یک اپلیکیشن نود‌جی‌اس ساده هست که به ازای هر درخواست به آدرس '/' یک شی (Object) به آرایه گلوبال اضافه کرده و یک پاسخ به کاربر میدهد. مشکلی که وجود دارد این است که این آرایه گلوبال در مدت زمان کم (نسبت به تعداد درخواست) شروع به افزایش حجم مموری نموده و هیچ گاه خالی نمیشود، تا اینکه پروسس نودمان کرش کند!

برای اجرای پروسسمان در حالت دیباگر به این صورت اقدام می‌کنیم:

node --inspect memoryleak.js

بعد از اجرای پروسسمان، آدرس زیر را در مرورگر باز میکنیم:

http://localhost:9229/json/list

که در این صفحه یک خروجی JSON چاپ شده که شامل اطلاعات زیر است:

مقدار مشخصه devtoolsFrontendUrl را در مرورگر گوگل کروم باز میکنیم و وارد دیباگر نود اینسپکتور میشویم!

حتما از مرورگر گوگل کروم استفاده کنید زیرا این آدرس در مرورگر‌‌های دیگر کار نخواهد کرد. برای امنیت بیشتر بعد از اینکه وارد دیباگر شدید، دیگر آدرس http://localhost:9229/json/list به شما خروجی جدید نخواهد داد!

به تب profile (در ورژن های جدید به نام memory تغییر یافته) دیباگر رفته و گزینه "allocation insurance on timeline" انتخاب کرده و سپس start را میزنیم

با استفاده از شبیه سازهای آماده تعدادی درخواست HTTP به سمت پروسس نودمان هدایت میکنیم، به محض اینکه درخواست‌ ها به پروسس نود‌مان رسید می‍توانیم تغییرات آبی رنگی را در نمودار افقی مشاهده کنیم بعد از گذشت 3 دقیقه تایم لاین را متوقف میکنیم و بازه های کوچکی را انتخاب کرده و مورد آنالیز قرار میدهیم. از آنجایی که این مموری لیک کاملا توسط خودمان ایجاد شده بود پس پیدا کردن آن بین این همه شی، آرایه و رشته کار چندان سختی نیست :) مستقیم به قسمت closure رفته و فاجعه را مشاهده میکنیم!!!

تعداد بسیار زیادی از تابع thisIsTestFunc تکرار شده است، اگر یادتان نباشد این تابع کارش این بود که شی req.headers رو به داخل آرایه گلوبال اضافه میکرد; اگر این اضافه کردن‌ها به مدت بیشتری ادامه پیدا کند ممکن است رم بیشتری را اشغال کرده و در نهایت منجر به کرش اپ نود‍مان شود پس برای جلوگیری از این کار بعد از اینکه کارمان با آرایه گلوبال تمام شد مقدار آن را خالی میکنیم.

... app.get('/', (req, res) => { globalArray.push(function thisIsTestFunc() { return req.headers; }) // do some stuff globalArray = [] return res.send('hi, this is memory leak!') }) ...

این یک مثال بسیار ساده از مموری لیک بود، لزوما مموری لیک ها به این سادگی از بین نمیروند، نیاز به تحلیل و آنالیز بیشتر heapdump ها دارند، برای اینکه مموری لیک را بیابید سعی کنید در بازه های زمانی مختلف heapdump گرفته و آنها را باهم مقایسه کنید و یا در یک بازه چندین heapdump گرفته و آنها را مورد بررسی قرار دهید.

یادگیری بیشتر

برای بیشتر یاد گرفتن، مقاله های زیر را پیشنهاد میکنم:

مفهوم GC ها در نود‌جی‌اس
برنامه نویسی Async در نود‌جی‌اس
جزئیات بیشتر کار با دیباگر و هیپ دامپ

نتیجه گیری

از اینکه اپ نود‌جی‌استان مموری لیک دارد خجالت نکشید; جستجو کنید، پشتکار داشته باشید و به تحلیل و بررسی ادامه دهید که تنها راه نابودی مموری لیک، وقت گرانبهای شماست!

اگر پیشنهادی، انتقادی و یا خطایی در صحبت هام بود ممنون میشم از قسمت کامنت‎ها یا توییتر من رو در جریان بگذارید ♥

memory leakmemorynodejsprogrammingمموری‌لیک
گاه نوشته های حامت ق - https://imber.live
شاید از این پست‌ها خوشتان بیاید