본문 바로가기

웹개발

웹 서비스 퍼포먼스를 향상시키는 법

 

 

 

웹 서비스의 퍼포먼스가 좋다는 것은 페이지 로딩 속도가 빠르다는 것을 의미한다. 

 

페이지 로딩 속도는 사용자의 경험과 직결되어 있는데 페이지가 로드되는데 한참 걸린다면 그것은 바로 사용자의 사이트 이탈을 의미하기 때문이다. 따라서 페이지의 로딩 과정에서의 최적화가 이루어져야한다.   

 

 

 


 

브라우저 로딩 과정(페이지 로딩 과정)

브라우저 로딩 과정은 다운로드, 파싱, 레이아웃, 페인트, 합성으로 나뉜다. 

 

  1. 브라우저에서 웹 페이지를 로드하면 가장 먼저 HTML 파일을 다운로드한다.
  2. 다운로드한 HTML을 파싱(해석)해서 DOM 트리를 구성한다(태그들이 노드). 
  3. HTML 파싱 중 script, link, img를 발견하면 각 리소스를 요청하고 다운로드한다.
  4. 그다음 CSS 파일을 파싱 하여 CSSOM 트리(선택자가 노드)를 구성한다.
  5. 스타일 단계에서는 생성된 DOM, CSSOM 트리를 가지고 스타일을 매칭 시켜주는 과정을 거쳐 랜더 트리를 구성한다. 
  6. 레이아웃 단계에서는 루트부터 노드를 순회하여 노드의 정확한 위치를 계산한다. (이 과정에서 %, vw등을 px단위로 바꿔줌) (geometry calculate)
  7. 페인트 단계에서는 레이아웃 단계에서 계산된 위치(각 노드)에 실제 픽셀로 그려주는 역할을 한다. (Fragment fill)
  8. 합성 & 랜더 단계에서는 페인트 단계에서 생성된 레이어를 합성하여 스크린을 업데이트한다. 이때 이후로 화면에서 웹페이지를 볼 수 있다. 

 

레이아웃과 리페인트

 

브라우저 로딩 과정에서 (스타일 - 레이아웃 - 페인트 - 합성) 과정을 랜더링이라고 한다. 랜더링 과정은 기하학적인 영향을 주는 상황에 따라 반복해서 발생하기도 한다. (DOM요소의 변경 등) 이 때문에 랜더 트리가 다시 그려지는 경우를 레이아웃이라고 한다. (리플로우)

 

반대로 요소의 기하학적인 영향을 주지 않는 CSS 속성 값을 변화시킬 때는 레이아웃 과정을 건너뛰고 페인트 과정부터 다시 시작하는 데 이것을 리페인트라고 한다. 

 

레이아웃이 일어나면 전체 픽셀을 다시 계산해야 하므로 부하가 크지만 리페인트는 레이아웃에 비해 부하가 적다. 따라서 불필요한 레이아웃이 발생하지 않도록 신경 써야 한다.

 

요소에 기하적인 영향을 주는 CSS 속성 값 변경

  • CSS 속성값 : height, width, left, top, font-size, line-height 등

 

 


 

성능 개선 지표 

 

성능 지표의 측정 기준은 브라우저와 사용자 입장으로 나눌 수 있다.

 

전통적인 성능 측정방식: 브라우저에서 발생하는 이벤트를 사용하는 것

 DOMContentLoaded, load 이벤트로  각 이벤트가 발생하는 시점으로 성능 측정, 두 이벤트 발생 사이에 구간 폭이 좁을수록 성능이 좋다. 

  • DOMContentLoaded: HTML, CSS 파싱이 끝나는 순간, 랜더 트리를 구성할 준비가 끝난 시점(DOM, CSSOM 트리 구성 완료)
  • load: HTML상 필요한 모든 리소스가 로드된 시점 (내비게이션 타이밍 API 사용하거나 크롬 개발자 도구로 확인해 볼 수 있다.)

하지만 개발 패러다임이 변화하면서 기존 전통적인 방식으로 판단하기 어려워졌다. SPA로 두 이벤트 사이의 간격을 줄일 수 있지만 그 후에도 수많은 스크립트 실행으로 인한 느린 로딩이 존재한다. 그래서 새로운 성능 측정방식이 필요하게 되었다. 

 

 

