웹에 날개를 달아주는 웹 성능 최적화 기법 Chapter2

웹에 날개를 달아주는 웹 성능 최적화 기법

  • 본 책을 읽고 책의 내용을 간략하게 정리한 글입니다.

Chapter 2. 웹 최적화

웹 최적화란

  • 웹 최적화란 웹 성능을 구현하기 위해 최고의 조건을 만드는 다양한 노력을 의미
프론트엔드 최적화
  • 웹 UI/UX와 관련된 최적화
    • HTML, CSS, 자바스크립트, 이미지 파일, 타사 파일 또는 이들과 어우러져 있는 컨텐츠를 만들 때 최적화 진행
  • 프론트엔드 최적화가 잘 되어 있는 웹 사이트는 브라우저에서 컨텐츠를 다운로드, 로딩, 렌더링할 때 속도가 빨라지는 효과가 있음
  • 프론트엔드 최적화를 하는 대표적인 기술
    1. 스크립트를 병합하여 브라우저의 호출 개수를 줄임
    2. 스크립트 크기를 최소화하여 byte 자체를 줄임
    3. 스크립트를 gzip 등으로 압축하여 전달
    4. WebP 등으로 브라우저 이미지 형식 최적화
    5. 이미지 손실, 무손실 압축
    6. Cache-Control 응답 헤더를 통해 브라우저 캐시 사용
    7. 도메인 수를 줄여 DNS 조회 최소화
    8. DNS 정보 미리 읽어오기
    9. CSS를 HTML 상단에, 자바스크립트를 HTML의 하단에 위치
    10. 페이지 미리 읽어오기 (page prefetching)
    11. 타사 스크립트가 웹 성능을 방해하지 않도록 조정
백엔드 최적화
  • 웹 UI를 로직에 맞게 만드는 최적화
  • 웹 서버, 웹 애플리케이션 서버, 데이터베이스, 로드 밸런싱, DNS 서버 등이 대표적인 백엔드
  • 백엔드 최적화는 프론트엔드에 비해 가시적인 효과가 크지 않지만 네트워크를 정상적으로 사용하고 컨텐츠를 전달하기 위해 반드시 필요한 요소
  • 백엔드 최적화를 하는 대표적인 방법
    1. DNS 응답이 빨라지도록 서버 증설
    2. DNS 응답을 빠르게 할 수 있도록 DNS 정보를 최대한 캐싱
    3. 웹 서버가 있는 데이터 센터의 네트워크 출력 (throughput) / 대역폭 (bandwidth) 증설
    4. 웹 서버, 웹 애플리케이션 서버의 CPU/RAM 증설
    5. 프록시 서버를 설정하여 웹 컨텐츠 캐싱
    6. CDN(Content Delivery Network) 사용하여 인터넷상에 컨텐츠 캐싱
    7. 데이터베이스 정규화로 디스크 I/O 최적화
    8. 데이터베이스 캐싱으로 응답 빠르게
    9. 로드 밸런싱을 통해 가장 성능이 좋은 웹 서버로 요청 연결
    10. 웹 애플리케이션 로직을 가볍고 빠르게 개발
프로토콜 최적화
  • 웹 컨텐츠를 전달하는 HTTP/HTTPS 프로토콜 자체의 효과를 극대화하면 웹 서버가 클라이언트에게 컨텐츠를 최대 속도와 최저 지연 시간으로 전달 가능
  • 웹 컨텐츠를 더 빠르게 요청하고 응답하도록 프로토콜을 업그레이드하는 과정

TCP/IP 프로토콜

  • 웹에서는 TCP/IP 프로토콜의 일종인 HTTP를 사용
  • OSI 7 계층 모델에서 TCP는 4번째인 전송 계층에 속하고 HTTP는 7번째인 응용 계층에 속함
  • TCP 네트워크를 사용하는 네트워크에 있어 대표적인 성능 지표는 대역폭과 지연 시간
    • 대역폭 : 특정 시간 동안 얼마나 많은 네트워크 트래픽을 보낼 수 있는지 (시간당 전송량)
    • 지연 시간 : 클라이언트와 서버 간 컨텐츠를 전달하는 물리적인 시간
    • Round Trip Time (RTT) : 서버와 클라이언트 두 호스트를 모두 왕복하는 데 걸리는 지연 시간
      • 인터넷상의 게임이나 화상 채팅 등에 대한 품질에 영향을 주는 값
