Web/Recoil

Recoil의 메모리 누수부터 Recoil의 미래까지

여러분의 메모리가 그냥 터지고 있습니다

최근 사내에서 서버 모니터링을 진행하는 도중에 Node App Memory 사용량이 지속적으로 증가하는 현상이 관찰되었다. 즉, 메모리 누수 현상이 발생한 것이다. 메모리 누수라는 것은 불필요한 메모리를 특정 프로그램이 지속적으로 점유하고 반환하지 않는 현상을 일컫는다. 메모리 누수가 누적되면 메모리가 낭비되고, 심해질 경우 누수되는 메모리가 메모리의 대부분을 차지해서 애플리케이션이 정상적인 동작을 할 수 없게 된다.

 

이러한 메모리 누수에 대해서 원인을 추적하고 최종적으로 도달한 결론에 대해 복기하고 공유하기 위해 오늘의 포스트를 작성하게 되었다.  살짝 스포일러를 하자면, 원인은 Recoil이었다. Recoil의 어느 부분이 문제가 되었고, 이 문제를 해결하기 위한 방법과 현재 Recoil이 당면한 추가적인 문제에 대해서 함께 알아보도록 하자.

 

 

이번 메모리 누수의 직접적인 원인은 Recoil이었다.

보통 여러 요청을 받는 서버의 경우 어느 정도의 메모리 누수 현상이 쉽게 발생할 수 있다. 그러나, 쉽게 발생한다는 것은 둘째치고 수용할 수 있는 범위를 벗어나서 너무나 빠른 속도로 메모리 사용량이 증가하고 있었다.

 

 

서버가 절대로 끊기지 않는 메모리 사용량을 계속해서 이어가고 있다.

위의 메모리 사용량 그래프를 나타내고 있던 서버는 Node.js 서버였다. 보통 Node.js 서버의 경우 heapdump를 통해서 메모리 누수를 추적할 수 있다. 보편적인 경우의 추적에 대해서 간단하게 설명한 글을 발견해서 첨부드리도록 하겠다. node.js에서 Heapdump를 이용한 메모리 누수 추적하기

 

 

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

