본문 바로가기
프로그래밍/Web

브라우저 작동 방식

by JR2 2022. 11. 26.

본 게시글은 mdn에서 작성한 “Populating the page: how browsers work”를 번역하였습니다.

 


사용자는 콘텐츠를 빠르게 로드하고 원활허게 상호 작용할 수 있는 웹 경험을 원합니다.

따라서 개발자는 이 두 가지 목표를 달성하기 위해 노력해야 합니다.

 

성능을 개선하는 방법을 이해하려면 브라우저 작동 방식을 이해하는 것이 좋습니다.

 

개요

빠른 사이트는 더 나은 사용자 경험을 제공합니다. 사용자는 빠르게 로드되고 원활하게 상호 작용할 수 있는 콘텐츠가 포함된 웹 경험을 원하고 기대합니다.

 

웹 성능의 개선을 위해서는 두 가지 주요 문제를 이해하는 것이 중요합니다.

 

대기 시간은 빠른 로드를 보장하기 위해 극복해야할 주요 위협입니다.

빠르게 로드하기 위해 개발자의 목표는 요청된 정보를 가능한 한 빨리 보내거나 빠른 것처럼 보이게 만드는 것입니다.

네트워크 대기 시간은 무선으로 컴퓨터에 바이트를 전송하는 데 걸리는 시간입니다.

웹 성능은 페이지 로드가 가능한 한 빨리 일어나도록 하기 위해 우리가 해야 할 일입니다.

 

대부분의 경우 브라우저는 단일 스레드로 간주됩니다.

원활한 상호 작용을 위해 개발자의 목표는 부드러운 스크롤에서 터치 반응에 이르기까지 성능이 뛰어난 사이트 상호 작용을 보장하는 것입니다.

메인 스레드가 모든 작업을 완료하고 사용자 상호 작용을 항상 처리할 수 있도록 하는 렌더링 시간이 중요합니다.

브라우저의 단일 스레드 특성을 이해하고 메인 스레드의 책임을 최소화함으로써 웹 성능을 향상시킬 수 있습니다.

가능하고 적절한 경우 렌더링이 원활하고 상호작용에 대한 즉각적인 응답을 보장합니다.

 

탐색

탐색은 웹페이지를 로드하는 첫 번째 단계입니다.

사용자가 주소 표시줄에 URL을 입력하고, 링크를 클릭하고, 양식을 제출하고, 기타 수행을 수행하여 페이지를 요청할 때마다 발생합니다.

 

웹 성능의 목표 중 하나는 탐색이 완료되는 데 걸리는 시간을 최소화하는 것입니다.

이상적인 조건에서 이 작업은 일반적으로 너무 오래걸리지 않지만 지연과 대역폭은 지연을 유발할 수 있는 적입니다.

 

DNS 조회

웹 페이지로 이동하는 첫 번째 단계는 해당 페이지의 리소스의 위치를 찾는 것입니다.

만약 당신이 https://example.com으로 이동한다면, HTML 페이지는 93.184.216.34 에 위치한 것입니다.

만약 이 사이트를 방문한 적이 없다면 DNS 조회가 이루어져야 합니다.

 

브라우저가 DNS 조회를 요청하면 최종적으로 네임 서버가 이 필드에 입력하고 IP 주소로 응답합니다.

이 초기 요청 후 IP가 잠시 캐시될 가능성이 높으며, 이는 DNS에 다시 연결하는 대신 캐시에서 IP 주소를 검색하여 후속 요청 속도를 향상시킵니다.

 

DNS 조회는 일반적으로 페이지 로드를 위해 호스트 이름당 한 번만 수행하면 됩니다.

그러나 요청된 페이지가 참조하는 각 고유 호스트 이름에 대해 DNS 검색을 수행해야 합니다.

글꼴, 이미지, 스크립트, 광고 및 메트릭의 호스트 이름이 모두 다른 경우 각 호스트에 대해 DNS 조회를 수행해야 합니다.

 

이는 특히 모바일 네트워크에서 성능에 문제가 될 수 있습니다.

