목차
앞의 두 방법은 서로 못 잡는 자리가 정확히 반대였다. 그러면 둘을 겹치는 게 당연한 수순이다.
겹치는 데는 순서가 있다
두 방법을 한 번에 쓸 수는 없다. 하나를 하고 나서 그 결과에 다른 하나를 하는 것이다. 그러면 어느 쪽이 먼저인가.
반복 가리키기가 먼저고 짧은 이름 주기가 나중이다. 이 순서로 겹친 것을 DEFLATE라고 부른다. 이름은 낯설어도 지금 이 글이 여러분에게 오는 길에 거의 확실히 한 번은 지나온 방식이다.
1단계가 크기를 줄이면서 2단계가 먹을 치우침까지 만든다. 순서를 바꾸면 이 연료가 생기지 않는다.
원본이 두 단계를 거치며 두 번 짧아진다. 가운데 결과는 사람이 볼 일이 없는 중간 산출물이고, 실제로 저장되거나 전송되는 것은 오른쪽 끝이다.
왜 이 순서인가
순서를 바꾸면 왜 안 되는지가 이 글에서 제일 중요한 대목이다. 답은 앞 단계가 뒷 단계의 연료를 만들어 주기 때문이다.
노트로 돌아가 보자. 학생이 “위 세 줄, 12자”라는 표시를 여러 번 쓰기 시작하면, 그 표시 자체가 노트에 자주 등장하는 것이 된다. 반복을 지우고 나면 “가리킴”이라는 새로운 단골이 생긴다.
데이터도 똑같다. 반복을 가리킴으로 바꾸고 나면 결과에는 이런 것들이 잔뜩 남는다.
- 짧은 거리를 가리키는 값이 유난히 자주 나온다. 반복은 대개 가까이 있어서다
- 길이 3~5짜리 가리킴이 긴 것보다 훨씬 많다
- 남은 원본 글자들도 원래의 치우침을 그대로 갖고 있다
즉 반복을 걷어낸 결과는 처음보다 더 치우쳐 있다. 그리고 치우침은 짧은 이름 주기의 연료였다. 앞 단계가 뒷 단계를 위해 밥상을 차려 준 셈이다.
거꾸로 했다면 이 이득이 없다. 짧은 이름을 먼저 붙여 비트로 만들어 버리면 원래 있던 반복이 비트 경계에 흩어져 눈에 안 보이게 된다. 12바이트짜리 반복이 어떤 곳에서는 27비트, 다른 곳에서는 31비트가 되어 더 이상 같은 조각이 아니게 되는 것이다. 찾을 수 있었던 반복을 스스로 지워 버리는 꼴이다.
구간마다 다르게 처리한다
DEFLATE는 데이터를 통째로 한 번에 처리하지 않는다. 적당한 구간으로 잘라서 구간마다 방식을 따로 고른다. 고를 수 있는 것은 셋이다.
| 구간 처리 방식 | 언제 고르나 |
|---|---|
| 그냥 원본으로 둔다 | 줄여 봐야 손해인 구간. 이미 압축된 데이터가 섞여 있을 때 |
| 미리 정해 둔 이름표를 쓴다 | 구간이 짧아서 이름표를 새로 만들어 보내면 배보다 배꼽이 클 때 |
| 이 구간 전용 이름표를 만든다 | 구간이 충분히 길어서 맞춤 이름표가 값을 할 때 |
앞 글에서 “이름표도 같이 보내야 한다”고 했던 대가가 여기서 관리된다. 짧은 구간에는 이름표를 안 만들고, 안 줄어드는 구간은 아예 손대지 않는다. 압축이 데이터를 무조건 부풀리지 않는 이유가 이 장치다.
구간으로 자르는 데는 다른 값도 있다. 다 모으기 전에 앞부터 내보낼 수 있다. 서버가 응답 전체를 만들어 놓고 압축을 시작하는 게 아니라, 만들어지는 대로 구간씩 압축해 흘려보낼 수 있다는 뜻이다. 큰 응답을 다루는 자리에서 이 성질이 크게 쓰인다.
gzip·zip·zlib은 무엇이 다른가
여기서 초중급이 가장 자주 헷갈리는 것을 갈라 둔다. 셋 다 안에서는 같은 DEFLATE를 쓴다. 다른 것은 그 주위에 무엇을 두르느냐다.
| 이름 | 정체 | 특징 |
|---|---|---|
| DEFLATE | 압축 방식 그 자체 | 줄이는 규칙만 정한다. 파일이라는 개념이 없다 |
| zlib | DEFLATE + 아주 짧은 머리말 + 검사값 | 프로그램 안에서 데이터 덩어리를 줄일 때 쓴다 |
| gzip | DEFLATE + 머리말(원래 파일 이름·시각) + 검사값 | 파일 하나를 줄인 것. 여러 파일을 못 담는다 |
| zip | 여러 파일을 담는 상자 + 각 파일을 대개 DEFLATE로 압축 | 파일 목록을 들고 있어 하나만 꺼내 풀 수 있다 |
.tar.gz라는 이름이 왜 두 겹인지가 여기서 풀린다. gzip은 파일 하나만 다루므로 먼저 tar로 여러 파일을 한 덩어리로 이어 붙이고, 그 덩어리를 gzip으로 줄인다.
그리고 이 차이가 크기로 나타난다. zip은 파일을 하나씩 따로 압축하니 파일들 사이에 걸친 반복을 못 본다. .tar.gz는 전부 이어 붙인 뒤 압축하니 파일 A에 나온 문장을 파일 B에서 가리킬 수 있다. 비슷한 파일이 많을수록 이 차이가 커진다.
대신 zip이 얻는 것도 분명하다. 하나만 꺼내 풀 수 있다. .tar.gz에서 파일 하나를 꺼내려면 앞에서부터 죽 풀어 가야 한다. 앞 글들에서 본 “압축하면 중간을 못 읽는다”가 여기서 실물로 나타난 것이다.
압축 레벨은 노력의 양이다
압축 프로그램에 대개 1부터 9까지의 레벨이 있다. 이 값이 바꾸는 것은 압축 방식이 아니라 반복을 찾는 데 들이는 노력이다.
- 낮은 레벨은 적당히 찾다가 만다. 그럴듯한 반복을 찾으면 더 좋은 게 있는지 안 보고 쓴다
- 높은 레벨은 더 오래 뒤진다. 더 길고 더 가까운 반복을 찾아내 결과가 작아진다
여기서 실무 감각 하나가 나온다. 레벨을 올릴 때 압축 시간은 가파르게 늘지만 결과 크기는 완만하게만 준다. 그래서 대부분의 상황에서 최고 레벨은 남는 장사가 아니다.
그리고 푸는 시간은 레벨과 거의 무관하다. 어느 레벨로 줄였든 푸는 쪽이 하는 일은 “가리킴을 펼치고 이름을 되돌리는 것”으로 같기 때문이다. 한 번 압축해 두고 여러 번 내려보내는 자리에서는 그래서 높은 레벨이 값을 한다. 압축은 한 번이고 푸는 건 수만 번이니까.
더 나은 것들이 나왔다
DEFLATE는 1990년대 초의 설계다. 그 뒤로 나온 것들이 무엇을 바꿨는지 보면 압축의 원리가 더 선명해진다.
- brotli - 웹에서 흔히 나오는 문자열(
<!DOCTYPE html>같은 것)을 미리 사전에 담아 두고 시작한다. 처음 보는 데이터인데도 이미 앞에서 본 것처럼 가리킬 수 있으니 작은 파일에서 특히 유리하다. 창도 훨씬 크다 - zstd - 같은 크기를 훨씬 빠르게 뽑는 데 무게를 뒀다. 우리 데이터로 사전을 만들어 넘겨줄 수 있어서, 비슷한 작은 조각을 수없이 다루는 자리에 잘 맞는다
둘 다 원리를 새로 발명한 게 아니다. 여전히 반복을 가리키고 자주 나오는 것에 짧은 이름을 준다. 바뀐 것은 창을 얼마나 크게 잡고, 시작할 때 무엇을 이미 알고 있느냐다. 압축의 뼈대는 이 시리즈에서 본 둘에서 크게 벗어나지 않는다.
실무에서: 켜는 자리가 앱 코드가 아니다
웹 응답 압축은 백엔드 개발자가 가장 자주 만나는 압축이다. 그런데 그 코드는 대개 애플리케이션 안에 없다.
브라우저가 “압축된 것도 받을 수 있다”고 알리고 서버가 그에 맞춰 답하는 헤더 주고받기가 먼저 있고, 실제로 줄이는 일은 모든 요청이 똑같이 겪는 구간에서 처리된다. 미들웨어의 흔한 쓰임 중 하나가 정확히 이것이고, 더 앞단인 리버스 프록시나 로드 밸런서에서 거는 경우도 흔하다.
그래서 응답이 압축되어 나가는지 확인할 곳은 핸들러가 아니라 앞단이다. 앱 코드를 아무리 뒤져도 gzip이라는 낱말이 안 나오는데 응답에는 Content-Encoding: gzip이 붙어 있는 상황이 정상이라는 뜻이다.
여기서 실제로 겪는 사고가 둘 있다.
- 두 번 걸린다. 앱과 프록시가 각각 압축을 걸어 두면 CPU만 두 번 태우고 크기는 거의 그대로다. 이미 압축된 것은 다시 안 준다는 원리 그대로다
- 압축 여부가 캐시와 엇갈린다. 같은 주소인데 어떤 요청에는 압축된 응답을, 어떤 요청에는 원본을 준다면 중간 캐시가 둘을 구별해야 한다. 안 그러면 압축을 못 푸는 클라이언트에게 압축된 응답이 나간다
정리
- DEFLATE는 반복 가리키기 다음에 짧은 이름 주기를 얹은 것이다. 이 순서가 핵심이다.
- 순서가 그런 이유는 반복을 걷어낸 결과가 더 치우쳐 있기 때문이다. 앞 단계가 뒷 단계의 연료를 만든다.
- 데이터를 구간으로 잘라 구간마다 방식을 고른다. 안 줄어드는 구간은 원본 그대로 두어 부풀지 않게 한다.
- gzip은 파일 하나, zip은 여러 파일을 담는 상자다. 안에서 줄이는 방식은 같다.
.tar.gz가 zip보다 대체로 작은 것은 이어 붙인 뒤 압축해 파일 사이의 반복까지 잡기 때문이다.- 레벨은 반복을 찾는 노력이다. 푸는 시간은 레벨과 거의 무관하니, 한 번 줄여 여러 번 보내는 자리에서는 높은 레벨이 값을 한다.
- 웹 응답 압축은 대개 앱 코드 밖에 걸려 있다. 두 번 걸지 않는지부터 확인한다.
다음 글에서 선을 하나 넘는다. 지금까지는 한 비트도 안 잃고 되돌렸는데, 되돌리기를 포기하면 무엇을 얻는가.