웹에 날개를 달아주는 웹 성능 최적화 기법
- 본 책을 읽고 책의 내용을 간략하게 정리한 글입니다.
Chapter 5. 웹에서 가속을 이끌어 내는 방법
웹 브라우저 동작 이해하기
- 가장 먼저 도메인 서버와 통신하여 접속하려는 호스트의 IP를 찾음
- 해당 IP를 가진 서버와 통신을 시도해 TCP 연결 맺음
- HTTPS 에선 암호화된 연결을 생성하려는 협의 단계 추가
- 연결이 맺어지면 브라우저는 서버로부터 필요한 리소스들을 다운로드해 화면에 표현
브라우저 아키텍처
- 브라우저의 7가지 컴포넌트
- 유저 인터페이스
- 사용자가 브라우저를 통해 상호 작용할 수 있도록 도움
- 브라우저 엔진
- 유저 인터페이스와 렌더링 엔진 사이에서 렌더링 상태를 조회하고 렌더링 작업을 제어하기 위한 인터페이스 제공
- 렌더링 엔진
- HTML 분석하여 그대로 표현하거나, CSS를 분석해 꾸미는 등 실제 웹 컨텐츠를 브라우저 창에 그리는 역할
- Webkit(사파리), Gecko(파이어폭스), Blink(크롬), Trident(인터넷 익스플로러) 등
- 네트워킹
- 네트워크를 통해 HTTP 요청을 보내고 응답받는 역할
- DNS 조회, TCP 연결 등의 작업을 수행
- 브라우저별로 6-10개 스레드로 동시에 TCP 연결을 생성해 리소스들을 신속히 다운로드
- UI 백엔드
- 콤보박스, 드롭박스 등 기본적인 UI 컴포넌트들 제공
- 자바스크립트 해석기
- V8, Spider Monkey 등의 엔진을 사용하여 자바스크립트를 분석 및 해석
- 데이터 저장소
- 데이터 지속성을 유지하기 위한 컴포넌트
- 가장 흔하게는 쿠키 값을 로컬 디스크에 저장
- HTML5 에서는 로컬 스토리지, 인덱스 DB 등을 이용해 더 많은 데이터를 저장할 수 있음
- 실제로 HTML을 처리해 화면에 렌더링하는 컴포넌트는 “렌더링 엔진”
중요 렌더링 경로
- 렌더링 엔진이 웹 페이지를 구문 분석해 화면에 표현하는 일련의 작업은 선후 관계가 명확하므로 단일 스레드에 의해 수행
- 브라우저는 HTML을 가장 먼저 구문 분석하면서 DOM 트리를 만들고 CSS 구문 분석하여 CSSOM 트리를 만듬
- 그 후 두 개의 트리 모델을 결합해 최종적으로 렌더 트리 생성
- 렌더 트리가 생성되면 브라우저는 이를 기반으로 페이지 구조를 결정하고 화면에 표현
DOM 트리 생성
- DOM (Documents Object Model)은 C#이나 자바 같은 객체 지향적 프로그램 언어로 표준으로 규정된 프로그램 인터페이스
- HTML이 DOM으로 바뀌면 프로그램 인터페이스로 원하는 태그를 조회하고 수정할 수 있음
-
let myElement = document.getElementById("intro"); document.getElementById("demo").innerHTML = "Hello World!";
-
- DOM은 다른 프로그래밍 인터페이스와 마찬가지로 객체 속성과 메서드, 이벤트 등을 정의함
CSSOM 트리 생성
- 브라우저가 HTML을 구문 분석하여 CSS를 참조하는 링크를 만나면 해당 CSS 리소스를 다운로드하고 구문 분석기가 CSS를 분석하기 시작
- CSS는 HTML과는 다르게 구문 분석에 엄격한 구문 검사가 적용됨
- 구문 분석 방법이 다르다 보니 사용하는 구문 분석기와 동작 스레드도 다름
렌더 트리 생성
- DOM과 CSSOM을 기반으로 렌더링을 위한 최종 정보를 가진 렌더 객체들을 생성해 이들의 상하 관계를 트리 모양으로 구성
- 예제
- DOM 트리의
<body>
태그는 렌더링하는 데 아무 의미가 없으므로 제외 - CSSOM 트리의 Img 노드는
display:none
으로 설정되었기에 DOM 트리의<img>
태그는 렌더 트리에서 제외
- DOM 트리의
레이아웃
- 렌더 트리 노드들의 위치 정보가 계산되는 단계
- 렌더 객체는 사각형 영역을 표시하므로 브라우저 창의 맨 왼쪽 위에서 시작하여 아래 오른쪽으로 이동하며 각 사각형 영역의 너비와 높이를 계산
페인트
- 렌더 트리 정보를 바탕으로 브라우저 창에 표현하는 단계
- 자바스크립트는 DOM과 CSSOM을 동적으로 변경할 수 있으며 이 경우 렌더 트리가 변경되고 레이아웃, 페인트 단계가 다시 수행됨
브라우저 렌더링 최적화하기
DOM 최적화하기
- HTML은 구문 체크에 관대하므로 문법 오류가 발생해도 브라우저에선 정상적으로 표현되는 경우가 많음
- 따라서 다양한 오류를 포용하기 위해 브라우저에서는 잘 알려진 수많은 오류 사항에 대해 예외 처리 방안 구현
- 웹 페이지 내 오류가 많을수록 브라우저는 예외 처리를 위해 더 많은 메모리와 CPU 파워 소모
- HTML 구문 오류를 최소화하고 간소화하는 것이 웹 사이트 성능 향상의 기본
- 과도하게 HTML 태그를 중첩 사용하는 행위도 피할 것
자바스크립트와 CSS 배치하기
- 자바스크립트는 이미 생성된 DOM과 CSSOM을 변경시킬 수 있음
- 자바스크립트는 CSSOM 생성이 완료될 때까지 수행을 중지하고 대기
- 즉 렌더링에 있어 CSS가 자바스크립트보다 더 높은 우선순위
- CSS를 최대한 소스 위쪽에 배치하여 CSSOM이 가능한 빨리 생성되도록 할 것
- 자바스크립트는 최대한 소스 아래쪽에 배치하여 DOM과 CSSOM이 모두 생성된 이후에 수행될 수 있도록 할 것
자바스크립트 최적화하기
- 자바스크립트가 전체 페이지 로딩 시간에 영향을 주는 것을 막으려면 자바스크립트 수행이 렌더링 스레드를 방해하지 않도록 별도 스레드로 자바스크립트를 수행시켜야 함
- 자바스크립트에서 제공하는 관련 속성 사용
-
<!-- async 속성: HTML 구문 분석과 동시에 자바스크립트를 다운로드하고 수행되도록 함 --> <script src="async_script.js" async> </script> <!-- defer 속성: 구문 분석 중에 별도의 스레드로 자바스크립트를 다운로드하고 구문 분석이 끝난 이후에 수행되도록 함 --> <script src="defer_script.js" defer> </script>
async
속성은 지연 수행 시 스크립트 간 선후 관계를 따지지 않지만defer
속성은 스크립트가 호출된 순서에 따라 차례로 수행- 스크립트 사이에 종속 관계가 있을 수 있으므로 비동기 처리나 지연 처리를 무분별하게 사용하면 안 됨
- 초기 렌더링에 꼭 필요한 그룹과 그렇지 않은 그룹으로 분류해 후자의 그룹에
async
,defer
속성 적용해야 함 - 브라우저는 페이지 로딩이 완료되면
onload
이벤트를 발생시키므로onload
이후에 스크립트를 수행시키는 것이 페이지 로딩 시간을 단축시키는 확실한 방법 -
// onload 시 스크립트 js를 다운로드 function deferOnload() { let element = document.getElementsByTagName("script"); for (let i = 0; i < element.length; i++) { element[i].src = element[i].getAttribute("t"); } } // onload 이벤트 등록 if (window.addEventListener) { window.addEventListener("load", deferOnload, false); } else if (window.attachEvent) { window.attachEvent("onload", deferOnload()); } else { window.onload = deferOnload; }
CSS 최적화하기
- CSSOM이 만들어지기까지 렌더링을 멈추므로 CSS는 렌더링 순위가 가장 높으면서 동시에 렌더링을 가장 방해하는 리소스
- CSS는 필요한 정보만 빠르게 다운로드하고 실행해야 브라우저 렌더링을 가속시킬 수 있음
- CSS를 적절히 분리하여 필요한 페이지에 필요한 CSS 파일만 포함해야 함
- 첫 화면에 사용될 CSS 파일과 숨겨진 화면에 사용될 CSS 파일을 분리하여 후자의 CSS는 지연 수행시켜야 함
- 필요한 CSS만 다운로드하려면 미디어 쿼리를 사용할 수 있음
- 기기 크기에 따라 적합한 CSS 파일을 다운로드해 적용하는 예제 ```html
```
- 숨겨진 화면에 적용될 CSS 파일들은
onload
이벤트 발생 이후 적용되도록 처리
이미지 로딩 최적화하기
- 화면 렌더링에 필요하지 않은 이미지를 다운로드하지 않도록 하기
- 그 이미지가 웹 사이트의 주요 이미지가 아니라면 CSS의
background-image
속성을 사용해 원치 않는 다운로드를 피할 수 있음 - 자바스크립트를 이용한 지연 로딩 방식을 적용해 불필요한 다운로드를 피할 수 있음
- 이미지 지연 로딩은 브라우저 로딩 속도 개선에 효과적일 수는 있지만 사용자 경험 개선에 항상 도움되는 것은 아님
- 지연 로딩은 첫 화면에 등장하지 않거나 숨겨진 이미지들을 다운로드하는 데만 사용하는 것 권장
- Progressive JPG 활용하기
- Progressive JPG: 고품질 이미지를 한 번에 전송하지 않고 분할 전송하는 방식
- 인터넷 속도가 빠른 구간에서는 차이를 느낄 수 없지만 느린 구간에서는 이미지를 빠르게 볼 수 있음
- 그 이미지가 웹 사이트의 주요 이미지가 아니라면 CSS의
도메인 분할 기법 이용하기
- 도메인 분할 기법 (domain sharding)은 여러 도메인을 소유한 경우 웹 컨텐츠를 병렬적으로 동시에 다운로드할 수 있도록 하는 방법
- 추가 도메인 디자인 예시
- www.feokorea.com : 웹 사이트 메인 페이지 및 동적 컨텐츠를 위한 도메인
- img.feokorea.com : 이미지 호출을 위한 도메인
- script.feokorea.com : 자바스크립트, CSS와 같은 정적 컨텐츠를 위한 도메인
- api.feokorea.com : API 서비스를 사용하기 위한 도메인
- 도메인 분할 기법을 사용하면 사이트 전체 쿠키 사이즈를 축소할 수 있다는 장점
- 하나의 웹 페이지에 포함된 리소스 개수가 얼마나 많은가에 따라 추가할 서브 도메인의 숫자를 결정해야 함
- 도메인 개수를
Nd
라고 할 때, 응답 속도 계산법- ```
-
가정 페이지당 평균 다운로드 리소스 수: 120 목표 응답 시간: 2초 평균 다운로드 속도: 300ms 브라우저 동시 연결 수: 6개 (크롬 기준)
-
도메인 개수를 Nd라고 할 때, 응답 속도는 아래와 같이 계산 가능 총 연결 수: 6 * Nd 연결당 다운로드 횟수(Np): 120/6Nd 응답 시간: Np * 300ms = 2초 (= 2000ms) 도메인 개수(Nd): 3 ```
-
- ```
도메인 분할 기법과 HTTP/2
- 최근 사용되는 브라우저들은 TCP 연결을 병합하는 방식으로 HTTP/2 기능을 저해하지 않으면서 다중 도메인을 사용할 수 있는 방안 제공
- 해당 기술이 적용하기 위해서는 몇 가지 고려해야 할 사항 존재
- 브라우저가 DNS를 확인할 떄 각 도메인은 모두 동일한 IP 주소를 반환해야 함
- 동일한 인증서를 사용해야 함
사용자 경험 개선하기
사용자 경험 지표 알기
- 사용자 중심의 지표
- WebPageTest의 Speed Index
- 시간에 따른 웹 사이트의 시각적 진행 상태를 수치화한 지표
- 값이 작을수록 시각적 진행 상황이 빠르다는 것을 의미
- First Contentful Paint
- 화면에 눈에 띄는 컨텐츠가 처음 표현되는 시점인 첫 번째 컨텐츠가 있는 페인트
- Largest Contentful Paint
- 히어로 이미지와 같은 주요 컨텐츠가 로딩되는 시점인 가장 큰 컨텐츠가 있는 페인트
- Time to Interactive
- 사용자와 상호 작용이 어느 정도 가능해지는 시점
- WebPageTest의 Speed Index
사용자 요청에 빠르게 반응하기
- 사람은 응답 속도가 100ms 가 늦어지면 이를 인지할 수 있고, 1초가 지나면 지연으로 인식
- 즉 웹 사이트는 1초 안에 반응해야 자연스럽게 그 다음 반응을 기대하게 되고, 2초 안에 페이지 로딩이 완료되어야 사용자의 이탈을 방지할 수 있음
- 브라우저가 렌더링을 빠르게 시작하게 하기 위한 최적화 기법
- CSS와 자바스크립트 파일 크기 줄이기
- 렌더링할 부분만 남기고 필요하지 않은 부분은 제거하는 방법이 가장 확실
- CSS와 자바스크립트들을 중요 리소스와 그렇지 않은 리소스로 분류
- 위에서 분류한 중요 리소스들은 가능한 빠르게 로딩시키기
- 가장 간단하게는 리소스 힌트 preload 사용
- HTTP/2 서버 푸시를 활용하는 방법도 존재
- 중요하지 않은 리소스들은 나중에 로딩
- 자바스크립트의
async
나defer
속성 사용하거나onload
이벤트 이후 수행하도록 지연
- 자바스크립트의
- CSS와 자바스크립트 파일 크기 줄이기
사용자 시선 붙잡기
- 사용자의 시선을 붙잡으려면 히어로 이미지가 가능한 빠르게 화면에 로딩되어야 함
- 히어로 이미지 로딩이 늦어지는 이유
- 페이지 로딩 시간 개선을 위해 모든 이미지들을 일괄적으로 지연 로딩시킴
- CSS의
background-image
속성으로 지정될 경우background-image
속성으로 지정된 이미지들은 CSS가 분석되고 DOM에 적용될 때 다운로드되고, preloader에도 적용되지 않음- 즉 다른 이미지들보다 훨씬 늦게 다운로드
- 아래 규칙을 따라 히어로 이미지를 일찍 로딩하도록 할 것
- HTML의
<img>
태그나<picture>
태그를 사용하여 직접 다운로드하고 지연 로딩을 적용하지 않음 - CSS의
background-image
속성에 히어로 이미지를 사용하지 않음 - CSS 배경 이미지로 히어로 이미지를 꼭 사용해야 한다면 리소스 힌트인 preload를 사용하여 일찍 다운로드 받을 것
- HTML의
- 메인 텍스트 역시 의미있는 컨텐츠를 구성함
- 브라우저에서 텍스트는 폰트를 먼저 다운로드해야 표현되므로 폰트를 빠르게 다운로드해야 함
- 폰트는 CSS의
font-face
를 통해 로딩되므로 일반적으로 이미지보다 다운로드 시점이 늦음 - 개선하기 위해서는 리소스 힌트인 preload를 사용하여 필요한 폰트를 일찍 다운로드하는 것을 권장
- 한글 폰트는 폰트 파일 크기가 크기 때문에, preload 전에 폰트 파일 크기를 경량화해야 함
- 폰트 로딩 방식 또한 메인 텍스트를 빠르게 표현하는 데 중요한 역할
- FOIT (Flash Of Invisible Text): 웹 폰트를 완전히 다운로드 후 텍스트를 나타내는 방식
- FOUT (Flash Of Unstyled Text): 시스템 폰트를 먼저 사용 후 웹 폰트가 다운로드되면 대체하는 방식
- 사용자 경험을 생각한다면 FOUT 방식이 효과적
- FOUT 방식을 사용하려면 CSS
font-face
룰에서font-display
속성을swap
으로 변경 -
@font-face { font-family: MyFont; src: url(/path/to/fonts/myFont.woff) format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
웹에 날개를 달아주는 웹 성능 최적화 기법, 강상진, 윤호성 저, 루비페이퍼 출판