[Web Performance] HTML

hellollolo·2024년 5월 8일

처음에 웹 사이트에 접속한 후 HTML을 요청하는 데에는 여러 단계를 거친다. 각 단계의 소요 시간을 줄이는 것이 Time to First Byte을 향상 시키는데 도움이 된다.
TTFB의 향상은 Largest Contentful Paint (LCP)이나 First Contentful Paint (FCP)의 향상에도 도움이 된다.

redirects 최소화

클라이언트로 부터 리소스가 요청되면 서버는 영구 리디렉션(301 Moved Permanently response) 또는 임시 리디렉션(302 Found response) 중 하나로 리디렉션을 응답할 수 있습니다.

redirect는 2가지로 나뉜다.

  • Same-origin redirect: 같은 origin 안에서 이동하고 개발자가 완전히 컨트롤 할 수 있다.

  • Cross-origin redirect: 다른 origin에서 초기화 되는 redirect이다. 기본적으로 개발자의 컨트롤을 벗어난다.

Cross-origin redirect는 주로 광고, URL 압축 services, 다른 제 3 서비스에 의해 사용된다.

Same-origin redirect의 흔한 경우 중 하나는 후행 슬래시로 끝나는 URL에서 후행 슬래시가 없는 URL로 이동시키거나 그 반대입니다(예: example.com/page/ -> example.com/page).

일반적인 동일 출처 리디렉션 패턴은 사용자를 후행 슬래시로 끝나는 URL에서 후행 슬래시가 없는 URL로 리디렉션하거나 그 반대로 리디렉션하는 것입니다(예: example.com/page/에서 example.com/page로 리디렉션하는 경우). 페이지 간에 내부 링크를 만들 때는 리디렉션으로 응답하는 페이지로 연결되지 않도록 하고 올바른 위치로 직접 연결해야 합니다.

HTML 응답 캐싱

HTML response를 캐싱하는 것은 생각보다 쉽지 않다. respone는 CSS나 Javascript와 같은 여러가지 자원들을 포함하는데 각 파일들을 내용이 수시로 바뀔 수 있다.

게다가, 캐시된 HTML 문서는 오래된 하위 리소스에 대한 참조를 포함하므로 배포 후 오래된 문서가 될 수 있습니다.

그럼에도 불구하고 캐싱은 성능을 높히기 위한 좋은 전략이다. 짧은 수명의 캐시를 적절히 사용하는 것은 응답 시간을 높히는데 도움이 된다. 정적 페이지의 경우 5분 정도의 캐싱이 적절할 것이다.

HTTP protocol에서 캐싱에 사용될 수 있는 몇가지 기능들을 소개하겠다.

Etag

Entity tag라고도 불리는 Etag는 요청 리소스의 고유성을 구분하는 식별자이다. 먼저 서버에 HTTP request를 보내면 서버는 응답 데이터과 함께 Etag를 같이 전송해 준다.

이후 클라이언트는 Etag의 값을 If-none-match request header에 Etag의 값을 넣어 서버에 전송한다.

서버는 클라이언트에게 받은 Etag의 값과 조금 전에 전송한 데이터의 Etag의 값을 비교해 같은 경우 304 Not Modified response 응답을 보낸다.

아래 예제 코드를 보자

const etag = md5(getTime(new Date()));

if (etag === request.headers["if-none-match"]) {
  reply.statusCode = 304;
  reply.send();
} else {
  reply.headers({
    "cache-control": "no-cache",
    etag,
  });
  reply.view("/src/pages/2.hbs", params);
}

return reply;

etag의 값이 같은 경우 replay.view를 할 필요가 없는 것을 볼 수 있다.

원본 크기가 42kb인 문서

서버에서 받은 Etag 값을

그 재요청시에 if-none-match header에 넣었다.

문서가 캐싱이 되어 용량이 34b로 줄어든 것을 확인할 수 있다.

Last modified

바로 위에서 본 Etag와 비슷하다 이번에는 고유값이 아닌 가장 최근에 변경된 시점을 저장한다.

처음에 서버에게 HTTP request를 보내면 서버는 last-modified와 함께 가장 최근 변경 시점을 기록한다.

두 번째 요청부터는 "클라이언트가 알고있는 최근 변경 시점"과 "서버가 알고있는 최근 변경 시점"을 비교해 갱신 여부를 결정한다.

아래 예제를 확인해 보자.