TCP 혼잡 제어
  • TCP 네트워크의 통신량을 조절하여 TCP 네트워크가 혼잡해지지 않도록 하는 방식
  • 패킷을 보내는 쪽에서 네트워크에서 수용할 수 있는 양을 파악하고 그만큼의 패킷만 보내는 약속으로 TCP 혼잡 해결
  • 호스트가 네트워크의 상태를 시시각각 파악하고 전송 속도를 조절하는 것 또한 혼잡 제어 기능 중 하나
  • 대표적인 혼잡 제어의 기술들
    1. 느린 시작
      • TCP 연결이 시작되면 전송 가능한 버퍼의 양인 혼잡 윈도우 (CWND) 의 초기값을 작게 설정하여 전송
      • 패킷이 정상적으로 도착할 떄마다 더 많은 패킷을 보내고, 이를 패킷 유실이 발생하기 전까지 반복하는 방식
      • 초기에는 적은 패킷을 보내면서 곱셈 방식으로 전송 패킷의 크기를 빠르게 늘리는 방법
    2. 빠른 재전송
      • 먼저 도착해야 하는 패킷이 도착하지 않고 다음 패킷이 도착한 경우에도 수신자가 일단 ACK 패킷을 보내는 방식
      • 중간에 패킷이 하나 손실되면 송신자는 중복된 ACK 패킷을 통해 이를 감지하고 정상적으로 보내지 않은 패킷을 재전송
      • 중복된 패킷을 3개 받으면 반드시 손실된 패킷 재전송
    3. 흐름 제어
      • TCP 송신자가 데이터를 너무 빠르게 혹은 너무 많이 전송하여 수신자의 버퍼가 오버플로우되는 현상을 방지하는 기술
      • 송신자가 데이터를 전송하는 속도를 애플리케이션 프로세스를 읽는 속도와 유사한 수준으로 만들어 트래픽 수신 속도를 송신 속도와 일치시키는 기술

HTTP 프로토콜

HTTP 최적화 기술
  • HTTP/0.9 버전까지 클라이언트와 서버의 인터넷 통신 정상화, 가용성, 신뢰성 등 기능에 초점을 두었다면 HTTP/1.0 버전부터는 클라이언트와 서버 사이 요청과 응답을 빠르게 할 수 있는 연구가 진행됨
  • 멀티호스트 환경으로 웹 환경이 변하면서 HTTP/1.1 버전부터 멀티호스트 기능과 클라이언트와 서버 사이에 TCP/IP 연결을 재사용하는 기능 추가
  • HTTP/1.1 버전부터 적용된 연결 재사용 (persistent connection), 파이프라이닝 기법 (pipelining)이 연결 기반의 HTTP 최적화 기술
HTTP 지속적 연결
  • HTTP 초기 버전에서는 통신을 통해 여러 오브젝트를 요청하고 응답하려면 3-way handshake 방식으로 TCP 연결을 맺음
  • 웹 사이트에 멀티미디어 컨텐츠가 늘어나면서 TCP 연결 재사용 (keep-alive)이 필요하게 되어 등장한 것이 지속적 연결 기술
  • HTTP 지속적 연결은 클라이언트와 서버가 TCP 상에서 한 번 연결되면 둘 사이의 연결이 완전히 끊어지기 전까지 맺어진 연결을 지속적으로 재사용하는 기술
  • HTTP/1.0 기반에서 지속적 연결 사용을 원하는 클라이언트가 해당 기능을 지원하는 웹 서버에 HTTP 요청 헤더를 이용하여 지속적인 연결을 요청하기 시작
    • Connection: keep-alive
  • HTTP/1.1 버전에서는 Connection 헤더를 사용하지 않아도 모든 요청과 응답이 HTTP 지속적 연결을 기본으로 지원
    • 필요 없는 경우에는 Connection: close 헤더 요청을 통해 지속적 연결을 사용하지 않겠다고 전달 가능
  • 지속적 연결 사용 시 장점
    • 단일 시간 동안 TCP 연결의 수를 줄여 서버의 CPU나 메모리 자원을 절약하고 네트워크 혼잡이나 지연을 줄임
    • 복수 개의 HTTP 요청과 응답을 병렬로 동시에 처리하기 위한 HTTP 파이프라이닝 기술을 사용하려면 꼭 지원해야 함
  • 지속적 연결 사용 시 단점
    • 서버에 연결된 모든 클라이언트의 TCP 연결이 계속 늘어나면 자원이 고갈되어 더 많은 클라이언트가 접속할 때 대처 못할 상황 발생 가능
  • HTTP/2 버전은 단일 TCP 연결을 통해 클라이언트와 서버 사이 응답 지연 없이 스트림 형태로 다수의 HTTP 요청과 응답을 주고받을 수 있는 멀티플렉싱 기술을 토대로 만들어짐
    • HTTP/2 를 사용하면 지속적 연결을 고민할 필요가 없음
