개발 기술/사소하지만 놓치기 쉬운 개발 지식

웹사이트를 최적화시키는 3가지 기법: 코드 압축, 경량화, 난독화

by GicoMomg 2024. 11. 18.

1. 들어가며…

개발자로서 매일 사용하는 개발자 도구는 성능탭, 메모리탭 등 여러 유용한 도구를 제공해준다. 이 중 네트워크 탭은 단순히 보면 API 통신을 보는 용도로 보이지만...그 내부를 자세히 들여다보면 gzip 압축이 적용된 파일이나, 하나로 합쳐진 CSS 혹은 난독화된 JavaScript 파일을 확인할 수 있다.
이러한 현상은 코드 압축, 코드 경량화, 코드 난독화와 같은 최적화 기법의 결과물이다.
코드 압축은 서버에서 전송되는 파일의 크기를 줄여 로딩 속도를 향상시키고, 코드 경량화는 불필요한 공백과 주석을 제거하여 파일 크기를 최소화한다. 또한, 코드 난독화는 소스 코드를 복잡하게 만들어 보안을 강화한다. 이 세 가지 기법은 웹사이트의 성능과 보안을 동시에 개선하는 방법이다.
그렇다면 이러한 기법들은 어떻게 작동하는 걸까? 이번 포스팅에서는 이 기법들이 어떻게 작동하는지, 그리고 실제 프로젝트에 어떻게 적용할 수 있는지 간략히 알아보겠다.



2. 파일 크기를 줄이는 코드 압축 (Compression)

1) 코드 압축이란?

  • 코드 압축은 서버에서 웹사이트 파일을 사용자에게 보낼 때 파일의 크기를 줄여 전송하는 방식이다.
  • 파일을 압축하면 서버에서 전송하는 데이터의 크기가 줄어들어 더 빠르게 전달될 수 있다.
  • 압축된 파일은 사용자의 브라우저에서 압축 해제된다.
  • 대표적인 압축 방식으로는 GZip, Brotli 등이 있다.
  • 각 사이트 별 압축 방식은 [개발자 도구 > 네트워크 탭 > Content-Encoding]에서 확인할 수 있다.

gzip 압축을 사용하는 네이버



2) 압축 알고리즘 종류

(1) GZip, 가장 널리 쓰이는 압축 방식

  • GZip은 Deflate 알고리즘(LZ77 + 허프만 코딩)을 기반으로 구현되었으며 현재 가장 널리 사용된다.
  • 대부분의 웹 서버(예: Apache, Nginx)에서 지원되며, 웹페이지 파일을 GZip으로 압축하여 사용자가 페이지를 요청할 때 빠르게 전송한다.
  • GZIP은 데이터를 두 가지 주요 방법으로 압축한다:
    1. LZ77 알고리즘을 사용하여 중복된 데이터를 제거한다.
    2. 허프만 코딩(Huffman Coding)으로 데이터를 효율적으로 인코딩한다.



잠깐! LZ77 알고리즘은 무엇이고 허프만 코딩은 또 무엇이길래 압축률이 높은 걸까? 간단히 살펴보자!

(a) LZ77 알고리즘, 중복을 제거한다.

  • LZ77 알고리즘은 데이터에서 반복되는 패턴을 찾아내어, 이를 참조 정보(거리, 글자수)로 바꿔 데이터 크기를 줄이는 압축 방법이다.
  • 여기서 "거리"는 현재 위치에서 참조하려는 이전 데이터까지의 거리이고, "길이"는 참조하려는 데이터의 길이(글자 수)를 나타낸다.
  • 한 예로 아래와 같은 글자가 있다고 가정해보자.
나는 나는 나는 붕어빵을 산다

 

  • 우선 문장을 한 글자씩 번호와 함께 나누어 본다.
위치: 1   2  3   4   5   6   7    8    9   10  11  12
글자: 나  는  나  는   나   는   붕   어   빵   을   산   다

 

  • 첫 번째 "나", "는" (위치 1, 2) 처음 등장하는 글자들이므로 그대로 기록한다.
[나][는]

 

  • 두 번째 "나", "는" (위치 3, 4)은 이전에 위치 1에서 2까지 동일한 문자열이 있었다.
  • 동일한 문자열이 있었으므로 문구를 그대로 쓰지 않고 참조 정보를 적는다.
    • 거리: 현재 위치(3)에서 참조하려는 위치(1)까지의 거리 → 2
    • 길이: 반복되는 문자열의 길이 → 2 
