Backend/TypeScript

JavaScript heap out of memory 에러 해결하기

누구세연 2024. 11. 12. 22:32

JavaScript 애플리케이션의 개발 및 배포 후 Out of Memory (OOM) 오류가 발생했다...😥

이 글에서는 OOM 오류의 원인과 이를 해결하기 위한 접근 방법을 중심으로 정리해 보겠습니다.

에러 로그

 

JavaScript Heap Out of Memory 오류란?

JavaScript Heap은 애플리케이션이 동적으로 생성하는 객체들이 저장되는 메모리 공간을 의미합니다.

JavaScript 애플리케이션의 힙 메모리는 고정된 크기를 가지고 있으며, 이 크기를 초과하면 Heap Out of Memory 오류가 발생하게 됩니다.

이 오류는 일반적으로 다음과 같은 상황에서 발생합니다.

  • 데이터가 지나치게 많이 로드될 때
  • 반복문 안에서 대규모 객체를 생성하고 해제하지 않을 때
  • 메모리 누수가 발생할 때

 

오류 발생 및 문제 파악

초기 상황

배포 후 서버의 메모리 사용량이 급격히 증가하여 OOM 오류가 발생했으며, 이를 해결하기 위해 다음 단계를 수행했습니다.

  1. 개발 환경에서 실제 환경과 유사하게 데이터를 설정하고 동일 작업을 반복해 OOM 오류를 재현했습니다.
  2. Heapdump 파일을 생성하여 문제 발생 시 메모리 상태를 분석했습니다.

Heapdump란?

Heapdump는 애플리케이션의 힙 메모리 스냅샷을 의미합니다. 이를 통해 실행 중인 프로그램에서 메모리를 차지하고 있는 객체들을 분석하고, 누수 여부나 과도한 메모리 사용 객체를 추적할 수 있습니다.

 

수동 스냅샷 생성 방법

메모리 상태를 추적하기 위해 heapdump 모듈을 설치하고, 특정 경로로 HTTP 요청을 보낼 때 힙 스냅샷을 생성하도록 설정했습니다.

npm install heapdump
import * as heapdump from "heapdump";

app.use("/heapdump", (req, res) => {
    const filename = `./heap_${Date.now()}.heapsnapshot`;
    heapdump.writeSnapshot(filename, (err) => {
        if (err) {
            return res.status(500).send("Error generating heapdump: " + err);
        }
        return res.send("Heapdump has been generated in " + filename);
    });
});

위 API를 호출하면 지정된 경로에 힙 스냅샷 파일이 생성되며, 이 파일을 통해 메모리 사용 상태를 확인할 수 있습니다.

 

 

 

메모리 스냅샷 분석 및 문제 해결 

메모리 스냅샷 분석 

스냅샷을 크롬 디버거에서 분석하여 메모리가 급격히 증가한 시점과 OOM 발생 후 안정화된 상태를 비교했습니다. 주요 분석 포인트는 다음과 같습니다.

 

  • 일시적 부하인지 메모리 누수인지 구별: 누수는 계속 증가하지만 일시적 부하는 특정 시점에서 메모리가 안정됩니다.
  • 과도한 메모리 사용 객체: 메모리를 많이 사용하는 객체를 식별하고, 이러한 객체가 반복적으로 생성되는지 확인했습니다.

결론적으로, 메모리 누수가 아닌 일시적인 부하로 인해 발생한 오류로 파악되었습니다.

 

 

해결 방안

문제의 원인을 바탕으로, 다음과 같은 최적화를 적용했습니다.

  • 메모리 부하 분석을 위한 Heapdump 모듈 추가 및 로직 구현
    Heapdump 모듈을 추가하여 힙 메모리 사용을 정기적으로 추적하고, 필요시 메모리 스냅샷을 생성하여 문제 발생 시 상태를 저장하고 분석할 수 있는 기반을 마련했습니다.
  • NxN 탐색 로직 최적화
    기존에는 List를 이용해 반복적으로 find 작업을 수행했지만, Map을 사용하여 중첩 반복문을 제거했습니다. 이를 통해 시간 복잡도를 줄이고 성능을 향상시켰습니다.
  • 쿼리 성능 최적화
    JOIN을 사용한 복잡한 조회 로직을 검토하고 최적화했습니다.
    • LEFT OUTER JOIN과 같은 복잡한 조인은 메모리와 성능에 부담을 줄 수 있어, 필요한 데이터만 별도로 조회하도록 개별 쿼리로 분리했습니다.
    • 조회된 데이터를 애플리케이션 코드에서 조합하여 반환하는 방식으로 변경하여, 쿼리 복잡도를 낮추고 메모리 사용량을 줄였습니다.

 

적용 후 결과 및 정리

위의 개선 사항을 적용한 후 재테스트 결과, 메모리 사용량이 안정화되었고 OOM 오류가 발생하지 않는 것을 확인했습니다.

 

성능 최적화는 메모리 문제를 예방하는 데 매우 중요한 요소라는 점을 다시금 깨닫게 되었습니다.

이번 경험을 통해, 향후 개발 시에는 정기적인 메모리 모니터링과 부하테스트를 통해 문제가 발생하기 전에 감지할 수 있는 체계를 마련할 계획입니다.