HTTP 파이프라이닝
  • 기존에는 HTTP 요청과 응답이 여럿일 때 하나의 응답이 지연되면 나머지 요청과 응답이 모두 지연될 수 밖에 없는 구조
  • HTTP 파이프라이닝은 먼저 보낸 요청의 응답이 없어도 다음 요청을 병렬적으로 전송하는 기술
    • 중간에서 응답 지연이 발생하더라도 클라이언트는 먼저 서버 측의 응답을 받을 수 있어 전체적으로 빠른 웹 로딩이 구현되는 구조

DNS

  • DNS 질의와 응답 성능이 나쁘면 웹 사이트 로딩에 영향을 줌
  • 관리자는 자신이 운영하는 웹 사이트 호스트명의 DNS 질의 속도를 파악하고 개선해야 함
DNS의 작동 원리
  1. 로컬 DNS 서버로 질의
    • 로컬 DNS : 사용자와 인접한 DNS
    • 사용자가 PC 등에 수동으로 설정한 DNS의 IP일 수도 있고, PC가 DHCP 설정을 통해 사용하는 ISP의 인근 서버일 수도 있음
  2. 루트 DNS 서버로 질의
    • 소유하지 않은 도메인 정보에 대한 질의를 받으면 로컬 DNS는 전체 도메인을 관장하는 루트 DNS에 www.example.com 도메인을 질의
  3. .com DNS 서버로 질의
    • 로컬 DNS는 .com 도메인을 관장하는 .com DNS에 www.example.com 도메인을 질의
  4. example.com DNS 서버로 질의
    • 로컬 DNS는 www.example.com 도메인을 관장하는 example.com DNS에 질의
  • 위와 같이 계층형으로 나누어진 역할에 따라 순차적인 DNS 질의를 반복하여 값을 받아오는 프로세스 과정을 반복적 질의 (iterative query) 라고 함
  • 로컬 DNS 서버들은 사용자가 이용 중인 ISP 업체나 DNS 전문 서비스 업체 등이 관리
  • 루트 DNS 서버는 ICANN (Internet Corporation for Assigned Names and Number) 기관에서 관리
  • 웹 서비스 운영 업체는 example.com 서버부터 관여해 DNS 전문 업체의 서비스를 받거나 분산된 DNS 서버를 직접 운영하는 방식으로 DNS 성능을 향상할 수 있음
사용 주인 다양한 도메인 확인 방법
  • 최근 웹 사이트는 자신의 웹 서비스 컨텐츠 뿐 아니라 다른 웹 서비스의 다양한 컨텐츠를 호출하여 사용
  • 브라우저의 개발자 도구 → Source 항목을 통해 하나의 웹 페이지에서 어떤 도메인들이 사용되고 있는지 파악할 수 있음
  • 특정 업체의 서비스에서 문제가 발생하지 않도록 지속적으로 모니터링해야 함
    • 사용 중인 특정 모듈 서비스 업체의 DNS 조회가 불가능하거나 느리다면 해당 모듈을 다운로드해 자신의 웹 서버에 업로드하여 사용하는 방법 등도 고려해야 함
    • 자주 업데이트 여부를 확인하고 적용해야 한다는 단점