(거리:2, 길이:2)
 
  • 세 번째 "나", "는" (위치 5, 6) 바로 앞의 위치 3에서 4까지 동일한 문자열이 있다.
  • 중복이므로 참조 정보를 업데이트한다.
    • 거리: 현재 위치(5)에서 참조하려는 위치(3)까지의 거리 → 2
    • 길이: 반복되는 문자열의 길이 → 2
(거리:2, 길이:2)

 

  • 나머지 글자들 "붕", "어", "빵", "을", "산", "다" (위치 7~12) 처음 등장하는 글자들이므로 그대로 기록한다.
[붕][어][빵][을][산][다]

 

  • 압축된 데이터를 순서대로 정리하면 다음과 같다:
[나][는](거리:2, 길이:2)(거리:2, 길이:2)[붕][어][빵][을][산][다] // 의미상 예시

[나][는](2, 2)(2, 2)[붕][어][빵][을][산][다] // 변환 예시
  • 처음 두 글자 "나", "는*은 그대로 기록한다.
  • 반복되는 "나", "는"은 각각 거리와 길이로 표현하여 중복을 줄였다.
  • 나머지 글자들은 그대로 기록한다.

 

(b) 허프만 코딩, 빈번하게 등장하는 문자를 짧은 비트로 만든다.

  • 허프만 코딩은 데이터를 더 작은 비트 수로 표현하는 방법이다.
  • 자주 등장하는 문자는 짧은 비트로, 그보다 덜 자주 등장하는 문자는 더 긴 비트로 인코딩한다
  • 예시로 "AAABBCC" 문자가 있다고 가정해보자! 
"AAABBCC"

 

  • 먼저 각 문자별 빈도수를 계산한다. 
A: 3회
B: 2회
C: 2회

 

  • 각 문자를 빈도수와 함께 노드로 만든다. 
[A:3], [B:2], [C:2]

 
 

  • 빈도수가 작은 순서대로 정렬한다. 
[B:2], [C:2], [A:3]
 
  • 그 다음, 가장 작은 두 노드 합쳐야한다.
  • [B:2]와 [C:2]를 합친다.
  • 그러면 새로운 부모 노드 [B+C:4]를 생성한다. 
     [B+C:4]
     /     \
 [B:2]   [C:2]
 
  • 새로 생긴 노드와 남은 노드를 빈도수가 작은 순으로 정렬한다.
[A:3], [B+C:4]

 

  • 그리고 다시 가장 작은 두 노드 합친다.
  • [A:3]와 [B+C:4]를 합치면, 새로운 루트 노드 [A+B+C:7]가 생성된다. 
       [A+B+C:7]
         /      \
     [A:3]    [B+C:4]
              /     \
           [B:2]   [C:2]
 

 

  • 이제 마지막으로 이진 코드를 할당한다.
  • 트리의 각 가지에 따라 왼쪽 가지에는 0, 오른쪽 가지에는 1을 할당한다. 
        [A+B+C:7]
          /    \
        0      1
      [A:3]  [B+C:4]
             /     \
           0       1
         [B:2]   [C:2]
 
  • A: 루트에서 왼쪽 가지 (0) → 코드: 0
  • B: 루트에서 오른쪽 가지 (1) → 왼쪽 가지 (0) → 코드: 10
  • C: 루트에서 오른쪽 가지 (1) → 오른쪽 가지 (1) → 코드: 11

 

  • 최종적으로 각 문자를 허프만 코드로 대체하면 끝이다.  
// 배치한 모습
A  A  A   B    B    C    C
0  0  0  10   10   11   11


// 최종 모습
0 0 0 10 10 11 11
  • 각 문자를 3비트로 표현한다고 가정하면:
    총 비트 수: 7문자 × 3비트 = 21비트
  • 허프만 코딩 후 데이터 크기
    • A (0): 3회 × 1비트 = 3비트
    • B (10): 2회 × 2비트 = 4비트
    • C (11): 2회 × 2비트 = 4비트
    • 총 비트 수: 3 + 4 + 4 = 11비트
  • 압축 전에는 21비트이지만, 압축 후에는 11비트가 되어 총 48% 감소되었다.

 