사용자 기준의 성능 측정: 사용자에게 컨텐츠를 보여주는 여러 가지 시점이 기반이다.

컨텐츠가 일정 시간이 지난 후 한 번에 보이는 것이 아니라 의미 있는 컨텐츠부터 일정 부분씩 점진적으로 보이는 것을 사용자는 최적화가 잘 된 페이지로 보고 로딩이 빠르다고 느낀다.

 

구글에서는 이러한 성능을 측정하는 지표를 4가지로 나누어 설명한다. 

  1. FP(First Paint): 흰 화면에서 화면에 무언가가 처음으로 그려지기 시작하는 순간이다.
  2. FCP(First Contentful Paint): 텍스트나 이미지가 출력되기 시작하는 순간이다.
  3. FMP(First Meaningful Paint): 사용자에게 의미 있는 콘텐츠가 그려지기 시작하는 첫 순간이다. 콘텐츠를 노출하는데 필요한 CSS, 자바스크립트 로드가 시작되고 스타일이 적용되어 주요 콘텐츠를 읽을 수 있다.
  4. TTI(Time to Interactive): 자바스크립트의 초기 실행이 완료되어서 사용자가 직접 행동을 취할 수 있는 순간이다.

가장 중요한 시점은 FMP로 흰 화면 대신 의미있는 컨텐츠를 빠르게 보여줘 사용자에게 빠르다는 인상을 줄 수 있다. 주요 랜더링 경로를 최적화하면 해결할 수 있다. 

 

 

 

 


그렇다면 어떤 방식으로 페이지 로드 속도를 빠르게 할 수 있을까? 

 

최적화 전략은 브라우저 로딩, 렌더링 과정을 정확하게 이해하고 각 과정에서 일어나는 불필요한 작업을 제거하는 것이다. 

 

웹 페이지 로딩 최적화

 

블록 리소스의 최소화

블록이라는 것은 브라우저 과정 중 파싱 과정에서  다른 요소(CSS, 자바스크립트)등을 다운로드하고 파싱 하는 과정 때문에 브라우저 과정이 멈추는 것을 의미한다. 

이러한 블록 리소스들을 최적화하여 로딩 과정을 최적화해야 한다.

 

CSS 최적화

  • CSSOM 트리는 다른 요소들과 다르게 CSS를 모두 해석해야만 구성된다. CSSOM트리가 구성되지 않아 랜더 트리를 만드는 것이 차단되지 않도록 항상 문서 최상단(haed)에 배치시킨다.  
  • 특정 조건에서만 필요한 css가 있다면 미디어 쿼리를 이용하면 블로킹을 방지할 수 있다.
  • 외부 스타일 시트를 가져올 때 @import 사용을 피한다. (스타일을 병렬로 다운로드가 되지 않아 로드 시간 늘어남)

 

자바스크립트 최적화: 

  • 블록을 방지하기 위해 body의 최하단에 위치시키거나 defer 속성을 사용해준다( async나 defer는 사용 브라우저가 한정적임 주의 ).

 

리소스 요청 수 줄이기: 

  • 리소스 파일 하나를 요청하는 데 많은 시간이 소요되므로, 필요한 요청만 할 수 있도록 해야 한다.
  • 이미지 스프라이트: 여러 개의 이미지를 하나의 파일로 합쳐서 관리하면 개별 이미지를 사용할 때보다 리소스 요청이 줄어든다.
  • CSS, 자바스크립트들을 webpack과 같은 번들러를 이용해 하나의 번들 파일로 합쳐서 요청을 줄인다.  
  • link태그로 외부 스타일 시트를 가져오는 대신 문서 안에 style 태그로 내부 스타일 시트를 이용해서 리소스 요청을 줄일 수 있다. (단, 리소스 캐시는 사용 불가능)
  • 작은 이미지는 HTML, CSS로 대체하여 줄일 수 있다. Data URI 처리로 외부 경로를 Base64로 변환된 URI로 대체하여 HTML, CSS에 포함하여 요청을 하지 않게 한다. (단, 리소스 캐시는 사용 불가능)

