
2.5초
2023년 tooltester의 웹사이트 로딩 시간 통계에 따르면 데스크탑에서 평균 웹 페이지 로드 시간은 2.5초이다. 이말은 즉 사용자가 브라우저를 열고 검색창 또는 주소창에 무엇인가 입력한 뒤 브라우저가 해당 결과를 표시하기 까지 걸리는 시간은 평균적으로 2.5초이다. 어떤 원리와 동작에 의해 웹페이지가 처리되는지 알아보자.
1. 네임서버 요청
사용자는 원하는 웹페이지를 보기 위해 브라우저가 제공하는 주소창에 주소를 입력한다. 이때 브라우저 엔진은 입력받은 주소를 HTTP통신을 통해 네임서버로 요청한다. 네트워크 상에서 컴퓨터들이 서로를 구별하고 통신하는 IP주소 대신 쉬운 문자를 이용하여 DNS를 운영하는 네임서버로 부터 실제 IP주소를 응답 받는다. 해당 결과는 복잡하고 빠른 처리를 위해 웹 브라우저 사이의 서로 다른 계층과 인터넷의 다양한 위치에 임시로 저장된다. 이를 캐시(Cache)라고 부며, 웹 브라우저의 고유한 캐시, 운영 체제 캐시, 라우터의 로컬 네트워크 캐시, 회사 네트워크 또는 인터넷 서비스 제공업체(ISP)의 DNS 서버 캐시 등이 있다.
2. 웹서버 요청
인터넷에 연결된 웹브라우저는 응답 받은 IP주소를 호스팅하고 있는 웹서버를 찾으면 TCP 연결을 설정하고, 페이지의 콘텐츠를 요청하기 위해 서버에 HTTP 요청을 전송한다. 이 과정에서 위치에 따라 웹서버에 직접 연결하기 보다 콘텐츠 전송 네트워크(CDN)를 사용하여 정적 및 동적 콘텐츠에 접근 하는 방식도 있다. 동영상이나 음악 파일 같은 것은 멀리 외국에 있는 웹 서버에서 제공하기 보다, 사용하는 인터넷 서비스 제공자들의 데이터 센터에 있는 콘텐츠 배포 서버에 있는 것이 훨씬 유리하다. 즉 CDN은 콘텐츠를 사용자에게 더 가까이 제공하여 사이트의 원본 연결 성능을 개선하는 캐싱 서버의 글로벌 분산 네트워크이다.
3. 웹서버 응답
웹서버와 웹브라우저는 각각 4개의 계층을 순차적으로 통과하면서 각각의 프로토콜에 따라 데이터를 처리하고, 네트워크를 연결하여 인터넷을 만드는 중간 노드인 라우터는 네트워크 인터페이스 계층과 인터넷 계층의 프로토콜에 따라 데이터를 처리하며 데이터를 송수신 한다. 웹서버는 브라우저에게 웹 페이지라는 데이터를 제공하기 위해 각 계층들이 순차적으로 데이터를 처리하고 인터넷으로 내보내는데, 각 계층을 지날 때마다 해당 계층의 프로토콜을 처리하고 상위 계층으로부터 받은 데이터에 헤더를 추가한다. 송신 호스트(웹서버)와 같은 계층의 수신 호스트(웹브라우저)의 프토로콜을 위한 정보가 헤더이고, 송신 호스트의 다음 하위 계층으로 전달하는 정보가 데이터이다. 상위 계층으로 부터 받은 데이터에 해당 프로토콜의 기능을 실현하기 위한 정보인 헤더를 추가하는 것을 캡슐화라고 한다. 송신 호스트가 만든 데이터는 4개의 계층을 통과하는 동안 여러 헤더가 추가되면서 캡슐화 된 후 최하위 계층에서 전기 신호로 변환하여 전송 매채를 통해 라우터 등의 중간 노드를 거쳐 수신 호스트의 최하위 계층인 네트워크 인터페이스 계층으로 전송된다.
4. 웹브라우저 수신
전송 매체 즉, 인터넷을 통해 웹브라우저에 도착한 전기 신호는 네트워크 인터페이스 계층에서 다시 디지털 데이터로 변환된다. 웹서버와는 반대로 하위 계층에서부터 해당 계층의 프로토콜을 처리한 후 헤더 부분을 제거한 데이터를 상위 계층으로 보낸다. 최상위 계층인 응용 계층에 도착했을 때 프로토콜에서 사용하고 제거하기 때문에 최종적으로 웹서버의 응용계층에서 보낸 데이터만 남게된다. 웹브라우저 각 프로토콜의 이러한 흐름을 역캡슐화 라고 한다.
5. 웹브라우저 렌더링
웹브라우저의 브라우저 엔진이 웹서버로부터 데이터를 전달 받고 사용자가 원하는 웹페이지의 데이터를 수신받았다. 수신한 데이터는 렌더링 엔진으로 전달되어 화면에 직접 그리기 위한 작업이 이뤄진다. 다양한 웹브라우저가 존재하고 각각 브라우저에서 사용하는 렌더링 엔진도 다양하지만 기본적인 동작 과정들은 비슷하다.
렌더링 엔진은 HTML문서를 파싱하고 DOM노드로 변환하며 CSS파일의 스타일 요소도 파싱한다 -> 파싱된 정보는 렌더 트리를 생성하고 이는 정해진 순서대로 화면에 표시된다 -> 렌더 트리의 생성이 끝나면 각 노드가 화면의 정확한 위치에 표시되는 것을 의미하는 배치가 시작된다 -> 배치가 완료되면 UI 백엔드에서 렌더 트리의 각 노드를 가로지르며 형상을 만들어 내는 그리기 과정이 이뤄지고 사용자가 보게되는 화면이 출력된다.
파싱
렌더링 엔진은 웹서버로부터 받은 데이터 문서를 파싱하는데 이는 브라우저가 이해하고 사용할 수 있는 구조로 변환하는 것이다. 파싱의 결과로 보통 문서의 구조를 나타내는 노드트리를 생성하는데 이를 파싱 트리 또는 문법 트리라고 부른다. 파서가 생성되면 문서 객체가 생성되고, 트리 구축이 진행되는 동안 문서 최상단에서는 DOM트리가 수정되고 요소가 추가된다. HTML 파싱 중 script 태그를 만나면 javascript를 요청하게 되는데, 코드를 해석하고 실행 완료가 될 때까지 DOM 생성을 멈춘다. javascript 실행 중 DOM을 바꾸는 코드가 있을 수 있기 때문이다. 따라서 javascript의 크기에 따라 DOM 생성이 지연되고 성능에 악영향을 끼칠 수 있기 때문에 최적화를 통해 웹 페이지 로딩 완료 후 실행되도록 지연시킬 수도 있다. 문서에 작성된 언어 또는 형식의 규칙을 따르며 모든 형식은 정해진 용어와 구문 규칙에 따라야 한다. 이것을 문맥 자유 문법이라고 한다. 파싱은 자료를 토큰으로 분해하는 과정인 어휘 분석과 언어의 구문 규칙을 적용하는 과정인 구문 분석 두 가지로 구분된다. 파서는 보통 어휘 분석기로부터 새 토큰을 받아 구문 규칙과 일치하는지 확인하고 규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청하는 토큰화 과정이 반복된다. 규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 일치하는 규칙이 발견될 때까지 요청한다.
트리 구축
파싱과 함께 DOM 트리가 구축되고 파싱한 정보로 브라우저는 렌더 트리를 구축한다. 화면에 표시해야할 순서와 문서의 시각적인 구성 요소로써 올바른 순서로 내용을 그려낼 수 있도록 하기 위한 목적이다. 렌더러(구성요소)는 DOM 요소에 부합하지만 1:1로 대응하는 관계는 아니다. “head”와 같은 비시각적 DOM 요소는 렌더 트리에 추가되지 않으며 display 속성에 “none” 값이 할당된 요소는 트리에 나타나지 않는다. 모든 DOM 노드에는 스타일을 결정하고 렌더러를 만드는 “attach” 메서드가 있다. 이는 동기적이며 DOM 트리에 노드를 추가하면 새 노드의 “attach” 메서드를 호출한다. html 태그와 body 태그를 처리함으로써 렌더 트리 루트를 구성한다. 렌더 트리를 구축하려면 각 렌더 객체의 시각적 속성에 대한 계산이 필요한데 이것은 각 요소의 스타일 속성을 계산함으로써 처리된다.
배치
렌더러가 생성되어 트리에 추가될 때 크기와 위치 정보는 없는데 이런 값을 계산하는 것을 배치 또는 리플로라고 부른다. HTML은 흐름 기반의 배치 모델을 사용하는데 이것은 보통 단일 경로를 통해 크기와 위치 정보를 계산할 수 있다는 것을 의미한다. 일반적으로 “흐름 속”에서 나중에 등장하는 요소는 앞서 등장한 요소의 위치와 크기에 영향을 미치지 않기 때문에 배치는 왼쪽에서 오른쪽으로, 또는 위에서 아래로 흐른다. 좌표계는 기준점으로부터 상대적으로 위치를 결정하는데 좌단(X축)과 상단(Y축) 좌표를 사용한다. 배치는 반복되며 HTML 문서의 html 요소에 해당하는 최상위 렌더러에서 시작된다. 최상위 렌더러의 위치는 0,0이고 브라우저 창의 보이는 영역에 해당하는 뷰포트 만큼의 면적을 갖는다. 렌더러는 다시 배치할 필요가 있는 변경 요소 또는 추가된 것과 그 자식을 “더티” 라고 표시하며 소소한 변경 때문에 전체를 다시 배치하지 않기 위해 브라우저는 “더티 비트” 체제를 사용한다. 배치는 실제로 다음과 같은 형태로 진행된다. 부모 렌더러가 자신의 너비 결정 -> 자식 렌더러 배치(자식의 x와 y설정) -> 필요하다면 자식 배치를 호출하여 자식의 높이 계산 -> 부모는 자식의 누적된 높이와 여백, 패딩을 사용하여 자신의 높이 설정. 부모 렌더러의 부모가 사용 -> 더티 비트 플래그 제거 렌더러의 너비는 포함하는 블록의 너비, 그리고 렌더러의 너비와 여백, 테두리를 이용하여 계산된다.
그리기
화면에 내용을 표시하기 위한 렌더 트리가 탐색되고 렌더러의 “paint” 메서드가 호출된다. 그리기는 UI 기반의 구성요소를 사용하며 배치와 마찬가지로 전역 또는 점증 방식으로 수행된다. 점증 그리기에서 일부 렌더러는 전체 트리에 영향을 주지 않는 방식으로 변경된다. 브라우저는 배치 단계에서 계산된 각 박스를 실제 화면의 픽셀로 변환한다. 텍스트, 색, 테두리, 그림자 및 버튼이나 이미지 같은 대체 요소를 포함하여 모든 요소의 시각적인 부분을 화면에 그리는 작업이 실행된다. 첫 페인팅보다 다시 페인팅하는 것이 더 빠르게 마무리되기 위해서, 화면에 그리는 작업은 일반적으로 몇 개의 레이어로 구분된다. 이로 인해 문서의 각 섹션이 다른 레이어에서 그려질 때, 섹션을 겹쳐놓으면서 그것들이 올바른 순서로 화면에 그려지는 것과 정확한 렌더링을 보장하기 위해 합성이 필요하다.
6. 상호작용
렌더링 엔진이 페이지 그리는 것을 완료하면 사용자가 원하는 페이지를 볼 수 있게된다. 하지만 지연된 javascript 다운 또는 onload 이벤트가 남아있을 수 있고 이를 자바스크립트 엔진이 처리한다. 마지막 과정이 끝난 후에야 스크롤이나 터치와 같은 상호작용이 가능하며 웹페이지를 사용할 수 있다. 사용자의 마우스 커서 이동, 버튼 클릭 또는 스크롤 등의 입력에 따라 동적으로 컨텐츠를 바꾸고, 멀티미디어를 제어하고 애니메이션을 추가하는 등 화면을 업데이트하는 작업이 javascript에 의해 실행된다. 화면의 내용이나 위치가 변경되는 경우 DOM을 조작하여 전체 렌더 트리 또는 필요한 부분의 렌더트리만을 다시 생성하여 트리구축 부터 그리기의 과정이 재실행된다.
참고자료
- tooltester의 웹사이트 로딩 시간 통계
https://www.tooltester.com/en/blog/website-loading-time-statistics/#top100 - Amazon Web Services 한국 블로그
https://aws.amazon.com/ko/blogs/korea/what-happens-when-you-type-a-url-into-your-browser/ - 쉽게 이해하는 네트워크 8.인터넷의 TCP/IP 데이터 전송 과정(ft. 캡슐화와 역캡슐화) - 냐옹아 멍멍해봐(How to Speak IT)
https://better-together.tistory.com/89 - 브라우저는 어떻게 동작하는가? - NAVER D2
https://d2.naver.com/helloworld/59361 - 웹 페이지 로딩 과정 이해하기 - IMQA Blog 김소희
https://blog.imqa.io/webpage_loading_process/