(2) Brotli

  • Brotli은 2015년에 Google에서 개발하였으며, GZip보다 더 높은 압축률을 가진다.
  • GZip처럼 LZ77, 허프만 코딩을 사용하되 추가적으로 사전(dictionary)을 활용해 압축한다.

 

  • Google Chrome과 Firefox 같은 최신 브라우저에서 지원되며,
  • HTML, CSS, JavaScript 파일을 더 작은 크기로 압축하여 웹사이트 로딩 속도를 개선한다.

brotli 압축을 사용하는, cloudflare 사이트
 

LZ77 알고리즘, 허프만 코딩은 알겠는데 딕셔너리 기법은 뭘까?

(a) 딕셔너리 기법, 자주 쓰이는 단어를 압축한다.

  • Brotli는 정적 사전, 동적 사전으로 데이터를 더욱 효율적으로 압축한다.
  • 그래서 Brotli은 gzip보다 압축률이 더 높은 편이다.
정적 사전 방식 동적 사전 방식
HTML, CSS, JavaScript 등의 일반적인 코드 용어를 미리 정의해 둔 사전이다.
코드 내에서 자주 사용되는 단어들을 빠르게 인식하고 압축할 수 있다.
최근에 사용된 데이터를 캐싱하고 이를 참조해 실시간으로 데이터를 압축한다.

 

(3) Brotli과 GZIP의 차이점

  • 앞서 우리는 2가지 압축 방식을 보았는데, 이 중 brotli이 gzip에 비해 압축률이 더 좋았다.
  • 하지만 어째서 다른 사이트들은 압축률이 좋은 brotli 대신 gzip을 쓰는 걸까? 두 압축 방식을 비교해보았다.
  brotli gzip
압축률 GZIP 보다 더 높은 압축률을 제공 DEFLATE라는 기본 알고리즘을 사용
압축 및 해제 속도 더 높은 압축률을 제공하기 위해 많은 CPU 자원을 사용 압축 및 해제 속도가 더 빠름
지원 및 호환성 최신 브라우저에서 널리 지원
일부 구형 도구나 CDN에서는 아직 완벽히 지원되지 않음
대부분의 웹 서버와 브라우저에서 널리 지원

그래프 데이터 참고: https://kwebby.com/blog/enable-brotli-compression/

자료를 보면 알 수 있듯이, brotli이 gzip보다 성능이 좋지만, 브라우저 호환성 문제와 압축 시간이 더 소요되는 걸 알 수 있다.



3) 코드 압축하는 방법 살펴보기

(1) Webpack에서 코드 압축 설정하는 법

  • Webpack에서 파일 압축을 위해 compression-webpack-plugin을 사용한다.
  • 우선 npm 명령어로 패키지를 설치한다.
npm install --save-dev compression-webpack-plugin

 

  • 설치 후, webpack.config.js 파일에 플러그인을 추가하여 압축을 설정한다.
const CompressionWebpackPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    // CompressionWebpackPlugin 추가
    new CompressionWebpackPlugin({
      filename: '[path][base].gz',  // 생성될 압축 파일 이름
      algorithm: 'gzip',            // 압축 알고리즘 (gzip, brotliCompress 등)
      test: /\.(js|css|html|svg)$/, // 압축할 파일 형식
      threshold: 10240,             // 최소 파일 크기 (바이트 단위)
      minRatio: 0.8,                // 압축 비율
      deleteOriginalAssets: false,  // 원본 파일 삭제 여부
    }),
  ],
};
  • 설정을 완료한 후, Webpack 빌드를 실행한다.
  • 그러면, 빌드 과정에서 지정된 파일 형식에 따라 압축된 .gz 파일이 생성된다.
  • 이 파일들은 서버에서 클라이언트로 전송될 때 사용된다.

 

(2) Vite에서 코드 압축 설정하는 법

  • Vite에서 압축을 위해 vite-plugin-compression을 사용한다.
npm install --save-dev vite-plugin-compression

 

  • 설치 후, vite.config.js 파일에 플러그인을 추가하여 압축을 설정한다.
