탐구 생활/Python 과 zstd

Python 과 zstd - middleware 적용하기

개발프로브 2025. 10. 14. 21:55

이 시리즈 첫글에서 Python 3.14 정식 배포 버전에서 zstd 알고리즘 native 지원에 더 눈길이 갔던 이유가 I/O Bounded API Server 의 응답속도 개선에 주의를 기울이고 있기 때문이라고 말했었습니다. 특히 지금 맡은 업무가 웹 브라우저와 직접적으로 상호작용하는 그렇다면 Python 으로 작성된 Web Server (FastAPI, Django) 에서 zstd 를 어떻게 사용할 수 있을지 공유하고자 합니다.


전체 흐름 점검하기

우선 가장먼저 드는 의문점은 누가 언제 무엇을 인코딩하고 디코딩하냐는 겁니다. 정말 다양한 기준이 있겟지만 웹서버를 기준으로만 정리해보겠습니다.  이제부터 말하는 클라이언트는 웹브라우저(PC / Mobile) 이며 서버는 웹 서버입니다. 그리고 주된 "흐름"은 파일 데이터가 아니라 HTML, CSS, JS, JSON 과 같은 현대적인 웹서비를 만드는데 필요한 포맷의 데이터를 주고받는 것을 지칭합니다.

데이터 인코딩 / 디코딩 과정을 간략하게 본다면

클라이언트: 디코딩의 책임자

일반적인 HTTP 에서 연결이 이어지는 과정은 클라이언트와 서버간 SYNC, ACK 의 과정이 필요합니다. 이때 클라이언트가 먼저 서버에게 요청을 보내면서 연결이 확립되죠(server sent event 조차도 처음에는 클라이언트가 GET 요청을 보냅니다). 그렇다면 이때 클라이언트가 "나는 x, y, z 등등의 인코딩 데이터를 식별하고 디코딩할 수 있다" 는 정보를 서버에게 보내줘야 할겁니다. HTTP 는 무상태(Stateless) 프로토콜이기 때문에 당연히 이러한 정보는 모든 요청정보에서 반복적으로 등장할 겁니다.

 

RFC 9110 에서는 이러한 Spec을 Accept-Encoding 헤더를 보내는것으로 표준을 잡았습니다. 또한 q-factor 라는 값을 추가하여 클라이언트가 처리할 수 있는 인코딩 알고리즘과 선호하는 인코딩 알고리즘을 서버에서 해석할 수 있도록 하였습니다.

 

이런식으로 값이 구성됩니다.

Accept-Encoding: gzip;q=1.0, br;q=0.9, zstd;q=0.1

// 아래와 같은 내용들을 사용가능,
Accept-Encoding: gzip
Accept-Encoding: compress
Accept-Encoding: deflate
Accept-Encoding: br
Accept-Encoding: identity
Accept-Encoding: *

서버: 인코딩의 책임자

서버는 Accept-Encoding 을 읽고 q-factor 의 우선순위에 따라 자신이 인코딩 할 수 있는 방식으로 Response 를 인코딩하여 반환합니다. 이때 서버는 Content-Encoding 헤더에 이 Response 가 어떤 알고리즘으로 인코딩되었는지 명시해서 보내야합니다.

Content-Encoding: gzip

 

만약 인코딩 방식과 Content-Encoding 헤더에 담긴 이름이 다르다면 클라이언트를 데이터를 제대로 디코딩할 수 없고 브라우저 사용자에게 데이터가 표출되지 않는 오류가 발생하게 됩니다. 그리고 현대 웹프레임워크에서 이런 "인코딩" 과정은 비즈니스적인 관심사는 아니고 프레임워크 레벨에서의 관심사이기 때문에 필터(Filter) 혹은 미들웨어(Middleware) 단에서 구현되기 마련입니다.


Python Backend 시스템에 도입하기

결국 Python Web Server Framework 에서 클라이언트에게 응답을 인코딩하는 역할은 Middleware 에게 주어져있습니다. FastAPI, Django 모두 GzipMiddleware 를 지원하며 이를 이용해서 단순 HTTP 뿐만 아니라 스트림 데이터도 인코딩할 수 있습니다.

 

