대시보드 화면의 지속적인 메모리 누수로 일정 시간이 지나면 Out of Memory로 화면이 다운되는 현상이 발생하여 해결하는 과정
Memory Leak이란?
메모리 누수(memory leak)는 프로그램이 더 이상 필요하지 않은 메모리를 해제하지 않고 계속 점유하는 현상으로 시간이 지남에 따라 이런 메모리가 계속 축적되어 시스템의 전체적인 메모리를 소모하게 된다.
Out of Memory(OOM)란?
시스템이나 애플리케이션이 사용할 수 있는 메모리가 부족할 때 발생하는 현상이다. 프로그램이 할당 가능한 메모리보다 더 많은 메모리를 요청할 때 발생하며, 이로 인해 프로그램이 죽거나 시스템의 성능이 저하될 수 있다.
이렇게 강제로 Memory Leak을 발생시키면 OOM으로 웹사이트가 죽게된다.
OOM이 발생하는 것을 확인했으면 개발자도구에서 지속적으로 Memory를 모니터링 하며 Memory Leak이 발생하는 구간이나 객체 등을 파악해야 한다.
메모리 누수 발생 원인 확인하기
1. OOM이 발생되는 페이지에서 개발자도구를 띄운다. (Windows: F12, Mac: cmd+opt+i)
2. Performance 탭에서 동그란 recording 버튼을 눌러서 원하는 만큼 페이지의 성능(performance)를 녹화한다.
- 개발 환경이나 웹페이지에 따라 다르겠지만 10~20분 정도는 녹화하는게 한눈에 보기 좋은 것 같다.
3. 녹화된 화면에서 메모리의 사용량 그래프를 보며 어떤 시점에 메모리 사용량이 급격히 증가하는 시점에 어떤 함수나 이벤트가 호출되었는지 등을 확인하여 문제가 될 만한 부분들을 확인한다.
4. Memory 탭에서 두번째 Allocation instrumentation on timeline을 선택하고 recording 버튼을 눌러 시간대 별 memory 사용 현황을 녹화한다.
- 시간대 별 Snapshot을 찍어 시간에 따른 자바스크립트의 메모리 할당을 볼 수 있다.
5. Performance와 같이 일정 시간 녹화를 하면 막대차트와 아래의 생성자 목록을 확인할 수 있다.
- 위의 막대 차트는 시간대 별로 사용한 메모리 수치로 막대차트의 높이는 해당 시간대에 사용한 총 메모리 사용량이고 그 중 파란색 영역은 메모리를 사용하였지만 메모리가 정상적으로 해제되지 않은 수치이다.
- 즉, 막대차트의 파란색 수치가 Memory Leak이고 이 수치들이 오랜시간 쌓여서 메모리 누수의 양이 커지면 Out of Memory가 발생하는 것이다.
- 막대차트에서 메모리 누수가 발생하는 시점을 필터링(차트 영역 클릭)하면 해당 시점의 Constructor 목록을 확인할 수 있다. 이와 같이 메모리 누수(파란색 차트)가 크게 발생하는 시점을 필터링하고 Constructor 목록의 상세 목록들을 타고 들어가면 어떤 부분에서 메모리 누수가 발생하는지 알 수 있다.
메모리 누수 해결하기
위에서 메모리 누수(Memory Leak)이 발생하는 원인을 확인했으면 메모리 누수가 발생하지 않도록 코드를 수정해야 한다.
원인
메모리 누수가 발생하는데 다양한 원인이 있겠지만 이번에는 대시보드에서 주기적으로 차트를 렌더링하여 변경된 데이터를 반영하는 로직에서 eChart, ApexChart를 사용하는데 특정 시간마다 차트를 새로 렌더링하기 때문에 기존에 사용하던 차트 객체가 그대로 메모리를 사용하고 있었다.
수정 방안
차트의 데이터가 변경될 때 기존의 차트 객체가 남아있는 것이 문제기 때문에 차트를 업데이트 할 때 기존 객체를 메모리에서 해제하거나 새로 렌더링을 하지 않고 기존 객체에 데이터만 갱신해주면 해결된다.
EChart의 경우 setOption 메서드를 사용하여 데이터를 변경할 수 있는데 기본적으로 setOption 메서드는 차트를 업데이트할 때 렌더링을 수행하지만, 렌더링 없이 기존 차트에서 데이터만 갱신하려면 notMerge 옵션을 사용하면 된다.
chart.setOption({
series: [{
data: newData
}]
}, false); // `false`는 `notMerge`를 의미하며, 데이터만 업데이트 한다.
+ 추가적으로 차트 이외에 많은 경우 Object, Array, SVG 등 더 이상 사용하지 않는 객체를 참조하는 곳이 남아있어 가비지 컬렉션(GC)의 대상으로 지정되지 않아 메모리 누수가 발생한다.
이런 경우 두 가지 방법으로 어느정도 개선할 수 있다.
1. var로 선언된 변수 let으로 변경 검토
javascript에서 var는 함수 범위를 갖지만 let은 블록 범위를 갖는다.
이러한 특성 때문에 let을 사용하면 변수가 의도한 범위 내에서만 유효하므로, 불필요한 변수 참조를 방지할 수 있으며, 해당 블록이 끝나면 변수에 대한 참조가 사라지기 때문에 let을 사용해도 되는 변수라면 let을 사용하는 것이 메모리 관리 측면에서 유리할 수 있다.
2. 불필요한 참조 제거
객체나 배열을 더 이상 사용하지 않는 경우 참조를 null 또는 undefined로 설정하여 해당 데이터가 필요 없다는 것을 명확히 하여 가비지 컬렉터가 이를 처리할 수 있도록 한다.
마지막으로 리서치 중 찾은건데 Meta에서 개발한 Javascript에서 메모리 누수를 찾기 위한 오픈 소스 프레임워크도 있다. 시간이 된다면 프레임워크를 사용해 보는 것도 재미있을 것 같다.
https://engineering.fb.com/2022/09/12/open-source/memlab/
*본문은 Chrome을 기준으로 작성하였습니다.
- 끝 -