사용자가 모바일 네트워크에 있을 때 각 DNS 조회는 권한 있는 DNS 서버에 도달하기 위해 전화기에서 Cell Tower로 이동해야 합니다.

전화, Cell Tower 및 DNS 사이의 거리는 상당한 지연시간을 유발할 수 있습니다.

 

TCP Handshake

IP주소를 알게되고 나면, 브라우저는 해당 IP 주소로 연결을 시도합니다.

이때 TCP 3-way handshake를 통해 연결합니다.

SYN, SYN-ACK, ACK를 통해 연결이 됩니다.

TLS Negotiation

HTTPS를 통해 보안 연결을 설정하려면 다른 "핸드쉐이크"가 필요합니다.

이 핸드셰이크 또는 TLS 협상은 통신을 암호화하는데 사용할 암호를 결정하고, 서버를 확인하며, 실제 데이터 전송을 시작하기 전에 보안 연결이 설정되어 있는지 확인합니다.

이렇게 하려면 내용 요청이 실제로 전송되기 전에 서버로 세번 더 왕복해야 합니다.

보안 연결을 설정하면 페이지 로드에 시간이 더 걸리지만, 보안 연결을 제 3자가 브라우저와 웹 서버 간에 전송된 데이터를 해독할 수 없기 때문에 지연 시간 비용만큼 가치가 있습니다.

8번의 왕복 여행 후, 브라우저는 마침내 요청을 할 수 있습니다.

Response

웹 서버에 대한 연결이 설정되면 브라우저가 사용자를 대신하여 초기 HTTP GET 요청을 보냅니다.

웹 사이트의 경우 대부분 HTML 파일입니다.

서버가 요청을 수신하면 관련 응답 헤더와 HTML의 내용으로 응답합니다.

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <title>My simple page</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="myscript.js"></script>
  </head>
  <body>
    <h1 class="heading">My Page</h1>
    <p>A paragraph with a <a href="https://example.com/about">link</a></p>
    <div>
      <img src="myimage.jpg" alt="image description" />
    </div>
    <script src="anotherscript.js"></script>
  </body>
</html>

이 초기 요청에 대한 응답은 수신된 첫 번째 데이터 바이트를 포함합니다.

Time to First Byte(TTFB)는 사용자가 링크를 클릭하여 요청을 한 시점과 HTML의 첫 번째 패킷을 수신한 시점 차이의 시간이다.

첫 번째 콘텐츠 청크는 일반적으로 14KB의 데이터입니다.

 

위의 예제에서 요청은 확실히 14KB 미만이지만 브라우저가 구문 분석 중에 링크를 만날 때까지 링크된 리소스는 요청되지 않았습니다.

TCP 슬로우 스타트 / 14KB 룰

첫 번째 응답 패킷은 14KB일 것입니다.

이것은 네트워크 연결 속도의 균형을 맞추는 알고리즘인 TCP 슬로우 스타트의 일부입니다.

슬로우 스타트는 네트워크의 최대 대역폭을 결정할 수 있을 때까지 전송되는 데이터의 양을 점진적으로 증가시킵니다.

 

TCP의 슬로우 스타트에서, 초기 패킷을 받은 후, 서버는 다음 패킷의 크기를 약 28KB로 두 배 늘립니다.

다음 패킷은 미리 결정된 임계값에 도달하거나 정체가 발생할 때까지 크기가 증가합니다.

초기 페이지 로드에 대한 14KB 규칙에 대해 들어본 적이 있다면 TCP 슬로우 스타트의 초기 응답이 14KB인 이유이며 웹 성능 최적화가 이 초기 14KB 응답을 염두에 두고 최적화에 집중해야하는 이유입니다.

TCP 슬로우 스타트는 정체를 피하기 위한 네트워크의 능력에 적합한 전송속도를 점진적으로 구축합니다.

혼잡 제어

서버가 TCP 패킷으로 데이터를 보낼 때 사용자의 클라이언트는 확인 응답 또는 ACK를 반환하여 전달을 확인합니다.

연결 용량은 하드웨어 및 네트워크 조건에 따라 제한됩니다.