import { defineConfig } from 'vite';
import compression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    // vite-plugin-compression 추가
    compression({
      verbose: true,             // 콘솔에 압축 정보 출력
      disable: false,            // 플러그인 비활성화 여부
      deleteOriginFile: false,   // 원본 파일 삭제 여부
      threshold: 10240,          // 최소 파일 크기 (바이트 단위)
      algorithm: 'gzip',         // 압축 알고리즘 (gzip, brotliCompress 등)
      ext: '.gz',                // 압축 파일 확장자
    }),
  ],
});
  • 이제 Vite 빌드를 실행하면 압축된 .gz 파일이 생성된다.





3. 불필요한 문자를 제거하는 코드 경량화 (Minification)

1) 코드 경량화란?

  • HTML, CSS, JavaScript 코드에서 불필요한 문자, 공백, 주석 등을 제거해 파일 크기를 줄이는 방법이다.
  • 이 작업은 코드의 동작에는 영향을 주지 없지만, 파일 크기가 줄어들어 로딩 시간이 단축된다.
  • 주로, 코드의 공백, 줄 바꿈, 주석 등을 제거하여 크기를 줄인다.
  • 경량화 작업은 개발자가 직접 하기보다는 Webpack이나 Gulp 같은 빌드 도구에서 자동으로 처리한다.

 

2) 경량화 예시

(1) 간단 예시

  • 코드 경량화는 보통 빌드 도구를 사용하여 자동으로 수행된다.
  • 그래서 개발 단계에서는 사람이 읽기 쉬운 코드로 작업하되, 배포 단계에서 빌드 도구로 경량화된 파일을 생성한다.
  • 경량화된 파일은 서버에 배포되어 사용자에게 전달된다.
  • 경량화가 진행될 경우, 아래와 같이 공백, 줄바꿈, 주석 등이 제거된다.
//✏️ 경량화 전

// 이거슨!! 디바은ㅅ아아아아아
// ????
//

/// 
const debounce = (cb, delay) => {
  let timer;
  return (...args) => {

    if (timer) clearTimeout(timer);
    timer = setTimeout(cb, delay, ...args);
  };
};
//✏️ 경량화 후

const debounce=(e,n)=>{let t;return(...c)=>{t&&clearTimeout(t),t=setTimeout(e,n,...c)}};

 

(2) 툴로 테스트해보기





4. 코드 난독화 (Uglification)

1) 코드 난독화의 개념과 특징

(1) 코드 난독화란?

  • 코드 난독화는 코드의 구조를 복잡하게 만들어 이해하기 어렵게 만드는 방법이다.
  • 난독화의 목적은 소스 코드를 이해하기 어렵게 만들어 무단 복제나 수정을 방지해, 라이선스를 보호하는 데 있다.
  • 하지만 완벽한 보안을 보장할 수 없어, 다른 보안 방법과 함께 사용해야 한다.

 

(2) 특징

  • 코드를 난독화할 때는 식별자를 변경하거나, 코드 구조를 복잡하게 만든다.
    • 식별자 변경: 변수 이름, 함수 이름 등을 짧고 의미 없는 이름으로 바꾸어 코드를 읽기 어렵게 만듬
    • 코드 복잡화: 코드 구조를 더 복잡하게 만들어 분석하기 어렵게 변환함
  • 아래 화면은 google 사이트의 어떤 js 파일이다. 보시다시피 알 수 없는 식별자로 함수와 로직이 변경된 걸 알 수 있다.

 

2) 코드 난독화하는 방법 살펴보기

(1) Webpack에서 코드 난독화 설정하는 법

  • Webpack에서 코드 난독화를 위해 webpack-obfuscator 플러그인을 사용할 수 있다.
npm install --save-dev webpack-obfuscator

 

  • 설치 후, webpack.config.js 파일에 webpack-obfuscator 플러그인을 추가하여 난독화를 설정한다.
const WebpackObfuscator = require('webpack-obfuscator');

module.exports = {
  mode: 'production',       // 프로덕션 모드 설정
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: __dirname + '/dist',
  },
  plugins: [
    // WebpackObfuscator 플러그인 추가
    new WebpackObfuscator({
      rotateStringArray: true,         // 문자열 배열 회전
      stringArray: true,               // 문자열 배열 사용
      stringArrayEncoding: ['base64'], // 문자열 배열 인코딩 방식
      stringArrayThreshold: 0.75,      // 문자열 배열 사용 비율

      // 추가 옵션 설정 가능
    }, ['excluded_bundle_name.js']) // 난독화에서 제외할 파일 목록
  ],
};

 

  • 설정을 완료한 후, Webpack 빌드를 실행한다.