node.js에서 Heapdump를 이용한 메모리 누수 추적하기 조대협 (http://bcho.tistory.com) 대부분의 애플리케이션 서버들에서 고질적인 문제점중의 하나가 메모리 누수 현상이다. 비단 애플리케이션 서버에

bcho.tistory.com

 

위 글에서는 /leak 경로로 접근하는 경우 메모리 누수를 유발하고, /heapdump 경로로 접근하는 경우 heapdump를 생성하는 방식으로 메모리 누수를 추적하고 있다. 우리의 서버는 이미 메모리 누수가 발생하고 있는 서버이기 때문에 heapdump를 생성하는 경로만 추가하면 된다. 해당 경로로 요청이 들어오는 경우, heapdump.writeSnapshot() 메소드가 실행되도록 코드를 작성해서 특정 파일 경로에 현재 서버 상태에 대한 스냅샷을 떠서 덤프를 생성할 수 있다.

 

 

heapdump 파일 분석 결과

덤프 파일 분석 결과, 문제되는 부분은 clearSelectorCache context 내 다량의 배열이 존재하는 것이었다. 그리고 그 clearSelectorCache는 Recoil의 Selector에서 사용 중인, 캐시를 삭제하는 함수였다. 아래 문제가 되는 Recoil의 코드를 첨부한다.

 

function clearSelectorCache(store: Store, treeState: TreeState) {
  invariant(recoilValue != null, 'Recoil Value can never be null');
  for (const nodeKey of discoveredDependencyNodeKeys) {
    const node = getNode(nodeKey);
    node.clearCache?.(store, treeState);
  }
  discoveredDependencyNodeKeys.clear();
  invalidateSelector(treeState);
  cache.clear();
  markRecoilValueModified(store, recoilValue);
}

또한 Recoil에서 공식적으로 제공하고 있는 문서에도 캐시 정책 수립 시 문제가 될 수 있는 부분에 대해서 경고하고 있다.

https://recoiljs.org/docs/api-reference/core/selector/#cache-policy-configuration

 

selector(options) | Recoil

Selectors represent a function, or derived state in Recoil. You can think of them as similar to an "idempotent" or "pure function" without side-effects that always returns the same value for a given set of dependency values. If only a get function is provi

recoiljs.org

Recoil 공식 문서 > Selector > Cache Policy Configuration

더보기

... which may lead to a memory issue over time as the internal cache grows indefinitely. Using the most-recent eviction policy, the internal selector cache will only retain the most recent set of dependencies and their values, along with the actual selector value based on those dependencies, thus solving the memory issue.


(기본적인 캐시 제거 정책을 사용하는 경우) 내부 캐시가 무한히 증가하며 시간이 지남에 따라 메모리 문제로 이어질 수 있습니다. most-recent 캐시 제거 정책을 사용해서, 내부 selector 캐시로 하여금 가장 최근의 종속성과 값을 해당 종속성에 기반한 실제 selector 값과 함께 유지하여 메모리 문제를 해결할 수 있습니다.

 

즉, 캐시 제거 정책(cache evinction policy)를 most-recent로 사용하면 메모리 누수를 어느 정도 해결할 수 있는 뜻이다.

 

실제로 캐시 제거 정책을 수정한 뒤, 메모리 누수가 어느정도 해결되었다. 그러나 완전히 해결되지 않고 일부 메모리는 반환되지 않는 문제가 계속되고 있었다. 팀원 분께서 추가적으로 조사해본 결과 SSR을 사용할 때, atomFamily와 selectorFamily를 사용할 때 메모리 누수가 발생하는 문제가 각각 2022년, 2020년 확인된 이후로 현재까지 수정되지 않고 있었다. 위 서버가 전부 해당되는 케이스인데, 아쉽게도 고질적인 Recoil 내부 문제이기 때문에 Recoil을 제거하지 않는 이상 해결할 수 없었다.

 

 

심지어 Recoil의 미래에 대해 꽤 비관적인 글들도 발견할 수 있었다.. 😂

 

 

npm trends: recoil vs jotai

npm trends를 통해 확인한 결과 이미 recoil은 jotai에게 밀리고 있는 추세다. 2020년 recoil이 facebook을 통해 처음 등장했을 때 받은 관심도에 비하면 꽤나 슬픈 결말이다. jotai도 recoil과 같이 atom 기반 상태(atomic state) 관리 방법을 사용하고 있어 recoil에서 jotai로 이관하는 데 큰 어려움이 없다는 점도 크게 작용하는 것 같다.

 

이번에 알아본 메모리 누수 건을 차치하더라도 이미 Recoil에 해결되지 못하고 쌓여있는 이슈는 250개가 넘는다. (2024년 3월 3일 기준) 아쉽게도 recoil은 이러한 이슈에 코멘트 하나도 달지 않고 있고, 이슈를 해결하는 패치를 지난 연도에 두 번 밖에 진행하지 않았다. 핵심 메인테이너 Douglas Armstrong은 이미 정리 해고로 인해 퇴사했다는 이야기까지 들려오고 있다. 사실 제일 놀라운 점은, recoil은 최초로 공개된 지 3년 반이 지났는데도 아직 experimental 단계라는 것이다.

 

그동안 수많은 React의 상태 관리 라이브러리들이 살아남기 위해 노력했지만 새롭고 간단하고 편리한 개념을 제시하는 신규 라이브러리들에 의해 죽음을 맞이했다. Recoil도 최초로 등장했을 때는 혁신적인 개념으로 주목 받았지만 결국 유지보수되지 않는 라이브러리는 서서히 죽음을 맞이하게 되는 것 같다.

 

 

 

그래서 SSR 메모리 누수는 어떻게 해결할 거냐고..?

 

Jotai로 마이그레이션해서 해결할 예정이다!

 

 

References