서버가 너무 많은 패킷을 너무 빨리 보내면 패킷이 없어집니다.

즉, 받았다는 확인을 하지 못합니다.

그러면 서버에서는 ACK가 누락된 것으로 등록합니다.

혼잡 제어 알고리즘은 전송된 패킷과 ACK의 흐름을 사용하여 전송 속도를 결정합니다.

파싱

브라우저가 첫 번째 데이터 청크를 수신하면 수신된 정보를 구문 분석하기 시작할 수 있습니다.

파싱은 브라우저가 네트워크를 통해 수신하는 데이터를 DOM과 CSSOM으로 변환하는 단계로, 렌더러가 화면에 페이지를 그리는데 사용합니다.

 

DOM은 브라우저 마크업의 내부 표현입니다.

DOM은 노출되어 있으며, Javascript의 다양한 API를 통해 조작할 수 있습니다.

 

요청 페이지의 HTML이 초기 14KB 패킷보다 크더라도, 브라우저는 그것이 가지고 있는 데이터를 기반으로 경험을 해석하고 렌더링하기 시작할 것입니다.

이것이 웹 성능 최적화가 페이지 렌더링을 시작하기 위해 브랑루저가 필요한 모든 것을 포함하는 것이 중요한 이유입니다.

적어도 페이지의 템플릿(첫 번쨰 랜더링에 필요한 CSS 및 HTML)을 처음 14KB에 포함하는 것 입니다.

그러나 화면에 렌더링 하기전에 HTML, CSS 및 Javascript를 구문 분석해야 합니다.

DOM 트리 생성

렌더링 과정에서 중요한 5가지를 소개해보겠습니다.

 

첫번쨰 단계는 HTML 마크업을 처리하고 DOM 트리를 만드는 것입니다.

HTML 구문 분석은 토큰화와 트리 구성을 포함합니다.

HTML 토큰에는 시작 태그와 끝 태그, 속성 이름 값이 포함됩니다.

문서가 잘 구성되어 있으면 구문 분석이 간단하고 빠릅니다.

구문 분석기는 토큰화된 입력을 문서로 구문 분석하여 문서 트리를 작성합니다.

 

DOM 트리는 문서의 내용을 설명합니다.

<html> 요소는 문서 트리의 첫 번째 태그 및 루트 노드 입니다.

트리는 서로 다른 태그 간의 관계 및 계층을 반영합니다.

다른 태그 내에 중첩된 태그는 하위 노드 입니다.

DOM 노드의 수가 많을 수록 DOM 트리를 구성하는데 시간이 오래 걸립니다.

파서가 이미지와 같은 차단되지 않는 리소스를 찾으면 브라우저는 해당 리소스를 요청하고 구문 분석을 계속합니다.

구문 분석은 CSS 파일이 발견될 때에는 계속 진행될 수 있지만, <script> 태그(특히 비동기 또는 지연 속성이 없는 태그)는 렌더링을 차단하고 HTML의 구문 분석을 일시 중지한다.

브라우저의 프리로드 스캐너가 이 과정을 가속화하지만 과도한 스크립트는 여전히 상당한 병목 현상이 될 수 있습니다.

Preload scanner

브라우저가 DOM 트리를 만드는 동안, 이 프로세스는 메인 스레드를 차지합니다.

이와 같이 프리로드 스캐너는 이용 가능한 콘텐츠를 분석하고 CSS, Javascript, 웹 폰트와 같은 우선순위가 높은 리소스를 요청합니다.

프리로드 스캐너 덕분에 파서가 외부 리소스에 대한 참조를 찾을 때까지 기다릴 필요가 없습니다.

기본 HTML 파서가 요청된 자산에 도달할 때까지 이미 이동 중이거나 다운로드 되었을 수 있도록 백드라운드에서 리소스를 검색합니다.

프리로드 스캐너가 제공하는 최적화 기능은 막힘을 줄여줍니다.

<link rel="stylesheet" href="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description" />
<script src="anotherscript.js" async></script>

이 예에서 메인 스레드가 HTML과 CSS를 구문 분석하는 동안 프리로드 스캐너는 스크립트와 이미지를 찾아 다운로드를 시작합니다.

