웹 프론트엔드에서 성능을 측정하는 여러 방법론들이 있습니다만, 이론 만으로는 실제 문제를 진단하고 적용하기 난해할 수 있습니다. 렌더링 횟수를 줄이는 방법을 흔히 생각해볼 수 있는데요, 사실 특수한 상황이 아니고서야 랜더링 횟수 몇 번 줄여서 달라지는 차이는 체감이 안됩니다. 게다가 그정도의 성능을 향상시키기 위해 코드가 지저분해진다면 그게 진짜 올바른 방향인지 의문이 들기도 하죠. 이번 포스팅에서는 제가 현업에서 효과적으로 성능을 개선했던 사례들을 소개하려고 합니다.

이벤트 누적 처리

문제점

사용자의 키 입력에 따른 반응이 너무 늦어서 불편함이 발생되는 상황입니다.

원인

개발중인 서비스는 keydown 이벤트를 사용했습니다만, 흔히 input 태그의 onChange 이벤트에도 해당될 수 있습니다. eventhandler로 실행되는 코드가 워낙 복잡한 알고리즘으로 구성되며 실행시간도 길었습니다. 알고리즘을 최적화 하기도 어려운 상황이고, 그렇다고 그대로 두기에는 연속적인 이벤트가 많이 일어나기에 사용자 입장에서는 입력이 부드럽지 않고 끊기는 현상이 일어납니다.

해결

키 입력마다 실행 시간이 긴 코드를 바로 실행하는 것이 아닌 다음 입력까지 기다렸다가 누적해서 처리하도록 설정합니다. 이때 사용되는 것이 setTimeoutclearTimeout입니다. React JS로 예시를 들어보겠습니다.

function App() {
  const onChange = () => {
    execute();
  };

  return <input onChange={onChange} />;
}

위의 코드대로라면 매번 onChange시마다 execute()가 실행됩니다.

function App() {
  const timeout = useRef<NodeJS.Timeout>();

  const onChange = () => {
    if (timeout.current) clearTimeout(timeout.current);
    timeout.current = setTimeout(() => {
      execute();
    }, 500);
  };

  return <input onChange={onChange} />;
}

새로 개선된 방식으로는 timeout으로 스케줄을 걸어놓고 500ms동안 새로운 입력이 없을 시에만 실행합니다. execute() 자체의 속도를 개선하지는 못했지만 적어도 입력 중에 끊기는 현상은 해결됩니다.

브라우저 캐싱

문제점

당시 개발하던 웹 서비스에서 첫 페이지가 화면에 나타나는데까지 걸리는 시간이 길다는 이슈가 있었습니다. 타사에서 개발한 페이지와 비교되면서 문제가 제기되었죠.

원인

비교 대상이 되었던 서비스와의 차이는 브라우저 캐싱이었습니다. 타사의 서비스와는 다르게 우리는 의도적으로 캐싱을 막아놨던 겁니다. 이 부분의 담당자는 소스 코드를 수정하고 재배포하면 캐싱된 소스들이 브라우저에 그대로 남아 업데이트가 적용되지 않는다는 이유로 캐싱을 막아놨다고 합니다. 당시 Nginx 설정은 아래와 같습니다.

location / {

	...

	try_files $uri $uri/index.html =404;
	error_page 404 /404/index.html;

	expires -1;
	add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}

expires 속성과 Cache-Control 헤더를 통해 브라우저 캐싱을 완전히 막아놓은 상태입니다. 이 경우 같은 페이지를 재방문할때 .js.css 파일과 같이 변하지 않는 정적 파일임에도 매번 http 요청을 하게됩니다. 업데이트를 실시간으로 적용시키겠다는 이유 하나로 캐싱을 전혀 하지 않는 것은 과한 조치였죠. 따라서 실시간 업데이트가 잘 적용되는 선에서 캐싱을 최대한으로 활용하는 환경을 구성해야 했습니다.

해결

일반적으로 번들러를 통해 정적 파일을 빌드하는 경우 .js.css 파일명에 해시값을 적용할 수 있습니다. 당시 Next.js로 개발하여 기본 설정된 번들러가 해시값을 파일명에 잘 붙여주고 있었습니다. 따라서 .js.css 파일은 업데이트시에 어차피 파일명이 달라지기에 캐싱을 해도 무관합니다. 아래는 새로 개선된 Nginx 설정입니다.

location / {

	...

	try_files $uri $uri/index.html =404;
	error_page 404 /404/index.html;

	add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, max-age=0';

	if ( $uri ~ \.js|\.css$ ) {
	  add_header 'Cache-Control' 'must-revalidate';
	}
}

개선된 설정에서는 if directive를 통해 요청 uri.js 또는 .css로 끝나면 Cache-Control 헤더가 달라지도록 합니다. 즉, .js.css 파일은 브라우저에서 자체적으로 캐싱하도록 풀어준 것이죠. 이 경우 페이지에 처음 접속할때는 차이가 없지만, 다음 접속부터 속도가 눈에 띄게 개선됩니다.

image1 image2

캐싱 전과 후의 페이지 첫 화면이 나타나는데까지 걸리는 시간을 비교해봤습니다. 보다시피 스크립트가 실행되는 시간은 10ms에 불과합니다. 대부분은 유휴 상태, 즉 I/O에 의한 시간소요입니다. 서비스의 특성상 첫 페이지 노출을 위해 요청하는 API가 여럿 있기에 여전히 긴 유휴 상태가 존재하지만, .js.css 캐싱만으로도 절반 가까이 줄어든 것을 볼 수 있었습니다.

업데이트:

댓글남기기