클라우드 컴퓨팅 & NoSQL/Vert.x & Node.js

빠르게 훝어 보는 node.js - heapdump를 이용한 메모리 누수 추적

Terry Cho 2016. 3. 29. 11:47

 node.js에서 Heapdump를 이용한 메모리 누수 추적하기


조대협 (http://bcho.tistory.com)
 

대부분의 애플리케이션 서버들에서 고질적인 문제점중의 하나가 메모리 누수 현상이다. 비단 애플리케이션 서버에만 해당하는 문제는 아니지만 특히나 동시에 여러개의 요청을 반복적으로 받는 애플리케이션 서버의 경우에는 이 메모리 누수 현상이 훨씬 더 많이 발생한다.

 

node.js 의 경우, 엔진의 근간이 되는 구글 크롬 V8 자바스크립트 엔진 자체가 많은 메모리 누수 버그를 가지고 있지만, 애플리케이션에서 발생하는 메모리 누수는 발생하는 양도 크거니와 더 큰 문제를 야기할 수 있다. 이러한 애플리케이션에서의 메모리 누수를 추적하기 위한 방법을 알아보자

 

node.js의 확장 모듈인 heapdump는 기동중인 node.js 서버의 메모리 스냅샷을 추출할 수 있는 기능을 제공한다. 이 메모리 스냅샷을 힙덤프라고 하는데, 힙덤프에서 어떤 객체들이 메모리를 반복적으로 많이 점유하는 지를 찾아내면 메모리가 누수 되는 지점을 파악할 수 있다.

 

예제를 통해서 살펴보자

먼저 heapdump 모듈을 설치해야 한다. https://www.npmjs.com/package/heapdump

%npm install heapdump

명령어를 이용하여 heapdump 모듈을 설치한다.

 

Express로 간단한 웹 애플리케이션을 생성하자

package.jsonheapdump 모듈에 대한 의존성을 다음과 같이 추가한다.

 

{

  "name": "MemoryLeak",

  "version": "0.0.0",

  "private": true,

  "scripts": {

    "start": "node ./bin/www"

  },

  "dependencies": {

    "body-parser": "~1.13.2",

    "cookie-parser": "~1.3.5",

    "debug": "~2.2.0",

    "express": "~4.13.1",

    "jade": "~1.11.0",

    "morgan": "~1.6.1",

    "serve-favicon": "~2.3.0",

    "heapdump":"~0.3.7"

  }

}

 

Figure 1 package.jsonheapdump 모듈 의존성을 추가

 

다음 express 프로젝트의 app.js에 아래와 같은 코드를 추가한다.

app.use('/', routes); // 기존에 자동으로 생성된 코드

app.use('/users', users); // 기존에 자동으로 생성된 코드

 

var heapdump = require('heapdump');

var memoryLeak = [];

function LeakedObject(){ };

 

app.use('/leak',function(req,res,next){

     

      for(var i=0;i<1000;i++){

           memoryLeak.push(new LeakedObject());

      }

      res.send('making memory leak. Current memory usage :'

                 +(process.memoryUsage().rss / 1024 / 1024) + 'MB');

});

 

app.use('/heapdump',function(req,res,next){

      var filename = '/Users/terry/heapdump' + Date.now() + '.heapsnapshot';

      heapdump.writeSnapshot(filename);

      res.send('Heapdump has been generated in '+filename);

});

 

 

Figure 2 메모리 누수를 유발하는 코드 및 힙덤프를 추출하는 코드 추가

 

/leak URL을 처리하는 부분은 인위적으로 메모리 누수를 만들어낸다. for 루프를 이용하여 LeakedObject 1000개씩 배열에 추가하여 메모리 누수를 유발하고, 화면에 현재 메모리 사용량을 리턴하도록 하였다.

 

다음 /heapdump라는 URL에서는 실제로 heapdump를 생성한다. heapdump.writeSnapshot(파일명) 이라는 메서드를 사용하는데, 이 파일명 위치에 힙덤프를 저장한다.

 

또는 명령어를 사용하지 않더라도 프롬프트 상에서 힘덤프를 생성할 수 있다.

% kill –USR2 {node.js 프로세스 ID}

명령을 이용하면, node.js를 실행한 위치에 heapdumpxxx.heapsnapshot 이라는 이름으로 힙덤프를 생성해준다.

 

코드 작성이 끝났으면 애플리케이션을 실행해보자. http://localhost:3000/leak 로 들어가면 메모리 누수를 유발하고, 현재 사용중인 메모리 양을 볼 수 있다.

 



Figure 3 메모리 누수를 유발하는 URL에 접속

 

많은 메모리 누수를 유발해보기 위해서 ab (Apache benchmark : 아파치 웹서버를 설치하면 같이 설치되는 간단한 부하 테스트 툴이다. http://httpd.apache.org/) 를 이용해서 반복적으로 http://localhost:3000/leak 를 호출해보자

%ab -n 5000 http://localhost:3000/leak

명령어를 이용하면 http://localhost:3000/leak 5000번 호출한다.



Figure 4 아파치 ab툴을 이용하여 메모리 누수를 유도하기 위해서 5000번 부하를 줌

 

다시 http://localhost:3000/leak 를 접속해보면 사용중인 메모리 양이 늘어난것을 볼 수 있다.

 



Figure 5 node.js 의 메모리 사용량이 늘어난것을 확인

 

이제 힙덤프를 추출해보자. http://localhost:3000/heapdump 에 접속하면 자동으로 힙 덤프가 생성된다.

이 힙덤프는 현재 node.js가 사용중인 메모리 양이 클수록 추출하는 속도가 느려진다.

 



Figure 6 힙덤프 추출

 

힙덤프 파일이 추출되었으면, 이 힙덤프를 분석하기 위해서 구글의 크롬 브라우져에서 개발자 도구를 실행해보자

개발자 도구에서 “Profiles” 탭에서 Load 버튼을 눌러서 앞에서 추출한 힙덤프 파일을 로드한다.




Figure 7 크롬 브라우져 개발자 모드에서 힙덤프 파일을 로드

 

힙덤프를 보면, LeakedObject라는 객체가 전체 메모리의 66%, 120MB를 점유함을 확인할 수 있다.

이 객체를 열어보면, 같은 객체가 수도 없이 반복됨을 확인할 수 있다.

 



Figure 8 메모리 누수를 유발한 LeakedObject를 발견

 

이러한 방식으로 어떤 객체들이 메모리를 많이 점유해서 메모리 누수를 유발하는지 찾아낼 수 있다.

예제 소스 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/MemoryLeak 를 참고하기 바란다.