스크립트가 프로세스를 차단하지 않도록 하려면 Javascript 구문 분석 및 실행 순서가 중요한 경우 비동기 속성 또는 지연 속성을 추가하십시오.

 

CSS를 얻기 위해 기다리는 것은 HTML 구문 분석이나 다운로드를 차단하지 않지만 Javascript는 종종 CSS 속성이 이 요소에 미치는 영향을 쿼리하는 데 사용되기 때문에 Javascript를 차단합니다.

CSSOM 생성

렌더링 과정에서 중요한 두 번째는 CSS를 처리하고 CSSOM 트리를 만드는 것입니다.

CSS 객체 모델은 DOM과 유사합니다. DOM과 CSSOM은 둘 다 트리입니다.

두 트리는 독립적인 데이터 구조입니다.

브라우저는 CSS 규칙을 이해하고 작업할 수 있는 스타일의 맵으로 변환합니다.

브라우저는 CSS의 각 규칙 집합으로 CSS를 기반으로 부모, 자식, 형제 관계를 가진 노드 트리를 생성합니다.

 

HTML과 마찬가지로 브라우저는 수신된 CSS 규칙을 사용할 수 있는 것으로 변환해야 합니다.

따라서 CSS에 대해서는 HTML-to-object 프로세스를 반복합니다.

 

CSSOM 트리는 사용자 에이전트 스타일 시트의 스타일을 포함합니다.

브라우저는 노드에 적용할 수 있는 가장 일반적인 규칙으로 시작하고 보다 구체적인 규칙을 적용하여 계산된 스타일을 재귀적으로 세분화 합니다.

다시 말해, 그 것은 속성 값들을 상속합니다.

 

CSSOM을 구축하는 것은 매우 빠르며 현재 개발자 도구에서 고유한 색상으로 표시되지 않습니다.

오히려 개발자 도구의 "Recalculate Style"은 CSS를 분석하고 CSSOM 트리를 구성하고, 계산된 스타일을 재귀적으로 계산하는데 걸리는 총 시간을 보여줍니다.

웹 성능 최적화 측면에서, CSSOM을 만드는 총 시간은 일반적으로 하나의 DNS 탐색에 걸리는 시간보다 짧기 떄문에 더 낮은 열매가 된다.

다른 프로세스들

Javascript 컴파일

CSS가 구문 분석되고 CSSOM이 생성되는 동안 Javascript 파일을 포함한 다른 리소스들이 다운로드되고 있습니다. (프리로드 스캐너 덕분이다.)

Javascript는 해석, 컴파일, 구문 분석 및 실행 됩니다.

스크립트는 추상 구문 트리로 구문 분석 됩니다.

일부 브라우저 엔진은 추상 구문 트리를 가져와서 인터프리터로 전달하여 메인 스레드에서 실행되는 바이트 코드를 출력합니다.

이를 Javascript 컴파일이라고 합니다.

접근성 트리 생성

브라우저는 또한 보조 장치가 콘텐츠를 분석하고 해석하는데 사용하는 접근성 트리를 구축합니다.

접근성 객체 모델(AOM)은 DOM의 의미론적 버전과 같습니다.

브라우저는 DOM이 업데이트되면 내게 필요한 옵션 트리를 업데이트 합니다.

접근성 트리는 보조 기술 자체로는 수정할 수 없습니다.

 

AOM이 구축될 때까지 스크린 리더(글씨 낭독 소프트웨어)에서 콘텐츠에 액세스할 수 없습니다.

렌더

렌더링 과정에는 스타일, 레이아웃, 페인트, 경우에 따라 합성이 포함됩니다.

파싱 단계에서 생성된 CSSOM 및 DOM 트리는 렌더링 트리로 결합된 다음 모든 가시적 요소의 레이아웃을 계산하는데 사용되며 화면에 그려집니다. 경우에 따라 콘텐츠를 자체 레이어로 승격하고 합성하여 CPU 대신 GPU에 화면 일부를 페인팅하여 성능을 향상시키고 주 스레드를 자유롭게할 수 있습니다.

