본문 바로가기

FrontEnd

리액트 성능 최적화 : Production Monitoring

반응형

사용자가 성능 불만 접수 전에, 성능 문제를 해결합시다.

렌더링 시간의 일부를 어떻게든 추적하고 해당 정보를 서버로 보내 모니터링할 수 있다면 좋을 것입니다.

React DevTools만큼 많은 정보를 제공하지는 않지만 유용한 정보를 제공합니다

 

참고 : facebook은 프로파일링 버전과 비 프로파일링 버전을 동시에 배포합니다,

즉 프로파일링 버전을 사용자의 일부에만 배포하여, 모니터링합니다.

 

주의 : 프로파일러는 기본적으로 프로덕션 모드에서 동작하지 않습니다.

성능 부하가 될 수도 있으니 주의합시다.

Create React App v3.2 이상 버전에서는 다음과 같은 방법을 사용하여 프로덕션에서 프로파일링을 enable할 수 있습니다.

yarn build --profile
npm run build -- --profile

출처 : fb.me/react-profiling

 

기본 사용법은 아래와 같습니다.

출처 : https://ko.reactjs.org/docs/profiler.html

<App>
  <Profiler id="Navigation" onRender={onRenderCallback}>
    <Navigation {...props} />
  </Profiler>
  <Main {...props} />
</App>


function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions, // the Set of interactions belonging to this update
) {
  // Aggregate or log render timings...
}

DIY 해보기

1. 1. 5초마다 성능 모니터링 정보 보내기 위한 모듈 구현

서버에 http post 요청으로 성능 정보를 보낼 수 있습니다.

우리는 따로 서버가 없으므로, 로깅하는 것으로 갈음합니다.

이런식으로 데이터가 보내질 것입니다.

report-profile.js

// 모든 업데이트의 대기열을 유지하고
// 대기열에 데이터가 있는 경우 10초마다 프로필 데이터를 서버로 보냅니다.
// 그런 다음 Grafana 또는 이와 유사한 데이터를 그래프로 표시합니다.

let queue = [];

// 5초마다 서버에 데이터를 보냅니다.
// 실제 시간은 앱의 요구 사항에 따라 다를 수 있습니다.
setInterval(sendProfileQueue, 5000);

function reportProfile(
  id, // 방금 실제 돔에 커밋한 프로파일러 트리의 "id" props
  phase, // "mount"(트리가 방금 마운트된 경우) 또는 "update"(다시 렌더링된 경우)
  actualDuration, // 커밋된 업데이트를 렌더링하는 데 소요된 시간
  baseDuration, // 메모이제이션 없이 전체 하위 트리를 렌더링하는 데 걸리는 예상 시간
  startTime, // React가 이 업데이트를 렌더링하기 시작한 시간
  commitTime // React가 이 업데이트를 커밋했을 때
  //     interactions 이 업데이트에 포함된 인터랙션. 리액트 17에서 trace API가 삭제되면서 사라진 속성
) {
  queue.push({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  });
  // this is a fire and forget, so we don't return anything.
}

function sendProfileQueue() {
  if (!queue.length) {
    return Promise.resolve();
  }
  const queueToSend = [...queue];
  queue = [];
  console.info("sending profile queue", queueToSend);
  // 여기가 실제로 queueToSend 데이터를 백엔드로 보내기 위해 서버를 호출하는 곳입니다...
  // 우리는 딱히 백엔드가 없으므로, return Promise.resolve()를 호출하고 끝냅니다.

  return Promise.resolve();
}

export default reportProfile;

 

2. 사용자 이벤트 포함하기 (interactions)

App.js

// Production performance monitoring
import * as React from 'react'
// 리액트 17에서 삭제됨 ㅠㅠ
// import {unstable_trace as trace} from 'scheduler/tracing'
import reportProfile from './report-profile'

function Counter() {
  const [count, setCount] = React.useState(0)
  // const increment = () => 리액트 17에서 삭제됨 ㅠㅠ
  // trace('click', performance.now(), () => setCount(c => c + 1))
  const increment = () => setCount((c) => c + 1);
  return <button onClick={increment}>{count}</button>
}

function App() {
  return (
    <div>
      <React.Profiler id="counter" onRender={reportProfile}>
        <div>
          Profiled counter
          <Counter />
        </div>
      </React.Profiler>
      <div>
        Unprofiled counter
        <Counter />
      </div>
    </div>
  )
}

export default App

 

참고

Interaction tracing with React (github.com)

https://reactjs.org/docs/profiler.html

https://kentcdodds.com/blog/react-production-performance-monitoring

https://kentcdodds.com/blog/profile-a-react-app-for-performance

 

반응형