리액트 앱에 대한 프로파일링

4분

11/27/2022

Thumbnail

최근 웹 애플리케이션은 리액트로 만들지 않은 앱이 없을 정도로 리액트가 많이 사용됩니다.

리액트는 빠르고, 유연하고, 사용하기 쉽습니다. 그래서 더 크고 방대한 웹 애플리케이션을 만들기 편리해졌습니다.

앱이 커지고 복잡해짐에 따른 반작용으로 다양한 문제도 발생할 가능성이 커졌습니다. 개인적으로 성능 문제는 문제를 찾기 어렵고 분석하기도 복잡한 문제입니다.

Kent C.Dodds가 작성한 Profile a React App for Performance 글을 옮기면서 어떻게 리액트 앱을 분석할 수 있는지, 프로파일링하면서 주의해야 할 것은 무엇인지 확인해보았습니다.

들어가면서

리액트 팀에서 제공하는 react-devtools이 있습니다. 브라우저 Chrome, Firefox 확장 프로그램으로 이용할 수 있습니다. 리액트 앱이 있는 어디서든 사용할 수 있습니다.

React DevTools의 가장 뛰어난 기능 중 하나는 프로파일링 기능입니다. 많은 프로파일러 프로그램이 있지만 이 포스트에서는 React Profiler를 사용합니다. React Profiler를 사용하면서 흔히 겪는 문제를 피하는 방법을 보여주려합니다.

note

Profile a React App for Performance에서는 my bookshelf app을 이용해 프로파일링 하는 과정을 다룹니다.

확장 프로그램 설치

프로파일러로 선택한 React DevTools를 브라우저에 설치합니다. 저는 크롬을 사용하고 있어서 Chrome 스토어에서 설치했습니다. 설치 후에 확장 프로그램이 나타나지 않는다면 브라우저 DevTools panel을 닫았다가 다시 열어보세요.

리액트 앱 실행

리액트 앱을 실행해보겠습니다. 제 블로그 앱은 next.js를 사용하고 package manager로 pnpm을 사용합니다. pnpm dev 명령어를 통해 next.js development server를 동작합니다.

react-app

프로파일러 실행

개발자 도구를 열어 "⚛️ Profiler" 탭을 클릭합니다. 그리고 해당 창에서 "Start profiling" 버튼(REC버튼 처럼 생긴, 실제로 녹화하는 것처럼 프로파일링 합니다.)을 클릭합니다.

react-profiler-start

그런 뒤에 앱을 사용해보면서 프로파일링을 진행합니다. 그런 다음 "Stop profiling" 버튼(빨간색으로 변한)을 클릭합니다.

react-profiler-stop

프로파일링 결과 확인

리액트 앱 프로파일링 결과를 분석할 때 DevTools에서 제공하는 몇 가지 설정들이 있습니다. 기본 View는 "Flamegraph chart"입니다. 이는 프로파일링 결과를 시간 순서에 따라 표현한 것입니다. 또 하나는 "Ranked chart"가 있습니다. 이는 프로파일링 결과를 컴포넌트의 렌더링 시간에 따른 랭킹을 표현한 것입니다. 필요한 정보에 따라 두 개 View를 자유롭게 보면서 분석할 수 있습니다.

react-profiler-result

또 다른 포스트에서 위 차트에 대한 자세한 분석을 진행할 예정입니다.

함정: development mode에서의 측정

프로파일링과 같은 성능 측정을 할 때 피해야 할 함정이 있습니다. 바로 development mode에서의 측정입니다. 위에서 한 작업은 development mode에서 앱이 어떻게 작동하는지 측정했습니다. 리액트 앱을 오랫동안 사용하신 분들은 아시겠지만, development mode에서만 나타나는 많은 warning 들을 보셨을 겁니다. 이는 개발할 때 많은 도움을 주지만 비용이 듭니다.

그래서 development mode에서의 측정은 실제 앱의 성능과는 다를 수 있습니다.

따라서 앱 성능을 정확하게 측정하기 위해서 production version(최종 사용자가 사용할 코드)에서 측정해야 합니다.

production app 빌드와 측정

제 블로그 앱은 pnpm build를 통해 next.js 앱 production version으로 빌드합니다. 그리고 pnpm start를 통해 production version을 실행합니다.

info

react app을 사용하시는 분들 경우 로컬에서 빌드 후 서버(리액트 정적 파일 서버 용도) 실행하는 간단한 방법은 serve를 사용하는 것입니다. npx serve -s build를 통해 빌드된 정적 파일을 서버로 실행할 수 있습니다.

이제 프로파일링을 실행해보겠습니다.