스타일

렌더링의 중요한 3번째 단계는 DOM과 CSSOM을 렌더 트리로 결합하는 것입니다.

계산된 스타일 트리 또는 렌더 트리 구성은 DOM 트리의 루트에서 시작하여 각 표시 노드들을 통과합니다.

 

표시되지 않을 태그(예: 사용자 에이전트 스타일시트에서 찾을 스크립트 {display:none;})은 렌더링된 출력에 표시되지 않으므로 렌더링 트리에 포함되지 않습니다. {visiablity: hidden}이 적용된 노드는 공간을 차지하므로 렌더 트리에 포함됩니다.

사용자 에이전트 기본 값을 재정의하는 명령을 제공하지 않았기 때문에 위의 코드 예제에 있는 스크립트 노드는 렌더 트리에 포함되지 ㅇ낳습니다.

 

보이는 각 노드에는 CSSOM 규칙이 적용됩니다.

렌더 트리는 콘텐츠와 계산된 스타일을 가진 모든 보이는 노드를 보유하고 있습니다.

모든 관련된 스타일을 DOM 트리의 모든 보이는 노드에 일치시키고 CSS 상속에 기초하여 각 노드에 계산된 스타일이 무엇인지 결정합니다.

레이아웃

렌더링의 중요한 4번째 단계는 렌더 트리에서 레이아웃을 실행하여 각 노드의 지오메트리를 계산하는 것입니다.

레이아웃은 렌더 트리에 있는 모든 노드의 너비, 높이 및 위치를 결정하는 프로세스이며, 여기에 페이지에 있는 각 개체의 크기 및 위치를 결정합니다.

리플로우는 페이지 또는 전체 문서의 후속 크기 및 위치 결정입니다.

 

렌더 트리가 작성되면 레이아웃이 시작됩니다.

렌더 트리는 계산 된 스타일과 함께 표시되는 노드 (비록 보이지 않더라도)를  식별했지만 각 노드의 치수 또는 위치는 식별하지 못했습니다. 각 객체의 정확한 크기와 위치를 결정하기 위해 브라우저는 렌더 트리의 루트에서 시작하여 이를 가로지릅니다.

 

웹 페이지에서 거의 모든 것이 상자입니다.

장치가 다르고 데스크톱 환경 설정이 다르다는 것은 뷰포트 크기가 무제한 이라는 것을 의미합니다.

이 단계에서는 뷰포트 크기를 고려하여 브라우저가 화면에 표시될 모든 상자의 치수를 결정합니다.

뷰포트의 크기를 기준으로, 레이아웃은 일반적으로 신체의 모든 후손의 치수를 배치하고, 각 요소의 상자 모델 속성을 사용하여, 우리의 이미지와 같이 치수를 알지 못하는 교체된 요소를 위한 placeholder를 제공합니다.

 

노드의 크기와 위치가 결정되는 첫 번째 시간을 레이아웃이라고 합니다.

노드 크기 및 위치의 후속 재 계산을 리플로우라고 합니다.

예제에서 이미지가 반환되기 전에 초기 레이아웃이 발생한다고 가정합니다.

이미지 크기를 선언하지 않았기 때문에 이미지 크기가 알려지면 리플로우가 발생합니다.

페인트

렌더링의 마지막 중요 부분은 개별 노드를 화면에 페인팅 하는 것입니다.

첫 번째 단계는 의미 있는 페인팅하는 것입니다.

페인팅 또는 래스터라이제이션 단계에서 브라우저는 레이아웃 단계에서 계산된 각 상자를 화면의 실제 픽셀로 변환합니다.

그림 그리기에는 텍스트, 색, 테두리, 그림자 및 단추 및 이미지와 같은 대체된 요소를 포함하여 요소의 모든 시각적 부분을 화면에 그리는 작업이 포함됩니다. 브라우저는 이 작업을 매우 빠르게 수행해야 합니다.

 

