본문 바로가기

Web

"웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가"

 

https://developer.mozilla.org/ko/docs/Web/Performance/How_browsers_work#%EB%A0%8C%EB%8D%94render

 

웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가 - 웹 성능 | MDN

사용자는 로드가 빠르고 상호작용이 원활한 컨텐츠로 이루어진 웹 경험을 원합니다. 따라서 개발자는 이 두 가지 목표를 달성하기 위해서 부단히 노력해야합니다.

developer.mozilla.org

브라우저 렌더링 과정을 찾아보다 해당 문서를 읽게 되었다. 웹페이지를 표시하기까지 브라우저의 작동 방식이 상세하게 설명되어, 해당 문서를 읽고 정리해보고자 한다.

 

브라우저: 대부분 싱글 쓰레드로 동작

즉 한번에 하나의 작업만 처리하기 때문에, 메인 쓰레드의 부담을 줄이는 것이 브라우저 성능 개선에 있어서 관건이다.

프로세스: 운영체제에서 실행 중인 프로그램 ex) 하나의 프로그램(게임, 브라우저)
쓰레드: 그 프로그램 내에서 실제로 작업을 수행하는 실행 흐름 ex) 그 프로그램 안에서 실행되는 세부 작업 (탭 여는 쓰레드, 페이지 로딩 쓰레드, 비디오 재생 쓰레드 등등)

✅ 쓰레드 특징
- 같은 프로세스 내의 다른 쓰레드와 코드, 데이터, 힙 메모리 공유
- 쓰레드마다 독립적인 스택 메모리, 레지스터
- 멀티 쓰레딩 가능
- 쓰레드 간의 컨텍스트 스위칭(작업전환)이 프로세싱보다 빠름
- 메모리를 공유하기 때문에 자원 접근 시 동기화 문제 가능성

 

웹페이지 표시: 브라우저는 어떻게 동작하는가?

 

1. 탐색(Navigation)

1) DNS 조회

탐색의 첫 단계는 해당 페이지의 자원이 어디에 위치하는 가, 즉 IP주소가 무엇인가를 아는 것이다.

만약 처음 방문한 페이지라면, DNS 조회를 한다

 

DNS (Domain Name System Server) 도메인 이름 시스템
- DNS의 이름 서버(특정 도메인의 IP주소를 보관하고 응답하는 역할)로부터 응답받은 IP주소를 한동안 캐시한다.
따라서, 이후에는 이름서버에 다시 요청하지 않고
캐시에서 IP주소를 검색하여 후속 요청 시간을 줄인다.
- 보통 호스트 하나 당 한 번만 수행.
if 글꼴, 이미지, 스크립트, 광고 등이 모두 다른 호스트에서 왔다면 각자 DNS 조회 수행

 

2) TCP 핸드셰이크

IP 주소를 알았다면, 브라우저는 서버와  TCP 3 방향 핸드셰이크를 통해 연결 설정

 

- TCP(Transmission Control Protocol) 3방향 핸드셰이크

  • 브라우저와 서버가 안정적인 연결을 만들기 위해 3단계 과정을 거치는 것
  • 3단계 과정
    1) 클라이언트 -> 서버 SYN(Synchronize): 시작신호
    클라이언트가 서버에 연결 요청
    패킷에 SYN 플래그 설정해 보냄

    2) 서버 -> 클라이언트 SYN-ACK(Synchronize + Acknowledge): 응답 및 동기화 신호
    서버가 요청받고 응답
    패킷에 SYN플래그와 ACK플래그 설정해 보냄.

    3) 클라이언트 -> 서버 ACK(Acknowledge)
    서버 응답 확인 뒤 다시 응답
    패킷에 ACK플래그 설정해 보내기

3) TLS 협상(Transport Layer Security)

- 클라이언트와 서버가 보안 연결을 설정하는 과정

- 주로 다음을 실행

  • 서로 인증(서버인증은 필수, 클라이언트 인증은 필수적)
  • 암호화 방식 협의
  • 공유 비밀키 생성

- 주요 단계

1. 클라이언트 헬로

  • 클라이언트가 서버에 연결 요청
  • 클라이언트가 지원하는 TLS버전(TLS1.2/ TLS1.3),
    지원하는 암호화 알고리즘 목록 등의  정보와 함께 요청

2. 서버 헬로

  • 서버가 응답
  • 사용한 TLS 버전, 암호화 알고리즘, SSL/TLS 인증서 제공(서버의 신원을 인증)(여기서 인증서 검증 과정 발생)

3. 키 교환 및 비밀키 생성

  • TLS 버전에 따라 방식 다름

4. 핸드셰이크 완료&세션 암호화 시작

  • 클라이언트와 서버는 최종석으로 "세션 키"를 공유하게 됨
  • 마지막으로 Finished 메세지 주고 받기
  • 이후부터 세션키를 사용한 대칭키 암호화로 데이터 주고받음.

* 연결에 보안성을 더하는 것은 페이지 로딩을 더디게 하지만 그만한 가치가 있음

 

2. 응답

웹 서버로 한번 연결이 되고 나면, 브라우저는 유저 대신 HTTP request를 보낸다.

-> 대개 HTML 파일 요청 -> 응답 헤더와 함께 HTML 파일 보냄

 

혼잡제어/ TCP 슬로우 스타터
- TCP는 데이터를 한꺼번에 보내지 않고 조각(세그먼트)로 보냄
- 서버가 너무 많이 보내면 네트워크 과부하
너무 적게 보내면 속도 저하
- TCP 슬로우 스타터는 처음엔 적게 보내고, 점점 늘려가며 최적의 속도를 찾음
- 만일 네트워크가 감당 못하면 자동으로 속도 줄여서 과부하 방지

 