웹 성능을 최적화하는 도메인 운용 방법
  • 직접 개발한 내부 서비스에 도메인 분할을 하고 싶다면 상위 도메인 (top level domain) 을 동일하게 해 DNS 질의를 최대한 적게 만드는 것을 권장
    • 공통된 상위 도메인을 사용하는 서비스들은 도메인 질의를 담당하는 네임 서버에 캐싱된 정보를 재사용할 수 있음
    • 동일한 상위 도메인을 사용하면 HTTPS 사용을 위한 SSL 인증서를 와일드카드 형식으로 하나만 생성해도 모든 도메인에 사용할 수 있음
  • HTML의 DNS 프리패치 (prefetch) 기능을 사용하면 웹 페이지에 사용된 도메인들의 DNS를 조회하는 시간이 좀 더 빨라짐
    • 프리패치는 하나의 웹 페이지에 다수의 도메인 호스트명이 섞여 있을 때 웹 문서 페이지를 여는 시점에 멀티스레드 방식으로 미리 DNS를 조회해 빠르게 IP 주소를 불러오는 기술
  • <link rel="dns-prefetch" href="//img.xxx.com">

브라우저

브라우저의 역사와 특징
  • 초기 브라우저는 HTTP와 DNS 기술을 접목해 DNS로 사용자가 입력한 http:// 주소에서 접속할 웹 서버의 IP를 찾고 HTTP로 웹 서버에 접속해 컨텐츠를 가져오는 단순한 기능을 수행했음
  • 현재 사용되는 최신 버전의 브라우저는 HTML, CSS, AJAX 등의 기술들을 대부분 지원함
  • HTTP가 빠르게 웹 컨텐츠를 전달해도 이를 사용자에게 제공하는 브라우저가 빠르게 작동하지 않으면 웹 성능은 느려질 수밖에 없음
  • 대체로 웹 성능 최종 테스트나 디버깅 작업은 브라우저를 통해 수행
내비게이션 타이밍 API
  • 네비게이션 타이밍 API는 웹 사이트의 성능을 측정하는 데 사용할 수 있는 데이터를 제공
  • 더 유용하고 정확한 종단 (end-to-end) 간 대기 시간 (latency) 정보 제공
  • API는 window.performance 개체의 속성으로 사용 가능
네비게이션 타이밍 속성
  • performance.timing 이벤트 속성은 페이지 로딩 흐름 순서에 따라 다음과 같이 정의
