브라우저 개발자 도구에서 응답 크기를 보면 "transferred" 크기가 "resource" 크기보다 훨씬 작습니다. 어떻게 줄인 걸까요?

개념 정의

데이터 압축 은 정보를 더 적은 비트로 표현하는 기술입니다. 웹에서는 서버 응답을 압축하여 전송량을 줄이고, 페이지 로딩을 빠르게 합니다.

압축은 크게 두 종류입니다.

  • **무손실 압축 **: 원본을 완벽히 복원 (gzip, zstd)
  • ** 손실 압축 **: 약간의 정보 손실 허용 (JPEG, MP3)

여기서는 백엔드에서 주로 사용하는 무손실 압축을 다룹니다.

왜 필요한가

  • ** 네트워크 대역폭 절약 **: API 응답을 60~80% 줄일 수 있습니다
  • ** 페이지 로딩 속도 **: 전송 데이터가 적으면 빠릅니다
  • ** 비용 절감 **: 클라우드에서 아웃바운드 트래픽은 비용이 됩니다
  • ** 로그 저장 **: 대량 로그 파일의 저장 공간 절약

허프만 코딩 (Huffman Coding)

핵심 아이디어

자주 나오는 문자에는 ** 짧은 코드 **, 드문 문자에는 ** 긴 코드 **를 부여합니다. 전체 비트 수가 최소화됩니다.

PLAINTEXT
문자열: "AABABCAAB"

빈도: A=5, B=3, C=1

고정 길이: A=00, B=01, C=10 → 9 × 2 = 18비트
허프만:     A=0,  B=10, C=11 → 5×1 + 3×2 + 1×2 = 13비트
                                          절약: 약 28%

허프만 트리 구성

JAVA
public class HuffmanCoding {
    static class Node implements Comparable<Node> {
        char ch;
        int freq;
        Node left, right;

        Node(char ch, int freq) { this.ch = ch; this.freq = freq; }

        @Override
        public int compareTo(Node o) { return this.freq - o.freq; }
    }

    public Map<Character, String> buildCodes(String text) {
        // 1. 빈도 계산
        Map<Character, Integer> freq = new HashMap<>();
        for (char c : text.toCharArray()) {
            freq.merge(c, 1, Integer::sum);
        }

        // 2. 우선순위 큐로 허프만 트리 구성
        PriorityQueue<Node> pq = new PriorityQueue<>();
        freq.forEach((ch, f) -> pq.offer(new Node(ch, f)));

        while (pq.size() > 1) {
            Node left = pq.poll();  // 빈도 가장 낮은 두 개를
            Node right = pq.poll();
            Node parent = new Node('\0', left.freq + right.freq);
            parent.left = left;     // 합쳐서 새 노드 생성
            parent.right = right;
            pq.offer(parent);
        }

        // 3. 트리를 순회하며 코드 생성
        Map<Character, String> codes = new HashMap<>();
        buildCodesRecursive(pq.poll(), "", codes);
        return codes;
    }

    private void buildCodesRecursive(Node node, String code, Map<Character, String> codes) {
        if (node == null) return;
        if (node.left == null && node.right == null) {
            codes.put(node.ch, code.isEmpty() ? "0" : code);
            return;
        }
        buildCodesRecursive(node.left, code + "0", codes);
        buildCodesRecursive(node.right, code + "1", codes);
    }
}

접두사 코드 특성

허프만 코드는 ** 접두사 코드(prefix code)**입니다. 어떤 코드도 다른 코드의 접두사가 아니므로, 구분자 없이 연속으로 붙여도 유일하게 디코딩할 수 있습니다.

PLAINTEXT
A=0, B=10, C=11

인코딩: 01010110 → 0|10|10|11|0 → ABBCA
모호함 없음!

LZ77 알고리즘

핵심 아이디어

이전에 나왔던 문자열을 ** 참조 **합니다. "앞에서 5글자 전에 3글자 복사"처럼 (거리, 길이, 다음 문자) 트리플로 표현합니다.

PLAINTEXT
텍스트: "ABCABCABC"

처리:
위치 0: A → (0, 0, 'A')  새 문자
위치 1: B → (0, 0, 'B')  새 문자
위치 2: C → (0, 0, 'C')  새 문자
위치 3: ABCABC → (3, 6, ?)  3글자 전에서 6글자 복사!

슬라이딩 윈도우

LZ77은 ** 슬라이딩 윈도우 **를 사용합니다. 이미 처리한 부분(탐색 버퍼)에서 현재 위치의 문자열과 일치하는 가장 긴 부분을 찾습니다.