react-profiler-prod-not-working

그런데 다음과 같은 문구와 함께 프로파일링을 할 수 없게 되어있습니다.

Profiling not supported.
Profiling support requires either a development or profiling build of React v16.5+.
Learn more at reactjs.org/link/profiling.

production 빌드를 할 때는 앱을 가능한 한 빠르게 만들기 위해 실제로 프로파일링을 위한 코드를 제거합니다. 즉, 리액트에는 프로파일링에 필요한 코드가 production version에는 없다는 것입니다.

기본 설정으로 빌드했을 경우 production version을 빌드하면 프로파일링을 할 수 없게 됩니다.

따라서 프로파일링을 위한 코드를 포함한 production version을 빌드해야 합니다.

프로파일링을 위한 production version 빌드

  • create-react-app을 사용할 경우 react-scripts@>=3.2.0에서 간단히 npx react-scripts build --profile 과 같이 --profile 설정을 하면 이를 해결할 수 있습니다.
  • next.js를 사용할 경우도 마찬가지로 --profile 설정을 하면 프로파일링을 위한 빌드가 자동으로 됩니다.
  • vite를 사용할 경우 찾아보았을 때 별도로 옵션이 존재하지 않아 '--mode=development'로 빌드 후 확인하는 방법을 사용했습니다.

그 외 경우 webpack 사용 시 이 gist 를 참고하여 webpack 설정을 수정하는 방법도 있습니다.

react-profiler-production-result

production 빌드 시에는 terser작업과 같이 compress시에 이름을 mangling하는데 이 때문에 프로파일링 결과를 알아보기 힘들 수도 있습니다.

Disable name mangling

리액트는 컴포넌트를 name 속성을 기반으로 호출할 대상을 알고 있습니다.

function AwesomeAppComponent() {
  return <div>Awesome</div>;
}
console.log(AwesomeAppComponent.name); // <-- logs "AwesomeAppComponent"

function a() {
  return <div>Awesome</div>;
}
console.log(a.name); // <-- logs "a"

terser의 경우 다음과 같이 mangling을 disable 할 수 있습니다. (https://terser.org/docs/api-reference.html#mangle-options)

// terser
new TerserPlugin({
  terserOptions: {
    // ... some other config ...
    mangle: {
      safari10: true,
    },
    keep_classnames: true,
    keep_fnames: true,
    // ... some more config ...
  },
  // ... even more config ...
});

swc minification을 사용할 경우 다음과 같이 disable 할 수 있습니다.(https://swc.rs/docs/configuration/minification)

// .swcrc
{
  // Enable minification
  "minify": true,
  // Optional, configure minifcation options
  "jsc": {
    "minify": {
      "compress": {
        "unused": true
        // ...
      },
      "mangle": {
        // ...
      }
    }
  }
}

next.js에는 최근에 merge된 이 PR을 통해 --no-mangling 옵션을 사용하면 mangling을 disable 처리할 수 있습니다. 근데 저는 동작을 안하네요... 흠...

함정: 빠른 컴퓨터에서 프로파일링 측정

대부분의 개발자가 앱을 사용하는 사용자보다 훨씬 더 나은 장치로 앱을 개발하고 있습니다. 사용자가 사용하는 장치에서 앱을 사용하면서 프로파일링 하는 것이 더 정확한 결과를 얻을 수 있습니다. 즉, 실제 사용자 경험을 시뮬레이션하기 위해 여러분의 CPU를 조절하면서 프로파일링한다면 앱의 실제 성능을 훨씬 더 잘 이해할 수 있습니다.

그래서 개발자 도구의 Performance 탭에서 CPU throttling을 이용합니다.

performance-cpu-throttling

No Throttlingx6 Thottling
no-throttlingx6-throttling

x6 throttling을 사용하니 꽤 느리게 보이나요? 실제 사용자가 사용하는 장치에서는 더 느리게 동작할 수도 있습니다.

결론

리액트 앱을 적절하게 프로파일링 하는 기본적인 방법에 대해서만 다루었지만 자신의 앱에서 이를 시도하고 앱의 성능을 확인할 수 있는 시간이 되었기를 바랍니다.

또한 React DevTools과 프로파일링에 대해 좀 더 자세히 알고 싶다면 리액트 블로그 글인 DevTools Profile로 컴포넌트 프로파일링 를 참고하시면 좋을 것 같습니다.

reference

마지막 업데이트

11/27/2022


Avatar

JHSeo

배우는 것을 좋아하고 관심이 많은 웹 엔지니어 입니다. 느리더라도 꾸준하게 성장하려고 노력하는 개발자입니다.