if (time <= request.headers["if-modified-since"]) {
  reply.statusCode = 304;
  reply.send();
} else {
  reply.headers({
    "cache-control": "no-cache",
    "last-modified": time,
  });
  reply.view("/src/pages/3.hbs", params);
}

return reply;

Max-age=N

max-age는 일정 시간동안 서버측에 요청자체를 안하고 디스크의 cache를 사용하는 옵션이다. 네트워크 요청을 안하기 때문에 위 2개의 옵션보다 속도가 빠르다.

max-age와 If-None-Match, If-Modified-Since을 적절히 섞으면 속도와 정확성의 균형을 잘 유지할 수 있다.

이 두개를 섞은 예제를 보자.

const etag = md5(getTime(new Date()));

if (etag === request.headers["if-none-match"]) {
  reply.statusCode = 304;
  reply.send();
} else {
  reply.headers({
    "cache-control": "max-age=30",
    etag,
  });
  reply.view("/src/pages/4.hbs", params);
}

return reply;

처음 request 요청 후 30초 동안은 max-age를 사용하다가 30초가 지나면 if-none-match를 검증하는 방식으로 넘어간다.

서버 응답 시간 측정

캐시되지 않은 응답의 경우 호스팅 업체와 벡엔드 상태에 따라 크게 달라진다. 사용자가 느린 TTFB를 받은 경우 Server-timing 응답 헤더를 사용해 서버의 상태를 확인할 수 있다.

위 Server-timing 응답 헤더는 auth(인증)작업에 55.5ms가 소모되었고 db(데이터베이스)작업에 220ms가 소모되었다는 의미이다.

압축

HTML, JavaScript, CSS, SVG 이미지와 같은 텍스트 기반 응답은 압축하여 네트워크를 통한 전송 크기를 줄여야 다운로드 속도를 높일 수 있습니다. 가장 널리 사용되는 압축 알고리즘은 gzip과 Brotli입니다. Brotli는 gzip을 통해 약 15% ~20% 의 개선을 실현합니다.

가능한 경우 Brotli를 사용합니다. 앞서 언급했듯이 Brotli는 gzip보다 현저하게 개선되었으며 Brotli는 모든 주요 브라우저에서 지원됩니다. 가능하면 Brotli를 사용하세요. 단, 기존 브라우저에서 다수의 사용자가 웹사이트를 사용하는 경우, 전혀 압축하지 않는 것보다 압축이 더 낫기 때문에 gzip을 대체 수단으로 사용해야 합니다.

파일 크기가 중요합니다. 1KiB 미만의 매우 작은 리소스는 잘 압축되지 않거나 아예 압축되지 않는 경우도 있습니다. 모든 유형의 데이터 압축의 효과는 압축 알고리즘이 더 압축 가능한 데이터 비트를 찾기 위해 작업할 수 있는 대량의 데이터가 있는지에 따라 달라집니다. 파일이 클수록 더 나은 압축이 이루어집니다

동적 압축과 정적 압축의 차이점을 알아보세요. 동적 압축과 정적 압축은 리소스를 압축해야 하는 시점에 관해 서로 다른 접근 방식을 취합니다. 동적 압축은 요청될 때 리소스를 압축하며, 때로는 요청될 때마다 리소스를 압축합니다. 반면에 정적 압축은 파일을 미리 압축하므로 요청 시 압축을 수행할 필요가 없습니다. 정적 압축은 압축 자체와 관련된 지연 시간을 제거하므로 동적 압축의 경우 서버 응답 시간에 추가될 수 있습니다. JavaScript, CSS, SVG 이미지와 같은 정적 리소스는 정적으로 압축되어야 하는 반면, HTML 리소스(특히 인증된 사용자를 위해 동적으로 생성된 경우)는 동적으로 압축되어야 합니다.

Content Delivery Network

Content Delivery Network 는 원본 서버의 리소스를 캐시하여 사용자에게 물리적으로 더 가까운 에지 서버에서 제공하는 서버의 분산 네트워크입니다. 사용자와의 물리적 근접성은 왕복 시간 (RTT)을 줄여주는 반면, HTTP/2 또는 HTTP/3, 캐싱, 압축과 같은 최적화로 CDN이 콘텐츠를 원본 서버에서 가져오는 경우보다 더 빠르게 제공할 수 있습니다. CDN을 활용하면 경우에 따라 웹사이트의 TTFB가 크게 개선될 수 있습니다.

0개의 댓글