압축 알고리즘에 대해 알아보자
이번 포스팅에서는 소프트웨어의 압축 알고리즘에 대한 이야기를 해보려고 한다.
용량이 큰 빌드 결과물을 서버나 스토리지에 올리면서 zip, tar.gz, tar.zst 중 무엇을 쓸지 고민하는 개발자를 위한 글이다. 끝까지 읽으면 주요 압축 형식의 속도와 압축률이 어디서 갈리는지, 그리고 tar.gz와 tar.zst 중 무엇을 고를지 판단하는 기준을 얻을 수 있다.
사내 프로젝트의 배포 프로세스를 개선하는 업무를 맡게 되었다. 높은 용량의 빌드 결과물을 S3에 업로드해야 하는 구조였는데, 빌드 폴더의 용량이 곧 업로드 시간과 스토리지 비용에 직결된다는 것을 체감하게 되었다. 자연스럽게 "어떻게 하면 효율적으로 압축해서 올릴 수 있을까?"라는 고민이 시작되었다.
그런데 막상 압축에 대해 찾아보니, zip, gzip, zstd, bzip2, xz 등 종류가 생각보다 많았다. 이름은 비슷비슷한데 각각 뭐가 다른 건지, 어떤 상황에서 어떤 걸 써야 하는지 정리된 자료를 찾기가 쉽지 않았다. (압축이라고 하면 다 같은 거 아니었나 싶었는데, 세상은 넓고 압축 방법은 많았다.)
비교에 들어가기 전에 공통 뿌리 하나만 짚고 가자. 오늘날 주류 압축기는 거의 모두 1977년에 발표된 LZ77의 후손이다. LZ77은 이미 지나온 데이터의 일정 구간(sliding window)을 사전처럼 삼아, 반복되는 패턴을 "몇 글자 뒤로 가서 몇 글자를 복사하라"는 짧은 참조로 바꾼다. 윈도우가 클수록 멀리 떨어진 반복까지 잡지만 연산과 메모리가 더 든다. ZIP과 GZIP이 쓰는 DEFLATE는 여기에 허프만 부호화(빈도가 높은 심볼에 짧은 비트를 주는 엔트로피 코딩)를 붙인 것이다. LZ77이 형제 격인 LZ78과 어떻게 갈렸는지는 LZ77과 LZ78의 차이에 따로 정리했다.
압축 성능을 평가할 때는 두 가지 축을 고려해야 한다. 압축률(얼마나 작아지는가)과 압축 속도(얼마나 빨리 끝나는가)이다. 이론적으로 더 높은 압축률을 추구하면 더 많은 연산이 필요하기 때문에 압축 시간이 길어진다. 이 둘 사이의 절충점을 찾는 것이 현실적인 압축 전략의 핵심이다.
그럼 차근차근 여러 가지 압축 형식들을 비교해 보자.
ZIP
ZIP은 1989년 Phil Katz가 만든 파일 형식으로, 내부적으로 DEFLATE 알고리즘(LZ77 + Huffman coding의 조합)을 사용하여 압축한다. 여기서 중요한 점은, ZIP은 "압축 알고리즘"이 아니라 "파일 형식(format)"이라는 것이다. ZIP이라는 컨테이너 안에 DEFLATE라는 알고리즘이 들어가 있는 구조이다.
ZIP의 특징은 각 파일을 개별적으로 압축한다는 점이다. 이렇게 파일마다 따로 압축하는 구조를 비솔리드 아카이브(Non-solid Archive)라고 부른다. 모든 파일을 하나의 스트림으로 이어 붙여 한 번에 압축하는 반대쪽은 솔리드 아카이브다. 비솔리드 구조 덕분에 압축 파일 내의 특정 파일 하나만 꺼내는 것이 가능하다. 반면 파일 간의 중복 데이터를 활용하지 못하기 때문에, 뒤에서 설명할 tar.gz 방식에 비해 압축률이 낮을 수 있다.
Windows, macOS, Linux 등 대부분의 운영체제에서 별도의 소프트웨어 없이 기본 지원되므로, 크로스 플랫폼 호환성이 중요한 경우에 가장 무난한 선택이다.
GZIP (GNU Zip)
GZIP은 ZIP과 마찬가지로 내부적으로 DEFLATE 알고리즘을 사용한다. 같은 알고리즘인데 왜 별도의 형식이 존재할까? ZIP이 여러 파일을 하나의 아카이브에 담는 "컨테이너" 역할까지 수행하는 반면, GZIP은 단일 파일(또는 단일 스트림)의 압축에 특화된 형식이다.
그래서 여러 파일이나 디렉토리를 GZIP으로 압축하려면, 먼저 TAR로 하나의 아카이브로 묶은 뒤 GZIP으로 압축하는 2단계를 거쳐야 한다. 이것이 바로 .tar.gz(또는 .tgz) 파일이 탄생하는 과정이다.
GZIP의 파일 구조는 RFC 1952에 명세되어 있으며, 상당히 단순하다. 10바이트 고정 헤더 + 선택적 확장 헤더(원본 파일명, 코멘트 등) + DEFLATE 압축 데이터 + 8바이트 트레일러(CRC-32 체크섬과 원본 크기)로 이루어진다. 여기서 CRC-32는 압축 해제 후 데이터가 원본과 동일한지를 검증하는 무결성 체크 장치이다. 즉 GZIP은 DEFLATE 압축 데이터를 감싸는 가벼운 래퍼(wrapper)인 셈이다.
DEFLATE 내부에서 사용하는 sliding window의 크기는 최대 32KB이다. 이 크기가 GZIP 압축률의 상한을 결정하는 중요한 요소인데, 32KB 이상 떨어진 패턴은 참조할 수 없기 때문이다. 또한 GZIP은 압축 레벨을 1부터 9까지 지정할 수 있다. 레벨 1은 빠르지만 압축률이 낮고(약 60%), 레벨 9는 느리지만 최대 압축률(약 75%)을 달성한다. 기본값은 레벨 6으로, 속도와 압축률 사이의 균형점이다.
Unix/Linux 환경에서 소스 코드 배포, 로그 파일 압축, 소프트웨어 패키지 등에 표준처럼 사용되고 있다. 웹 서버의 HTTP 압축(Content-Encoding: gzip)에서도 여전히 기본값으로 널리 쓰이고 있지만, 이 영역에서는 점차 Brotli로 대체되는 추세이다.
ZSTD (Zstandard)
ZSTD는 Meta(구 Facebook)의 Yann Collet이 개발하여 2016년에 오픈소스로 공개한 압축 알고리즘이다. GZIP과 비슷한 수준의 압축률을 유지하면서도, 압축/해제 속도가 압도적으로 빠르다는 것이 핵심 강점이다.
ZSTD의 내부 구조는 크게 세 단계로 나뉜다. 먼저 LZ77 계열의 매치 파인더(Match Finder) 가 입력 데이터에서 반복 패턴을 찾아낸다. 그 다음 찾아낸 매치 정보(리터럴, 매치 길이, 오프셋)를 시퀀스로 인코딩한다. 마지막으로 이 시퀀스를 엔트로피 코딩으로 압축하는데, 여기서 GZIP의 허프만 부호화 대신 FSE(Finite State Entropy) 라는 방식을 사용한다. FSE는 ANS(Asymmetric Numeral Systems) 이론에 기반한 엔트로피 코더다. ANS는 심볼을 하나 처리할 때마다 정수 하나로 된 상태값을 갱신하면서, 자주 나오는 심볼일수록 적은 비트를 내보낸다. 허프만 부호화에 가까운 속도로 산술 부호화(Arithmetic Coding)에 가까운 압축률을 내는 것이 목표다. 허프만 부호화가 심볼당 정수 비트만 할당할 수 있는 반면, FSE는 분수 비트까지 표현할 수 있어 이론적 최적값에 더 가까운 압축이 가능하다. (이름이 거창하지만, 핵심은 "같은 데이터를 더 적은 비트로 표현하는 더 똑똑한 방법"이라고 이해하면 된다.)
매치 파인더도 압축 레벨에 따라 전략이 달라진다. 낮은 레벨(1~4)에서는 단순 해시 테이블로 빠르게 검색하고, 중간 레벨(5~12)에서는 여러 후보를 비교한 뒤 가장 좋은 매치를 선택하는 Lazy 방식을, 높은 레벨(13~22)에서는 이진 트리와 동적 프로그래밍을 활용해 최적에 가까운 매치를 찾아낸다. 이렇게 레벨을 1부터 22까지 세밀하게 조절할 수 있어, 실시간 전송에는 낮은 레벨을, 아카이빙에는 높은 레벨을 사용하는 식으로 유연하게 대응할 수 있다.
Silesia Corpus 벤치마크 기준으로, ZSTD 기본 레벨(3)의 압축 속도는 약 300MB/s, 해제 속도는 약 1,200MB/s이다. 반면 GZIP 기본 레벨(6)은 압축 약 34MB/s, 해제 약 380MB/s에 그친다. 압축은 약 8배, 해제는 약 3배 빠르면서도 압축률은 ZSTD(3.17) : GZIP(3.09)로 오히려 앞선다. 이 숫자가 ZSTD의 트레이드오프 개선을 가장 직관적으로 보여준다.
현재 생태계에서의 채택도 빠르게 확대되고 있다. Linux 커널의 모듈 압축과 파일시스템 투명 압축에 사용되고 있으며, Arch Linux, Fedora, Debian/Ubuntu 등 주요 배포판에서 패키지 압축 기본 형식으로 채택했다. 2025년 2월 릴리스된 v1.5.7부터는 멀티스레드 압축이 기본 활성화(최대 4스레드)되어, 싱글스레드만 지원하는 GZIP과의 실질적인 속도 격차는 더욱 벌어졌다. AWS는 내부 서비스에서 gzip을 zstd로 전환하여 S3 스토리지를 약 30% 절감했다고 밝힌 바 있다.
BZIP2
BZIP2는 여러 단계의 변환을 거쳐 압축을 수행한다. 핵심 파이프라인은 다음과 같다.
- RLE(Run-Length Encoding): 초기 데이터의 연속 반복을 줄임
- BWT(Burrows-Wheeler Transform): 데이터를 압축하기 쉬운 형태로 재배열
- MTF(Move-to-Front Transform): BWT의 출력을 숫자 배열로 변환
- RLE: MTF 결과의 반복을 다시 줄임
- Huffman Coding: 최종적으로 빈도 기반 부호화로 압축
GZIP보다 높은 압축률을 제공하지만, 압축 속도와 해제 속도 모두 느리다. 고압축률이 필요하면서 속도가 덜 중요한 아카이빙 용도로 사용되어 왔다.
다만 마지막 릴리스가 2019년(v1.0.8)이며, 활발한 개발은 이루어지지 않고 있다. 압축률과 속도 양면에서 ZSTD가 BZIP2를 능가하는 벤치마크 결과가 나오면서, 새로운 프로젝트에서는 ZSTD를 선택하는 경우가 늘고 있다.
XZ
XZ는 LZMA2를 사용하는 압축 형식이다. LZMA(Lempel-Ziv-Markov chain Algorithm)는 Igor Pavlov가 개발한 알고리즘으로, LZ77 기반의 사전 압축에 범위 부호화(Range Encoding)를 결합한 것이다. LZMA2는 LZMA의 "개선된 버전"이라기보다는, LZMA 스트림을 감싸는 컨테이너 형식에 가깝다. 멀티스레드 압축/해제 지원과 비압축 데이터의 효율적인 처리가 추가된 것이 핵심이다.
이 글에서 다루는 형식 중 가장 높은 압축률을 자랑한다. 그 대신 압축 속도가 매우 느리고, 메모리 사용량도 크다. 저장 공간 절약이 최우선인 아카이빙에 적합하다.
다만 2024년 3월, XZ의 핵심 라이브러리인 xz-utils에서 백도어가 발견되는 심각한 보안 사건(CVE-2024-3094)이 있었다. 2년간의 소셜 엔지니어링 공격으로 메인테이너 권한이 탈취되어, CVSS 10.0(최고 심각도)을 받았다. 주요 배포판에서 즉시 안전한 버전으로 롤백했지만, 이 사건은 오픈소스 공급망 보안에 대한 경각심을 일깨워 주었다. (XZ 자체의 기술적 가치는 여전하지만, 도구 선택 시 이런 맥락도 알아두면 좋다.)
TAR
TAR(Tape Archive)는 그 자체로 압축 알고리즘이 아니다. 여러 파일과 디렉토리를 하나의 아카이브 파일로 묶는 도구이다. 이름에서 알 수 있듯이 원래 자기 테이프(Tape) 백업을 위해 만들어진 포맷인데, 테이프라는 매체 특성상 데이터를 순차적으로 쭉 이어 붙이는 구조가 자연스러웠다.
TAR의 내부 구조는 의외로 단순하다. 모든 데이터는 512바이트 블록 단위로 처리된다. 각 파일 앞에 512바이트짜리 헤더 블록이 붙는데, 이 헤더에 파일명(최대 100바이트), 파일 모드, 소유자 UID/GID, 파일 크기, 수정 시간, 체크섬 등의 메타데이터가 들어간다. 파일 데이터는 헤더 뒤에 이어지며, 512바이트의 배수로 패딩된다. 아카이브의 끝은 512바이트짜리 제로 블록 두 개로 표시된다. 현대의 대부분의 TAR 구현체는 POSIX에서 정의한 UStar(Unix Standard TAR) 형식을 따르며, 이 형식은 더 긴 파일명(최대 256바이트)과 추가 메타데이터 필드를 지원한다.
핵심은 TAR가 파일의 권한, 소유권, 타임스탬프, 심볼릭 링크 같은 Unix 파일시스템 메타데이터를 그대로 보존한다는 점이다. ZIP은 이러한 Unix 고유 메타데이터를 완벽히 보존하지 못하는 경우가 있어, 서버 환경에서의 배포에는 TAR가 더 적합하다.
TAR 자체는 크기를 줄여주지 않는다. 오히려 헤더와 패딩 때문에 원본보다 약간 커진다. 실제 압축은 GZIP, BZIP2, XZ, ZSTD 등과 결합하여 수행한다. 이것이 .tar.gz, .tar.bz2, .tar.xz, .tar.zst 같은 확장자가 존재하는 이유이다. TAR가 "묶기"를, 압축 도구가 "줄이기"를 각각 담당하는 Unix 철학("한 가지 일을 잘 하라") 의 전형적인 사례이다.
Unix/Linux 환경에서 표준 아카이빙 도구로 사용되며, Windows에서는 별도의 소프트웨어(7-Zip 등)가 필요하다.
잠깐, Brotli도 알아두자
프론트엔드 개발자라면 Brotli도 짚고 넘어갈 필요가 있다. Brotli는 Google이 개발한 압축 알고리즘으로, 2015년에 HTTP 스트림 압축 사양(Content-Encoding: br)으로 표준화되었다.
모든 주요 브라우저에서 HTTPS 환경에서 지원되며(지원률 96% 이상), GZIP 대비 약 15~25% 더 높은 압축률을 보인다. 특히 JavaScript, CSS, HTML 같은 텍스트 기반 정적 파일의 압축에 효과적이다. Cloudflare 등 주요 CDN에서 기본 압축 방식으로 채택하고 있으며, 현대 웹 최적화의 모범 사례는 "Brotli 우선, GZIP 폴백"으로 정리된다.
빌드 결과물을 S3에 올려 CDN으로 서빙하는 구조라면, 정적 파일을 사전에 Brotli로 압축해 두는 것이 네트워크 전송 최적화에 큰 도움이 된다. (지금 당장 고민하기에는 자료가 많지 않아 도입하기 어렵지만 추후 고민의 여지가 될 수 있기에 개발자라면 알아두면 좋을 것 같다.)
tar.gz가 zip보다 압축률이 높은 이유
이것은 솔리드 아카이브(Solid Archive) 와 비솔리드 아카이브(Non-solid Archive) 의 차이에서 비롯된다.
tar.gz는 TAR로 모든 파일을 하나의 연속된 데이터 스트림으로 묶은 뒤, GZIP이 이 전체 스트림을 한 번에 압축한다. 이 과정에서 파일 간의 중복 데이터를 인식하고 활용할 수 있다. 이것이 솔리드 아카이브 방식이다. 예를 들어 빌드 폴더에 구조가 유사한 JavaScript 번들 파일이 수십 개 있다면, 파일 A에서 등장한 패턴이 파일 B에서도 반복될 때 이를 하나의 참조로 처리할 수 있다. 또한 파일마다 별도의 헤더, 체크섬, 목차(Table of Contents)를 기록할 필요가 없으므로 메타데이터 오버헤드도 줄어든다.
반면 ZIP은 각 파일을 독립적으로 압축하는 비솔리드 방식이므로, 파일 간 중복을 활용하지 못한다. 파일 A와 파일 B에 동일한 코드 블록이 있더라도, 각각의 DEFLATE 스트림은 상대방의 존재를 모른다. 일반적으로 tar.gz가 ZIP 대비 5~15% 더 높은 압축률을 보이는 이유가 바로 이것이다. 특히 유사한 구조의 파일이 대량으로 포함된 빌드 결과물일수록 이 격차는 더 벌어진다.
다만 솔리드 아카이브에도 뚜렷한 단점이 있다.
- 압축 파일 내의 특정 파일 하나만 꺼내려면 그 파일 앞에 위치한 모든 데이터를 먼저 해제해야 한다. 모든 파일이 하나의 스트림으로 이어져 있기 때문에, 중간 지점으로 바로 건너뛸 수가 없는 것이다. ZIP은 개별 파일에 대한 랜덤 액세스가 가능하므로, 아카이브에서 특정 파일만 빈번하게 꺼내야 하는 상황이라면 ZIP이 더 적합할 수 있다.
- 아카이브의 일부가 손상되면 손상 지점 이후의 모든 데이터가 복구 불가능해질 수 있다. 비솔리드 방식에서는 손상된 파일 하나만 잃고 나머지는 살릴 수 있는 것과 대비된다.
2024년의 선택은 tar.gz, 지금은?
이 절은 2026년에 덧붙였다. 필자가 당시 tar.gz를 선택한 이유는 호환성과 안정성이었다. S3 업로드 이후 다양한 환경에서 해제해야 했기 때문에, 어디서든 지원되는 tar.gz가 안전한 선택이었다.
하지만 지금 같은 상황이 다시 온다면 tar.zst(TAR + ZSTD) 에 대해 고민해볼 것이다. 앞서 살펴본 벤치마크 수치를 다시 떠올려 보자.
GZIP 기본 레벨의 압축 속도가 34MB/s인데, ZSTD 기본 레벨은 300MB/s이다. 2GB 빌드 폴더 기준으로 단순 계산하면 GZIP은 약 60초, ZSTD는 약 7초 만에 압축이 끝난다. 여기에 ZSTD v1.5.7부터 기본 활성화된 멀티스레드(최대 4스레드)까지 고려하면, 실제 체감 시간은 더 짧아진다. CI/CD 파이프라인에서 이 차이는 배포 한 사이클마다 누적되는 것이니, 무시할 수 없는 수치이다.
# tar.zst 생성 (멀티스레드 자동 활용)
tar --zstd -cf archive.tar.zst directory/
# 또는 압축 레벨 지정 (-T0은 사용 가능한 모든 코어 활용)
tar -cf archive.tar.zst -I 'zstd -3 -T0' directory/압축률 면에서도 ZSTD는 GZIP과 동등하거나 오히려 앞서기 때문에, "속도를 위해 압축률을 포기한다"는 트레이드오프가 사실상 존재하지 않는다. 속도도 빠르고, 결과물도 작다.
다만 수신 측 환경에서 zstd 해제가 가능한지는 반드시 확인해야 한다. 주요 Linux 배포판에는 이미 기본 설치되어 있고, macOS에서도 Homebrew(brew install zstd)로 간단히 설치할 수 있다. 하지만 레거시 시스템이나 최소 설치 환경에서는 별도 설치가 필요할 수 있으므로, 팀 내 모든 환경을 사전에 점검하는 것이 중요하다. 호환성이 최우선이라면 여전히 tar.gz가 가장 무난한 선택이다.
한눈에 비교하기
| 형식 | 알고리즘 | 압축률 | 속도 | 특징 |
|---|---|---|---|---|
| ZIP | DEFLATE | 보통 | 빠름 | 크로스 플랫폼, 비솔리드 |
| GZIP | DEFLATE | 보통 | 빠름 | 단일 스트림, TAR와 결합 |
| ZSTD | Zstandard | 높음 | 매우 빠름 | 압축 레벨 조절, 최신 표준 |
| BZIP2 | BWT+MTF+Huffman | 높음 | 느림 | 개발 정체 |
| XZ | LZMA2 | 매우 높음 | 매우 느림 | 최고 압축률, 보안 사건 주의 |
| Brotli | Brotli | 높음 | 보통 | 웹 최적화 특화 |
마치며
압축이라는 주제를 파보기 전까지는, 솔직히 "그냥 zip으로 묶으면 되는 거 아닌가?"라고 생각했다. 하지만 실제로 2GB가 넘는 빌드 폴더를 다루다 보니, 어떤 알고리즘을 선택하느냐에 따라 업로드 시간과 비용이 유의미하게 달라진다는 것을 체감할 수 있었다.
각 압축 형식은 저마다의 설계 철학과 트레이드오프를 가지고 있다. ZIP의 호환성, GZIP의 범용성, ZSTD의 속도, XZ의 압축률까지 어느 것이 "최고"라고 단정할 수 없고, 프로젝트의 맥락에 따라 적합한 선택이 달라진다.
평소에 무심코 사용하던 도구의 원리를 파보는 것은, 다음에 비슷한 문제를 만났을 때 더 나은 판단을 할 수 있게 해준다. 이 글이 압축 알고리즘을 선택해야 하는 누군가에게 작은 참고가 되었기를 바란다.