티스토리 뷰

Spring

[SpringBoot] 톰캣(Tomcat)에서 server.compression.min-response-size가 제대로 동작하지 않는 이유 분석하기

망나니개발자 2025. 2. 25. 10:00
반응형



 

1. 톰캣에서 server.compression.min-response-size가
제대로 동작하지 않는 이유 분석하기


아래의 내용은 HTTP 요청 중에서 content-type: application/json인 경우를 전제로 한다. text/plain 등의 경우에는 정상적으로 작동하고 있다.

 

 

[ 스프링 부트의 server.compression 설정들 ]

스프링 부트에서는 다음의 압축 설정을 통해 압축을 활성화하고, 압축을 적용할 조건을 명시할 수 있다. 해당 설정을 추가하고 클라이언트가 HTTP 요청에 Accept-Encoding: gzip 헤더를 넣고 보내면, 응답의 Content-Length 헤더가 설정된 값보다 클 경우 압축이 진행됨을 기대할 수 있다.

server.compression.enabled=true
server.compression.min-response-size=4KB

 

 

 

[ 실제 압축 여부 확인해보기 ]

대량의 응답을 생성하여 테스트해보면, 다음과 같이 응답 헤더 중 Content-Encoding: gzip을 통해 응답이 gzip 압축되어 제공되었음을 확인할 수 있다.

 

 

실제로 서버로부터 받은 패킷을 뜯어 확인해 보도록 하자. 다음과 같이 응답 페이로드의 데이터를 보면, gzip의 매직 넘버인 1F 8B(16진수)가 존재하며, 응답 전의 데이터는 11656 바이트로 조건에 맞춰 압축되었음을 확인할 수 있다.

 

 

문제는 응답 크기가 설정 값보다 작은 경우에도 gzip 압축이 적용된다는 것인데, 실제로 아래의 내용을 보면 압축 전 payload의 크기가 106 바이트임에도 불구하고 압축이 적용되었음을 확인할 수 있다.

 

 

즉, 현재 기준으로는 스프링 부트 + 톰캣 환경에서 server.compression.min-response-size 옵션이 정상적인 압축 조건으로써의 역할을 하지 못하고 있는 것이다.

 

 

 

 

[ server.compression.min-response-size 옵션이 정상적으로 동작하지 않는 이유 분석하기 ]

위의 설정한 값들은 스프링 부트의 Compression 클래스를 통해 관리된다. 그리고 Compression 객체에 설정된 값들은 내장 톰캣 관련 압축 커스터마이징 클래스인 CompressionConnectorCustomizer로 위임되어 설정된다.

 

 

이러한 부분은 최종적으로 톰캣의 CompressionConfig 클래스에 전달되어 압축 여부를 결정(useCompression)할 때 사용된다. 압축 여부 판단 로직을 보면, 압축이 활성화 되어 있고, 응답 길이(content-length) 헤더의 값이 설정된 상태에서(-1이 아님) 응답 길이 조건을 만족하지 않는 경우에만 압축을 하지 않음을 확인할 수 있다. 즉, 응답 길이 헤더가 설정되지 않았다면, 항상 압축을 진행하는 것이다. 실제로 톰캣 공식 문서에 따르면 Content-Length 가 없을 때 응답을 압축을 한다는 이야기가 있다.


If the content-length is not known and compression is set to "on" or more aggressive, the output will also be compressed.

 

 

이후에는 HTTP 요청 헤더에 accept-encoding: gzip 이 존재하는지를 확인하여, 해당 헤더의 값이 존재한다면 압축 진행 여부를 true로 반환하고, contentLength의 값을 -1로 설정함과 동시에 content-encoding 헤더를 gzip으로 설정함을 확인할 수 있다.

 

 

위에서 반환받은 압축 여부 값은 다음과 같이 Gzip 처리 필터인 GzipOutputFilter를 등록하는 데 사용되고, 최종적으로 해당 필터를 통해 응답 데이터를 생성할 때 압축을 적용하게 된다.

 

 

그리고 톰캣에서는 요청 타입이 application/json 이라면, Transfer-Encoding: chunked로 설정되는 로직이 존재한다. Transfer-Encoding: chunked 헤더는 응답 payload를 여러 개의 논리적 단위인 청크(chunk)로 나누어 이를 독립적으로 송/수신 함을 의미한다.

 


RFC 7230 3.3.3 섹션에서는 Transfer-Encoding 이 존재하는 경우, Content-Length 는 무시된다고 한다. 따라서 스프링의 로직에서도 application/json 타입의 요청이라면 항상 content-length를 별도로 설정해주지 않고, 톰캣을 따라 Transfer-Encoding: chunked 헤더로 데이터를 제공하는 것으로 판단된다.

 

 

 

 

[ 정리 및 요약 ]

결국 핵심 문제는 톰캣에서는 요청 타입이 application/json 이라면 Transfer-Encoding: Chunked이라고 설정을 하며, RFC 7230에 따라 스프링은 별도의 contentLength를 설정해주지 않는다. 톰캣은 content-length 헤더가 없을 경우에 응답을 압축하기 때문에, min-response-size 값과 무관하게 gzip 압축이 적용되고 있는 상황으로 요약할 수 있다. 스프링 프레임워크에서 application/json 타입으로 데이터를 직렬화/역직렬화 하는 곳은 MappingJackson2HttpMessageConverter이며, 직렬화를 처리하는 코드(AbstractJackson2HttpMessageConverter의 writeInternal)를 살펴보면 content-length 를 설정하는 부분이 실제로 없음을 확인할 수 있다.

 

 

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함