아래는 찾아본 Middleware 등의 정보를 간단히 공유합니다. Middleware 를 적용하는 방법은 너무 간단하고, 각 프로젝트의 README 에 잘 나타나 있으므로 굳이 내용을 추가하지 않겠습니다.

Middleware (ASGI)

ASGI 에서는 별 10개 이상 받은 Middleware 가 zstd-asgi, cramjam-asgi 두개를 찾을 수 있었습니다. 

 

zstd-asgi 와 cramjam-asgi 를 비교했을때 zstd-asgi 가 약간 더 빠른 압축 속도(Python 3.13.9-slim 기준)를 보여주었습니다. 다양한 환경에서 테스트가 필요하겠지만 모든 요청에 대응하는 Middleware 이므로 약간의 성능 차이가 크게 작용할 수 있다는 점, 그리고 zstd-asgi 는 Python 3.14 를 까지 지원하는 업데트가 최근에 일어났다는 점을 봤을때 zstd-asgi 가 더 나은 선택인 것으로 보입니다. (CPU 사용은 별 차이가 없었음)

Middleware (WSGI)

Django 에서 사용할 수 있는 Middleware 로는 가장 최근까지 유지보수되었던 django-http-compression 을 발견했습니다.

 

djang-http-compression 는 굉장히 인상적이었습니다. RFC 9110 을 준수하기 위한 수 많은 옵션들과 DEFLATE, Brotli, Zstandard 등을 모두 지원하는 Middleware 를 구현해뒀다는 점이 인상적이었고, 역시 Python 백엔드 생태계에서는 Django 가 가장 성숙하다는 것을 다시 한번 확인할 수 있었습니다.


인코딩을 꼭 해야하나요?

우리는 근본적인 질문을 할 수 있습니다. 도대체 왜 인코딩을 해야하는걸까요? 인코딩을 하면 서버의 CPU 를 사용하게 되고 오버헤드가 추가 될겁니다. 그건 클라이언트(브라우저)도 마찬가지죠. 어떤 이득이 있기 때문에 웹 서버 생태계에서 인코딩을 위한 Middleware 들이 개발되고 있는걸까요?

 

인코딩을 적용하면 네트워크 레벨로 전송되는 절대저인 데이터량이 적어집니다. 당연히 브라우저가 다운로드 받아야하는 데이터량도 적어지는 것이죠. 이러한 이유로 네트워크 트래픽 감소에 따른 비용 절감과 다운로드 속도 향상으로 전반적인 웹 페이지 로딩 속도 개선을 기대할 수 있습니다. 물론 인코딩/디코딩에 따른 오버헤드(시간 및 CPU 사용)는 있으므로 트레이드 오프를 잘 보고 선택해야합니다.


지금 당장 우리 프로덕트에 적용해도 될까?

단적으로 봐도 JSON Request/Response 를 처리하는 API 서버는 DEFLATE 알고리즘을 이용하는 Gzip Middleware 를 Zstd Middleware 로 교체하는게 이득이 있을 것으로 보입니다.

 

아직 safari 에서는 zstd를 완전히 지원하는게 아닙니다.

 

다만, 25년 10월을 기준으로 아직 모든 브라우저들이 zstd 알고리즘을 지원하는 것은 아닙니다. 따라서 단순히 Gzip Middleware 를 걷어내고 Zstd Middleware 로 갈아탄다! 의사결정을 하기 보다는 django-http-compression 와 같이 여러 알고리즘을 같이 적용할 수 있는 미들웨어를 선택하는 것이 현명할 것으로 보입니다.

 

brotli 압축방식은 전반적으로 지원됩니다.


참고

zstd asgi: https://github.com/tuffnatty/zstd-asgi

zstd starlette: https://github.com/developmentseed/starlette-cramjam

zstd django: https://github.com/adamchainz/django-http-compression

zstd 지원 브라우저 정보: https://caniuse.com/zstd

cloudflare blog: https://blog.cloudflare.com/new-standards/#introducing-zstandard-compression