부드러운 스크롤과 애니메이션을 보장하기 위해 리플로우 및 페인트와 함께 스타일 계산을 포함하여 주 스레드를 차지하는 모든 작업은 16.67ms 미만의 브라우저를 사용해야합니다. 2047 X 1536에서 iPad는 3,145,000 픽셀 이상을 화면에 칠해야 합니다.

그것은 매우 빨리 칠해져야하는 많은 픽셀들입니다.

초기 페인트보다 훨씬 더 빨리 다시 칠할 수 있도록 하기 위해 일반적으로 화면에 그려지는 그림은 여러 레이러로 나뉩니다.

이런일이 발생하면 컴포지팅이 필요합니다.

 

그림을 그리면 레이아웃 트리의 요소를 레이어로 분할할 수 있습니다.

CPU의 메인 스레드 대신 GPU의 레이어로 콘텐츠를 승격하면 재 페인팅 성능이 향상됩니다.

<video>와 <canvas>를 포함하여 계층을 인스턴스화 하는 특정 속성과 요소가 있으며, 불투명도, 3D 변환, 의지 변화 등의 CSS 속성을 가진 요소가 있다. 이러한 노드들은 후손이 위의 이유 중 하나 또는 그 이상의 이유로 자신의 계층을 필요하지 않는 한 후손과 함께 자신의 계층에 그려진다.

 

계층은 성능을 향상시키지만 메모리 관리에 있어서는 비용이 많이 들기 때문에 웹 성능 최적화 전략의 일부로 과도하게 사용되어서는 안 됩니다.

컴포지팅

문서의 섹션이 서로 겹쳐서 서로 다른 레이어로 그려지는 경우, 올바른 순서로 화면에 그려지고 내용이 올바르게 렌더링 되도록 구현해야합니다.

페이지에서 리소스를 계속 로드하면 리플로우가 발생할 수 있습니다(늦게 도착한 예제 이미지를 기억하십시오).

리플로우는 재 페인팅을 유발합니다.

이미지의 크기를 정의했다면 리플로우가 필요하지 않았을 것이며, 다시 칠해야 하는 레이어만 다시 칠하고 필요한 경우 합성할 수 있습니다.

하지만 우리는 이미지 크기를 포함하지 않았습니다!

서버에서 이미지를 가져오면 렌더링 프로세스가 레이아웃 단계로 돌아가서 다시 시작합니다.

상호작용성

일단 메인 스레드가 페이지를 칠하고 나면, 당신은 우리가 "모든 것이 준비되었다"고 생각할 것입니다.

꼭 그렇지는 않습니다.

Load에 Javascript가 포함되어 있고 올바르게 지연되고 onLoad 이벤트가 발생한 후에만 실행되는 경우 주 스레드가 사용중이며 스크롤, 터치 및 기타 상호 작용에 사용할 수 없습니다.

 

TTI(Time to Interactive)는 DNS 조회 및 SSL 연결을 초래한 첫 번째 요청에서 페이지가 대화형일 때까지 시간을 측정한 것입니다.

즉 대화형은 페이지가 50ms 내에 사용자 상호 작용에 응답할 때 첫 번째 내용물 그림판 이후의 시점입니다.

만약 메인 스레드가 자바스크립트를 분석, 컴파일, 실행하는데 사용된다면, 그것은 사용할 수 없기 때문에 적시에 (50ms 미만의) 사용자 상호작용에 응답할 수 없습니다.

 

우리의 예제에서는 이미지가 빠르게 로드되었지만 anotherscript.js 파일은 2MB 였고 사용자의 네트워크 연결이 느렸습니다.

이 경우 사용자는 페이지를 매우 빨리 볼 수 있지만 스크립트를 다운로드, 구문 분석 및 실행할 때까지 jank 없이 스크롤할 수 없습니다.

그것은 좋은 사용자 경험이 아닙니다.

이 WebPageTest 예제에서 보여지는 것처럼 메인 스레드를 차지하지 마십시오.

이 예제에서 DOM 콘텐츠 로드 프로세스는 1.5초 이상 걸렸고, 메인 스레드는 클릭 이벤트나 화면 탭에 응답하지 않고 전체 시간 동안 완전히 사용되었습니다.

 

출처 : https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work

댓글