npm run build
  • 빌드 과정에서 dist 폴더에 난독화된 JavaScript 파일이 생성된다.
  • 생성된 파일을 열어보면 변수명, 함수명 등이 의미 없는 이름으로 변경되고, 코드 구조가 복잡해져 있는 것을 확인할 수 있다.

 

(2) Vite에서 코드 난독화 설정하는 법

  • Vite에서는 vite-plugin-obfuscate 플러그인으로 코드 난독화를 설정할 수 있다.
npm install --save-dev vite-plugin-obfuscate

 

  • 설치 후, vite.config.js 파일에 vite-plugin-obfuscate 플러그인을 추가하여 난독화를 설정한다.
import { defineConfig } from 'vite';
import obfuscate from 'vite-plugin-obfuscate';

export default defineConfig({
  plugins: [
    // vite-plugin-obfuscate 플러그인 추가
    obfuscate({
      // 난독화 옵션 설정
      options: {
        compact: true,
        controlFlowFlattening: true,
        controlFlowFlatteningThreshold: 0.75,
        deadCodeInjection: true,
        ....
      },
      // 난독화에서 제외할 파일 목록
      exclude: ['excluded_file.js'],
    }),
  ],
});
  • webpack과 동일하게, 빌드 과정에서 dist 폴더에 난독화된 파일이 생성된다.

 

3) 주의할 점

  • 코드 난독화는 보안성을 강화하지만 파일 크기를 줄이지는 않는다. 만약 파일 크기도 줄이고 싶다면 난독화와 코드 경량화를 병행해야한다.
  • 코드 난독화는 빌드 시간을 늘리고, 코드 실행 시 약간의 성능 저하를 일으킬 수 있다. 그래서 중요한 로직에만 적용하는 게 좋다.




5. 마치며…

이번 시간에는 웹사이트를 최적화하는 3가지 방법에 대해 알아보았다.

  코드 압축 코드 경량화 코드 난독화
목적 데이터 전송량 감소 파일 크기 감소 소스 코드 보호
방식 압축 알고리즘 사용 불필요한 문자 제거 코드 구조 복잡화 및 식별자 변경
성능 영향 압축/해제 시 CPU 사용 없음 (빌드 시 수행) 실행 성능에 영향 가능
가독성 해제 후 원본 복원 가능 가독성 저하 가독성 매우 낮음
  • 코드 압축은 서버에서 전송되는 파일 크기를 줄이고, GZip과 Brotli와 같은 다양한 압축 알고리즘을 활용함으로써 웹페이지의 로딩 시간을 크게 단축시킬 수 있었다. 특히 Brotli는 GZip보다 높은 압축률을 제공하여 더 작은 파일 크기를 실현할 수 있지만, 서버와 클라이언트의 호환성을 고려해야 한다.
  • 코드 경량화는 불필요한 공백, 주석, 줄 바꿈 등을 제거하여 파일 크기를 최소화하는 방법으로, Webpack이나 Gulp와 같은 빌드 도구를 사용하면 자동으로 처리할 수 있다. 이는 사용자에게 더 빠른 로딩 속도를 제공할 수 있다.
  • 코드 난독화는 소스 코드를 복잡하게 만들어 무단 복제나 수정을 방지하는 동시에, 중요한 로직을 보호하는 데 유용하다. 하지만 난독화는 빌드 시간과 실행 성능에 영향을 미칠 수 있으므로 신중하게 적용해야 한다. 따라서 난독화와 경량화를 병행하는 게 좋다.

이 세가지 기법은 대부분의 빌드 도구에서 제공하지만, 모르고 쓰고 있었을 지도 모른다. 비록 이 기법들을 실제로 써보지 않더라도 그 동작을 이해하면 추후 연관 지식을 배울 때 도움이 될 것이다 🙂



 
 
 
참고자료
https://dev.to/biellls/compression-clearing-the-confusion-on-zip-gzip-zlib-and-deflate-15g1
https://kinsta.com/blog/enable-gzip-compression/
https://medium.com/@omkarbhavare2406/understanding-minification-a-process-with-purpose-145256b3cb5d
https://simpleit.rocks/web/html/compacting-html-code/
https://www.appsealing.com/code-obfuscation/

반응형

댓글