ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Goodbye Recoil
    Frameworks, Platforms and Libraries/React 2024. 10. 31. 14:00

    Recoil 관련 포스팅남긴 이후 다른 업무로 인해 사이드로 진행하던 레거시 프로젝트의 Recoil 포팅 작업이 멈춘 상태였습니다. 다시 작업을 시작하려는데 더 이상 Recoil을 사용하지 않는다는 관련 레퍼런스들을 읽고 해당 내용을 정리하게 되었습니다.

     

    Recoil을 선택했던 이유

    기존 React를 사용한 프로젝트에서 상태 관리를 위해 Redux를 도입하여 사용 중이었습니다. 하지만 Redux를 사용하다 보니 계속 생산되는 보일러 플레이트 코드, 신규 입사자들의 러닝 커브 등의 문제가 있었습니다. 사실 이러한 이유보단 규모가 작은 project에서 Redux 사용을 위해 설정하는 상황에서 배보다 배꼽이 큰 느낌을 항상 받았었습니다. 그런 상황에서 Recoil은 좋은 선택지가 되었습니다.

     

    Recoil을 사용하지 않는 이유

    Recoil을 더이상 사용하지 않는 이유를 하나씩 보겠습니다.

    1. 업데이트 내역

    Recoil 저장소의 릴리스 내역을 보면 2023년 4월 이후 릴리스 내역이 없으며 아직 메이저 버전으로 업데이트되지 않았습니다.

    https://github.com/facebookexperimental/Recoil/releases

     

    처음 도입 당시 아직 메이저 버전이 아니라 우려스러웠지만 React를 만든 Facebook에서 개발했기 때문에 관리가 잘 될 것이라 생각했습니다. 하지만 1년 넘게 릴리스가 되고 있지 않고 PR의 내역도 살펴보면 7월 이후 올라온 내역이 없으며 PR들도 제대로 코멘트가 달리고 있지 않습니다.

     

    2. UNSTABLE 요소들

    Recoil로 포팅 할 당시 사용했던 useRecoilTransaction_UNSTABLE와 더불어 useRecoilTransactionObserver_UNSTABLE, useRecoilRefresher_UNSTABLE 등 여러 hook에서 불안정한(UNSTABLE) 상태의 hook을 발견할 수 있습니다. 실제 상용 중인 서비스에서 불안정한 요소들을 사용하는 건 꺼려질 수밖에 없습니다.

     

    3. 메모리 누수

    Recoil에 대한 레퍼런스를 찾다보면 메모리 누수라는 키워드도 한번씩 보게됩니다. 이전 Recoil 포스팅의 예제로 해당 내용 살펴보겠습니다.

    import { atomFamily, selectorFamily } from 'recoil';
    
    interface TodoItem {
      id: string;
      title: string;
      desc: string;
    }
    
    export const todoItemFamily = atomFamily<TodoItem, string>({
      key: 'todoItemFamily',
      default: (id) => ({
        id,
        title: 'title',
        desc: 'desc',
      })
    });
    
    export const projectStarSelector = selectorFamily({
      key: 'project/star',
      get: (path: string) => async () => {
        if (!path) return '...';
        const response = await fetch(
          `https://api.github.com/repos/${path}`
        );
        const projectInfo = await response.json();
        return projectInfo.stargazers_count;
      }
    });

     

    파라미터를 전달받는 atomFamily, selectorFamily 케이스에서 같은 파라미터가 올 경우 캐싱 되어 있는 동일한 요소의 값이 변경되지만 새로운 파라미터가 올 경우 새로운 요소로 값이 쓰이게 됩니다. 해당 요소들은 캐싱 되어 사용되지 않더라도 메모리에 남아 누수를 발생시키게 됩니다. 이는 atom, selector에서도 동일하게 발생합니다.

     

    이러한 문제점을 파악하기 위해 Recoil의 구조를 통해 살펴보겠습니다. Recoil에선 atom, selector 그리고 파라미터를 받아 atom, selector를 만들어내는 atomFamily, selectorFamily은 node로 저장되며 node 간의 관계를 graph로 저장합니다. 이러한 node와 graph는 최상위 store에 저장이 되며 변경이 발생할 경우 RecoilRoot에서 매번 store을 찍어냅니다. Recoil을 사용하며 사용하던 snapshot은 변경이 발생되어 찍어내는 store에 접근하는 값입니다.

     

    결국 Recoil에선 RecoilRoot에 최신화된 node와 graph를 가진 store를 가지고 있게 되며 아래의 Recoil 자원 해제하는 로직에서 특정 단계를 벗어나지 못해 메모리에 자원을 해제를 못함으로써 메모리 누수가 발생하게 됩니다.

    function findReleasableNodes(
      store: Store,
      searchFromNodes: Set<NodeKey>,
    ): Set<NodeKey> {
      ...
      function findReleasableNodesInner(searchFromNodes: Set<NodeKey>): void {
        ...
        // Find which of the downstream nodes are releasable and which are not:
        for (const node of downstreams) {
          // Not releasable if configured to be retained forever:
          // 해당 부분에 모든 케이스가 걸리게 됩니다.
          if (getNode(node).retainedBy === 'recoilRoot') {
            nonReleasableNodes.add(node);
            continue;
          }
          // Not releasable if retained directly by a component:
          if ((storeState.retention.referenceCounts.get(node) ?? 0) > 0) {
            nonReleasableNodes.add(node);
            continue;
          }
          ...
        }
        ...
      }
    }

     

    그래서 자원 해제를 위해 RecoilRoot를 다시 그리거나 Recoil의 다른 옵션을 사용할 수도 있지만 RecoilRoot를 다시 그릴 경우 페이지 전체가 다시 그려지게 되고 자원 해제를 위한 Recoil의 옵션들은 UNSTABLE 상태라 사용하기가 꺼려지는 상황입니다.

     

    4. 떨어지는 점유율

    최근 상태 관리 라이브러리로 많이 언급되는 Zustand와 Jotai를 recoil과 함께 npmtrends에서 다운로드 수를 비교한 그래프입니다.

    https://npmtrends.com/jotai-vs-recoil-vs-zustand

    비슷한 콘셉트의 Jotai와 점점 차이가 나기 시작했으며 Zustand와는 더 큰 격차가 벌어져 있습니다.

     

    마무리

    Recoil 도입을 결정하던 시기에 Facebook의 커뮤니티를 믿고 우려스러웠던 부분들을 간과했습니다. 한동안 업무가 바빠 Recoil 포팅 작업을 멈춘 게 다행이라 할 수 있지만 앞으로 라이브러리 선택 시 많은 부분을 고려하여 선택해야겠습니다. 또한 사용 중인 라이브러리들도 한번 체크하여 앞으로 운영 시 문제가 없도록 해야겠습니다.

     


    참고 자료

    https://medium.com/@clockclcok/recoil-%EC%9D%B4%EC%A0%9C%EB%8A%94-%EB%96%A0%EB%82%98-%EB%B3%B4%EB%82%BC-%EC%8B%9C%EA%B0%84%EC%9D%B4%EB%8B%A4-ff2c8674cdd5

    https://medium.com/@altoo/recoil%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%AC%B8%EC%A0%9C-fb709973acf2

     

Designed by Tistory.