3. 구문 분석(Parsing)

- 브라우저가 받은 HTML, CSS, JavaScript 코드를 이해하고 화면에 표시할 준비를 하는 것

1) HTML->DOM 생성

2) CSS -> CSSOM 생성

3) JavaScript -> 필요한 경우 DOM/CSSOM을 변경

이 과정이 끝나야 실제로 화면에 콘텐츠가 보이기 시작

 

- TTFB(Time To First Byte)

웹페이지를 클릭하면 서버에 요청하고, 서버가 응답을 주기까지 시간이 걸림.

 

"TTFB란?"

요청을 보낸 순간부터 첫 번째 데이터(HTML 조각)을 받을 때까지 걸린 시간

 

즉, 서버가 얼마나 빨리 응답하는지를 측정하는 중요한 지표

  • TTFB가 길면, 웹사이트가 느려보일 수 있음.
  • TTFB가 짧으면, 빠르게 페이지 로딩이 시작됨.

- 브라우저는 첫 14KB 데이터를 받으면 바로 렌더링 시작!

  • 서버에서 HTML 파일을 보낼때, 처음에는 14KB정도의 작은 청크(조각)을 보낸다.
  • 이 작은 데이터를 받으면, 브라우저는 "아, 이제 페이지를 그려야겠구나"하면서 화면 준비
  • HTML이 안 와도, 받은 만큼 먼저 처리(구문분석)하면서 렌더링을 시작할 수 있음.

즉, 웹페이지 로딩을 빠르게 하려면

중요한 HTML, CSS를 이 첫 14KB 안에 넣어야함! (페이지 템플릿, 주요 스타일 등)

 

- DOM 트리 구축

HTML을 토큰화해서 분석후, DOM 트리 생성.

DOM 노드의 개수가 많을 수록 DOM 트리를 만드는데 시간이 오래 걸림

 

JavaScript(<script>)를 만나게되면 HTML 분석을 멈추고, 다운로드&실행 후 분석 재개.

JavaScript는 HTML을 수정할 수도 있기 때문!

 

 

✅ 해결책 => async & defer

 

  • async

    - 스크립트를 백그라운드에서 다운받으면서 HTML 파싱
    - 다운로드가 끝나는 순간 HTML 파싱이 멈추고, 스크립트 실행
      -> HTML 문서가 완전히 다운로드되지 않은 상태에서 스크립트 실행 가능성 있음
    - 여러개의 async 스크립트는 순서가 보장되지 않음. 먼저 다운로드된 순서대로 실행
       -> 방문자수 카운터, 광고 스크립트처럼 독립적인 스크립트, 혹은 실행순서가 중요하지 않은 경우 유용
    <script src="app.js" async></script>
  • defer

    -  async와 마찬가지로 HTML 파싱 도중에 스크립트를 백그라운드에서 다운하고,
    HTML 파싱이 끝난 후 스크립트 실행(DOMContentLoaded 이벤트 발생 전에 실행)
    - 여러개의 defer 스크립트는 문서에 작성된 순서대로 실행
    - DOM 전체가 필요한 스크립트나, 실행순서가 중요한 경우 유용
    <script src="app.js" defer></script>

 

- 프리로드 스캐너(Preload Scanner)

 

프리로드 스캐너는 HTML을 다 분석하기 전에 미리 중요한 자원들(이미지, CSS, JS)을 찾아서 다운로드 함

즉, 프리로드 스캐너는 HTML 구문 분석과는 별도로 "필요한 자원을 미리 찾아서" 다운로드하는 역할을 함.

메인 쓰레드가 HTML과 CSS를 분석하고 있을 때, 프리로드 스캐너는 스크립트와 이미지를 찾아 다운로드하기 시작할 것입니다. Javascript의 분석과 실행 순서가 중요하지 않고 스크립트가 프로세스를 막지 않도록 하려면 async 속성이나 defer 속성을 추가하세요.

 

문서에서 이 부분이 이해가 안 됐다.

async와 defer가 미리 JS 파일을 다운한다고 했는데, 그러면 프리로드 스캐너와 역할에 큰 차이가 없는 것 아닌가?

 

그에 대한 답변은 GPT를 참고했다.

  • 브라우저가 HTML을 읽으면서 <script> 태그를 만나야 원래는 JS 다운로드를 시작할 수 있음.
  • 하지만 프리로드 스캐너는 HTML을 다 읽기 전에 <script>를 찾아서 미리 다운로드 요청을 보냄!
  • 즉, async나 defer가 있어도 "다운로드 시작을 앞당기기 위해" 프리로드 스캐너가 필요함.

-> 그러니깐 프리로드 스캐너의 역할은 다운로드를 미리 요청하는 것이었다.

따라서 async/defer와 함께 쓰면 다운로드를 더 빨리 시작할 수 있다.

 

-> async/defer가 없다면, 어차피 스크립트가 다운로드시 HTML 파싱이 멈추기 때문에 프리로드 스캐너가 큰 의미가 없어진다. 따라서 스크립트 다운으로 인한 블로킹 현상을 막고싶다면 async/defer를 사용하도록 하자.

 

- CSSOM 구축

 

- JavaScript 컴파일

 

- 접근성 트리 구축

 

 

 

 

 

참고)

async/defer: https://ko.javascript.info/script-async-defer

 

+)이어서 추가 예정