속성 이벤트 내용
Prompt for unload navigationStart 이전 페이지가 unload 시작한 시점, 즉 이전 페이지의 모든 작업이 끝나고 새로운 페이지를 로드할 준비가 완료된 시점을 의미
이전 페이지가 없다면 fetchStart와 같은 값이 됨
unload unloadStart 이전 페이지와 현재 페이지가 동일 근원일 때 unload 이벤트가 시작하는 바로 직전 시점
이벤트가 없거나 동일 근원이 아닐 경우 값은 0
unloadEnd 이전 페이지와 현재 페이지가 동일 근원일 때 unload 이벤트를 마친 바로 다음 시점
이벤트가 없거나 동일 근원이 아닐 경우 값은 0
redirect redirectStart HTTP에서 페이지 재전송이 수행될 때의 시작 시점
페이지 재전송이 없다면 0
redirectEnd HTTP에서 페이지 재전송이 수행될 때의 마지막 페이지 재전송 응답의 마지막 바이트를 받은 시점
페이지 재전송이 없다면 0
App cache fetchStart HTTP 요청으로 새로운 리소스를 불러오기 시작하는 시점
HTTP를 요청하는 애플리케이션이 캐시를 지원한다면 캐시의 존재를 확인하는 시점
캐시가 없다면 HTTP 응답으로 리소스를 받아오기 시작하는 시점
DNS domainLookupStart 웹 사이트 도메인 DNS로 IP 검색을 시작하는 시점
해당 도메인에 연결된 TCP 세션이 남아 있어 도메인 조회가 필요 없거나 DNS 정보가 브라우저나 운영 체제에 캐싱되어 있거나 IP 주소로 접근하는 등 실제 DNS 조회가 발생하지 않으면 fetchStart와 동일한 값
domainLookupEnd DNS 조회 종료 시점으로 DNS 조회가 발생하지 않았다면 fetchStart와 동일한 값
TCP connectStart 브라우저가 웹 페이지 문서를 받기 위해 서버와 연결을 시도하는 시점
이미 서버와 연결되어 있거나 원하는 컨텐츠가 캐싱되어 있거나 로컬 리소스를 이용하여 TCP 연결이 필요 없는 경우라면 domainLookupEnd와 동일한 값
SecureConnectionStart HTTPS로 접속할 때 브라우저가 웹 페이지 문서를 받기 위해 서버와 연결을 시도하는 시점
connectStart와 마찬가지로 서버와 이미 연결된 상태 등 별도 연결이 필요 없는 경우라면 직전 단계인 domainLookupEnd와 동일한 값
HTTPS를 사용하지 않는다면 0
connectEnd 브라우저가 웹 페이지 문서를 받으려 연결했던 서버와 연결을 끊는 시점
이미 서버와 연결되어 있거나 원하는 컨텐츠가 캐싱되어 있거나 로컬 리소스를 이용하여 TCP 연결이 필요 없는 경우라면 domainLookupEnd와 동일한 값
Request requestStart 브라우저가 접속한 서버 또는 애플리케이션 캐시 시스템에 문서를 요청한 시작 시간
만약 첫 번째 요청이 실패하여 재요청을 하는 경우 새 요청 바로 직전 시간
Response responseStart 브라우저가 서버 또는 캐시 시스템으로부터 응답 데이터의 첫 번째 바이트를 받은 시간
responseEnd 브라우저가 서버 또는 캐시 시스템으로부터 응답 데이터의 마지막 바이트를 받은 시간
Processing domLoading 브라우저가 웹 페이지 문서를 만들기 시작하는 시점
domInteractive 브라우저가 웹 페이지 문서 준비 상태를 interactive로 변경하는 시점
domContentLoaded 웹 페이지 문서에서 DOMContentLoaded 이벤트가 호출되는 시점
domComplete 웹 페이지 문서가 준비 상태를 complete 상태로 변경하는 시점
onLoad loadEventStart 웹 페이지의 load 이벤트가 발생하는 시점
이벤트가 발생하지 않았다면 0
loadEventEnd 웹 페이지의 load 이벤트가 완료된 시점
  • navigation은 사용자가 어떻게 페이지를 탐색하는가를 조사하는 반면, timing 속성은 탐색과 페이지 로드 이벤트에 대한 데이터를 가지고 있음
내비게이션 타이밍 속성값 구하기
  • window.performance.navigation 객체는 페이지 재전송 속성, 앞뒤 이동 버튼이나 URL이 어떤 페이지 로딩을 발생시키는지 (trigger) 확인하는 속성 저장
    • window.performance.navigation.redirectCount : 페이지 내에서 재전송이 몇 번 발생했는지
    • window.performance.navigation.type : 사용자가 해당 웹 페이지에 어떻게 접속했는지에 관한 정보
      • 아래 테이블은 window.performance.navigation.type 속성 중 대표적인 4개 방식
상수 설명
TYPE_NAVIGATE 0 링크, 북마크, 폼 전송, URL 브라우저 타이핑 등의 방식으로 페이지 접속
TYPE_RELOAD 1 브라우저의 새로고침 버튼을 통해 페이지 접속
TYPE_BACK_FORWARD 2 뒤로가기 버튼을 통해 페이지 접속
TYPE_RESERVED 255 그 외 방법으로 페이지 접속
  • navigationStart 속성으로 사용자가 느끼는 페이지 로딩 시간 값을 구하는 예제 코드
<script>
    function onLoad() {
        let now = new Date().getTime();
        let pageLoadTime = now - performance.timing.navigationStart;
        console.log("User-perceived page loading time : " + pageLoadTime);
    }
</script>
  • 위 코드 실행 후 브라우저 개발자 도구 → Console 탭을 열면 페이지 로딩에 소요된 시간을 밀리초 단위로 확인 가능
  • 내비게이션 타이밍 API에 포함된 각 속성값을 사용하면 다양한 성능 지표 얻기 가능
  • 테스트에 사용한 클라이언트 기기 종류나 네트워크 종류에 상관 없이 connectTime 값이 항상 크다면 웹 서버를 좀 더 빠르게 네트워크에 연결할 수 있는 방법을 찾아야 함

웹에 날개를 달아주는 웹 성능 최적화 기법, 강상진, 윤호성 저, 루비페이퍼 출판