리소스 용량 줄이기: 각 리소스에 맞게 불필요한 데이터를 제거하고 압축하여 사용하는 것이 좋다.

  • 중복 코드 제거하기
  • 외부 유틸 사용을 주의하기: lodash 같은 유틸 라이브러리 사용을 주의해야 한다. import _ from 'lodash’; 와 같은 방법은 용량이 커지므로 부분적으로 호출해서 사용하도록 해야한다.

 

HTML 마크업 최적화

  • 공백, 주석 등을 제거하고 태그의 중첩을 최소화해서 단순하게 구성하여 DOM트리가 커지는 것을 막아야 한다.
  • 간결한 CSS 선택자를 사용해 최적화해야 한다. (Id 대신 class로 중복된 스타일 처리, 선택자는 최소화하여 사용)
  • HTML, CSS, 자바스크립트 모두 webpack 플러그인과 같은 도구로 압축하여 사용할 수 있다.

 

 


 

웹 페이지 렌더링 최적화

웹페이지를 랜더링 하기 위해서는 DOM과 CSS가 필요하지만 다양한 기능, 효과를 구현하기 위해서는 자바스크립트가 렌더링에 어떤 영향을 주는지 알아야 한다. 

자바스크립트는 단일 스레드로 동작하기 때문에 자바스크립트 실행 속도는 랜더링 성능과 직결된다. 자바스크립트로 인한 DOM, CSS변경이 다시 화면을 끊임없이 랜더링 시킬 수 있기 때문에 이 부분 역시 최적화가 되어 있어야 한다. 

 

 

레이아웃 최적화

레이아웃 최적화의 목표는 자바스크립트 실행 과정과 랜더링이 다시 일어나는 과정에서 레이아웃이 걸리는 시간을 단축하고 레이아웃을 최대한 적게 하고 리페인트만 할 수 있도록 해야 한다.

 

자바스크립트 실행 최적화: 자바스크립트 실행 속도를 최소화하여 한 프레임 처리를 빠르게 해야 한다

  • 강제 동기 레이아웃 피하기: 계산된 값을 호출하기 전 변경된 스타일이 계산 결과로 적용돼있어야 하기 때문에 강제로 동기 레이아웃을 발생시킨다. 이러한 부분들을 주의하여 강제 동기 레이아웃을 발생시키는 부분을 최대한 피해야 한다.
  • 레이아웃 스래싱(thrashing) 피하기: 한 프레임 내에서 강제 동기 레이아웃이 연속적으로 발생하면 성능이 더욱더 느려진다. 공통으로 사용하는 부분을 따로 선언하는 방식 등으로 레이아웃 스래싱을 막아야 한다.
  • 가능한 한 하위 노드의 DOM을 조작하고 스타일을 변경: 상위 노드를 조작하면  랜더링 과정이 모두 필요해질 수 있다. 변경 범위를 줄여야 한다.
  • 영향받는 엘리먼트 제한: 부모-자식 관계, 같은 위치의 엘리먼트 등의 영향을 최소화해야 한다. (위치 변경이 최소화되도록)
  • 숨겨진 엘리먼트 수정: display: none은 다시 보이도록 속성을 변경해도 레이아웃과 리페인트가 발생하지 않는다.

 

애니메이션 최적화

애니메이션 처리 시 한 프레임 처리가 16ms(60 fps) 내로 완료되어야 자연스러운 렌더링을 만들어낼 수 있다.

  • requestAnimationFrame() 사용: 브라우저의 프레임 속도에 맞추어 애니메이션이 실행된다. 브라우저 WebAPI상에서 실행되는 setTimeout이나 setInterval과 달리 자바스크립트 엔진에서 랜더링과 함께 호출되기 때문에 더 정확한 시간대로 애니메이션을 수행하고 콜백 함수가 호출되지 않아 불필요한 동작을 하지 않는다. 
  • CSS 애니메이션 사용: CSS 애니메이션을 사용해 자바스크립트를 실행할 필요 없이 부드러운 애니메이션을 구현할 수 있다.
  • Position: absolute 처리로 주변 영역에 영향이 없도록 주의해야 한다
  • transform 사용: transform으로 레이어에서 분리해 레이아웃과 페인트를 줄일 수 있다. transform은 합성만 일으켜 랜더링 속도를 줄일 수 있다.

 

 

 

 

참고: https://ui.toast.com/fe-guide/ko_PERFORMANCE