JAVA
// LZ77 개념적 인코딩
public List<int[]> compress(String text) {
    List<int[]> result = new ArrayList<>();
    int i = 0;
    int windowSize = 4096; // 탐색 윈도우 크기

    while (i < text.length()) {
        int bestDist = 0, bestLen = 0;

        // 슬라이딩 윈도우에서 가장 긴 매칭 찾기
        int searchStart = Math.max(0, i - windowSize);
        for (int j = searchStart; j < i; j++) {
            int len = 0;
            while (i + len < text.length() &&
                   text.charAt(j + len) == text.charAt(i + len)) {
                len++;
            }
            if (len > bestLen) {
                bestLen = len;
                bestDist = i - j;
            }
        }

        if (bestLen > 2) { // 매칭이 충분히 길면 참조
            result.add(new int[]{bestDist, bestLen});
            i += bestLen;
        } else { // 리터럴(원래 문자 그대로)
            result.add(new int[]{0, text.charAt(i)});
            i++;
        }
    }
    return result;
}

Deflate = LZ77 + 허프만

Deflate 는 gzip의 핵심 알고리즘으로, LZ77과 허프만 코딩을 결합합니다.

PLAINTEXT
1단계: LZ77으로 중복 문자열을 (거리, 길이) 쌍으로 치환
2단계: 허프만 코딩으로 결과를 최적 인코딩

원본 → [LZ77] → 중간 표현 → [허프만] → 압축 데이터

gzip은 Deflate + 헤더/체크섬으로 구성됩니다.

HTTP 압축

요청/응답 흐름

PLAINTEXT
클라이언트 → 서버:
  Accept-Encoding: gzip, deflate, br

서버 → 클라이언트:
  Content-Encoding: gzip
  (압축된 응답 본문)

주요 압축 형식

형식알고리즘압축률속도지원
gzipDeflate좋음빠름거의 모든 브라우저
br (Brotli)LZ77 + 허프만 변형매우 좋음중간모던 브라우저
zstdZstandard매우 좋음매우 빠름신규 지원 확대 중

Brotli는 gzip보다 20~30% 더 압축하지만, 압축 시간이 더 걸립니다. 정적 파일은 미리 압축해두고(pre-compression), 동적 응답은 gzip을 쓰는 전략이 많습니다.

Spring 응답 압축 설정

YAML
# application.yml
server:
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/plain
    min-response-size: 1024   # 1KB 이상인 응답만 압축
JAVA
// 프로그래밍 방식
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> customizer() {
    return factory -> {
        Compression compression = new Compression();
        compression.setEnabled(true);
        compression.setMimeTypes(new String[]{
            "application/json", "text/html", "text/plain"
        });
        compression.setMinResponseSize(DataSize.ofKilobytes(1));
        factory.setCompression(compression);
    };
}

압축 대상 선택

PLAINTEXT
✓ 압축해야 할 것: JSON, HTML, CSS, JS, XML, SVG
✗ 압축하면 안 되는 것: JPEG, PNG, MP4 (이미 압축됨 → 오히려 커질 수 있음)

압축률 비교

데이터 유형gzip 압축률Brotli 압축률
JSON API 응답60~80%65~85%
HTML 페이지70~85%75~90%
JavaScript60~75%65~80%
이미 압축된 이미지0~5%0~5%

백엔드/실무 연결

로그 압축

대량의 로그를 저장할 때 gzip으로 압축하면 10배 이상 줄어들기도 합니다.

JAVA
// Java에서 GZip 압축
try (GZIPOutputStream gzip = new GZIPOutputStream(new FileOutputStream("app.log.gz"))) {
    gzip.write(logData.getBytes());
}

CDN에서의 압축

CloudFlare, AWS CloudFront 같은 CDN은 자동으로 응답을 압축합니다. Origin 서버에서 압축하지 않아도 CDN 엣지에서 gzip/Brotli를 적용할 수 있습니다.

gRPC와 Protocol Buffers

gRPC는 JSON 대신 Protocol Buffers(바이너리 직렬화)를 사용하여 데이터 크기를 줄입니다. 여기에 gzip 압축까지 적용하면 전송량이 극적으로 줄어듭니다.

주의할 점

이미 압축된 데이터를 다시 압축하여 오히려 크기 증가

JPEG, PNG 등 이미 압축된 파일을 gzip으로 다시 압축하면 오히려 크기가 커질 수 있습니다. HTTP 압축 설정 시 이미지, 동영상 등은 제외해야 합니다.

Spring에서 작은 응답까지 압축하여 CPU 낭비

Spring Boot의 server.compression.min-response-size를 너무 작게 설정하면, 수십 바이트 응답까지 압축하여 CPU를 낭비합니다. 기본값(2KB) 이상으로 유지하는 것이 좋습니다.


정리

  • **허프만 코딩 **: 빈도 기반 가변 길이 코드로, 자주 나오는 데이터에 짧은 코드를 부여합니다
  • LZ77: 이전에 나온 문자열을 (거리, 길이)로 참조하여 중복을 제거합니다
  • Deflate (gzip): LZ77 + 허프만의 조합으로, 웹에서 가장 많이 사용됩니다
  • Brotli: gzip보다 20~30% 더 압축하며, 모던 브라우저가 지원합니다
  • Spring에서 server.compression.enabled=true로 쉽게 활성화할 수 있습니다
  • JSON, HTML 등 텍스트 데이터는 60~85% 압축되지만, 이미 압축된 미디어는 효과가 없습니다
댓글 로딩 중...