Optimization - 캐시와 Tree Shaking

uk·2023년 3월 19일
0

캐시(Cache)

캐시는 다운로드 받은 데이터나 값을 미리 복사해 놓는 임시 장소를 뜻하며 캐시에 저장된 복사본을 통해 처리속도를 향상시키고 추가로 발생되는 요청을 더 빠르게 처리할 수 있다.

예를 들어 서버에 이미지를 받아오는 요청을 보낼 경우 첫 번째 요청에서는 이미지를 통째로 받아온다. 이미지의 용량이 1MB, HTTP 헤더의 용량이 0.1MB라면 총 용량은 1.1MB이다.

문제는 두 번째 요청부터 동일한 파일을 다시 받아오는 일이 발생하는데 전에 받았던 이미지를 재사용할 수 있다면 1.1MB의 응답을 통째로 받아올 필요없이 0.1MB의 HTTP 헤더만 받아도 될 것이다. 이러한 요청이 수백 수천번 발생한다면 트래픽 낭비또한 커질 것이고 이때 캐시를 활용하면 리소스 낭비를 막을 수 있다.

서버에서 응답 시 이미지 파일과 함께 헤더에 Cache-Control값을 60으로 작성해서 보내면 해당 이미지 파일은 60초동안 유효하게 되며 브라우저 캐시에 저장된다.

이후 두 번째 요청부터는 브라우저의 캐시를 우선으로 조회하고 해당 데이터가 존재하면서 유효시간이 지나지 않았다면 캐시에 있는 데이터를 가져와서 사용한다. 만약 유효시간이 지났다면 서버에서 다시 이미지를 받아온다.

브라우저 캐시를 활용하면 네트워크 리소스를 아낄 수 있고 파일을 서버로부터 다시 받아올 필요가 없기 때문에 브라우저의 로딩이 빨라지며 빠른 사용자 경험을 제공한다.


캐시 검증 헤더와 조건부 요청

캐시 유효시간은 지났지만 서버에서 다시 받아와야하는 파일(데이터)이 캐시에 저장되어 있는 파일과 동일한 경우 똑같은 파일을 다시 받아오는 것이 아닌 서버와 캐시의 파일이 동일(최종 수정일, 버전)한지 확인하고 재사용할 수 있다.

캐시 검증 헤더

캐시에 저장된 데이터와 서버의 데이터가 동일한지 확인하기 위한 정보를 담은 응답 헤더

-Last-Modified - 데이터가 마지막으로 수정된 시점을 의미하는 응답 헤더로 조건부 요청 헤더인 If-Modified-Since와 묶어서 사용한다.

  • Etag - 데이터의 버전을 의미하는 응답 헤더로 조건부 요청 헤더인 If-None-Match 와 묶어서 사용한다.

조건부 요청 헤더

캐시의 데이터와 서버의 데이터가 동일하다면 재사용하게 해달라는 의미의 요청 헤더

  • If-Modified-Since - 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인하고 수정되지 않았다면 캐시된 리소스를 사용한다.
  • If-None-Match - 캐시된 리소스의 ETag 값과 현재 서버 리소스의 ETag 값이 같은지 확인하고 같다면 캐시된 리소스를 사용한다.

서버와 캐시의 데이터가 동일한 데이터임이 검증되었다면 서버는 '데이터가 수정되지 않았음'을 의미하는 304 Not Modified 라는 응답(HTTP 헤더만 전송)을 보내주고 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 된다.

두 헤더 중 하나의 헤더만 사용할 수도 있지만 보통은 두 헤더 모두 사용한다. 둘 중 하나만 사용했다가 매칭되는 응답 헤더가 없는 경우 재사용할 수 있는 경우에도 리소스를 다시 받아와야 하는 문제가 생길 수 있기 때문이다.


Tree Shaking이란?

Tree Shaking은 말 그대로 나무를 흔들어 잔가지를 털어내듯 불필요한 코드를 제거하는 것을 의미한다.

웹 애플리케이션의 규모가 커지면서 코드의 양이 방대해지고 다양한 라이브러리를 가져다 사용하게 되면서 불필요한 코드를 그대로 가져가는 경우가 빈번하게 발생하는데 이러한 불필요한 코드를 제거하면 웹 성능 최적화에 도움된다.

Tree Shaking이 필요한 이유

JS 파일의 크기 증가

웹 애플리케이션이 발전하면서 사용자 interaction 또한 많아졌는데 이는 JS의 비중이 높아졌음을 뜻한다. http archive의 자료에 따르면 JavaScript 파일 크기를 2010년도와 비교했을 때 중위값이 데스크톱에서는 498.2%, 모바일에서는 827.1%나 증가했다.

JS 파일의 크기뿐만 아니라 JS 파일을 요청하는 HTTP Request 또한 데스크탑에서 144.%, 모바일에서 425.0% 증가했으며 크기가 커진 JS 파일이 늘어난 요청만큼 더 오가는 것이니 네트워크 리소스 소모도 그만큼 커졌다는 것을 알 수 있다.

JS 파일 크기의 증가와 요청 횟수의 증가는 그만큼 파일이 오가는 동안 화면 표시가 늦어지고 네트워크 속도가 느린 환경에서는 더 큰 병목현상을 유발하므로 Tree Shaking을 통해 파일 크기를 줄이는 것이 최적화에 도움이 된다.


JS 파일의 실행 시간

JS 파일이 실행되기 위해서는 아래와 같은 과정을 거친다.

  1. 서버에 요청을 보내고 파일을 다운로드한다.
  2. 다운로드한 파일을 압축 해제한다.
  3. JS 코드를 파싱하여 DOM 트리를 생성한다.
  4. 파싱이 끝나면 컴파일하여 컴퓨터가 이해할 수 있는 언어로 변환한다.

이처럼 거쳐야 하는 과정이 많기 때문에 JS는 다른 리소스에 비해 상대적으로 많은 시간을 소모한다. JS 파일의 크기가 커진 만큼 파일의 실행 시간 또한 늘어났는데 2010년에 비해 데스크톱에서는 25% 모바일에서는 277.8%만큼 증가했다. JS 파일 실행은 CPU에 크게 영향을 받으므로 사양이 천차만별인 모바일 환경에서 그 영향이 더욱 두드러진다.


JavaScript Tree Shaking

Webpack 4버전 이상을 사용하는 경우 ES6 모듈(import, export)을 대상으로는 기본적인 Tree Shaking을 제공하며 Create React App을 통해 만든 React 애플리케이션도 Webpack을 사용하고 있기 때문에 Tree Shaking이 가능하다.

1. 필요한 모듈만 import 하기

import 구문을 사용하여 라이브러리를 불러올 때 필요한 모듈만 불러오면 번들링 과정에서 사용되는 코드만 포함시키기 때문에 Tree Shaking이 가능해진다.

import React from 'react';
...

console.log(React);

// 올바른 예시
import { useState, useEffect } from 'react'

React를 통째로 불러온 다음 console.log로 확인해보면 React의 모든 코드가 불러와진 것을 볼 수 있다. 이렇게 모든 코드를 불러오면 번들링할때 사용하지 않는 불필요한 코드까지 같이 빌드된다.


2. Babelrc 파일 설정하기

Babel은 자바스크립트 문법이 구형 브라우저에서도 호환이 가능하도록 ES5 문법으로 변환하는 라이브러리이다. 이때 ES5 문법은 import를 지원하지 않기 때문에 commonJS 문법의 require로 변경시킨다. 이 과정은 Tree Shaking에 큰 걸림돌이 되는데 require는 export 되는 모든 모듈을 불러오기 때문이다.

{
  “presets”: [ 
    [
      “@babel/preset-env”,
      {
	    "modules": false
      }
    ]
 ]
}

위와 같은 상황을 방지하기 위해 Barbelrc 파일에 위와 같은 코드를 작성해주면 ES5로 변환하는 것을 막을 수 있다. modules 값을 true로 설정하면 항상 ES5 문법으로 변환된다.


3. sideEffects 설정하기

Webpack은 사이드 이펙트를 발생시킬 수 있는 코드를 사용하지 않더라도 Tree Shaking 대상에서 제외시킨다.

const cats = ['navi', 'coco']

const addCat = (name) => {
	cats.push(name)
}

addCat 함수는 함수 외부에 있는 cats 배열을 변경시키는 함수이다. 해당 함수는 순수 함수(외부에 영향을 주거나 받지 않는 함수)가 아니기 때문에 Tree Shaking을 통해 제외하는 경우 문제가 생길 수도 있다고 판단하여 Webpack은 이 코드를 제외시키지 않는다.

  // package.json
{
  // App 전체에서 사이드 이펙트가 발생하지 않을 것을 알림
  "name": "tree-shaking",
  "version": "1.0.0",
  "sideEffects": false
  
  // sideEffects를 아래와 같이 작성하면 특정 파일에서는 발생하지 않은 것을 알림
  "sideEffects": ["./src/components/NoSideEffect.js"]
}

package.json 파일에서 sideEffects를 설정하면 사이드 이펙트가 생기지 않을 것이므로 코드를 제외시켜도 되는 것을 웹팩에게 알려줄 수 있다.


4. ES6 문법을 사용하는 모듈 사용하기

보통 위의 예시까지 작성하면 Tree Shaking이 잘 작동하지만 Tree Shaking이 적용되지 않는 라이브러리가 있다면 해당 라이브러리가 어떤 문법을 사용하고 있는지 확인해야한다.

모듈에 따라서 ES5로 작성된 모듈이 있을 수도 있기 때문에 ES5 문법을 일부만 사용하는 경우라면 해당 모듈을 대체할 수 있으면서 ES6를 지원하는 다른 모듈을 사용하는 것이 Tree Shaking에 유리하다. ES6 문법을 사용하는 모듈은 필요한 부분만 import 해서 사용하지 않는 코드는 빌드할 때 제외되기 때문이다.

profile
주니어 프론트엔드 개발자 uk입니다.

0개의 댓글