개발 노트/ASP.NET Core

Kestrel 개선과 Zstandard 압축 — 응답 크기와 CPU를 같이 줄이기

Roslyn 2026. 9. 5. 09:00
반응형

서버 지표를 보다가 이상한 장면을 만난 적이 있습니다. 트래픽은 그대로인데 CPU 사용률만 눈에 띄게 올라가 있었습니다. 원인을 좁혀보니 압축 설정을 바꾼 배포가 그 시점에 있었습니다. 응답 크기를 줄이려고 켠 옵션이 CPU를 대신 먹고 있었던 겁니다.

응답 압축은 켜기만 하면 이득처럼 보이지만 실제로는 거래입니다. 네트워크로 나가는 바이트를 줄이는 대신 프로세서 시간을 씁니다. 이 거래가 이득인지 손해인지는 서비스마다, 심지어 엔드포인트마다 다릅니다.

.NET 11에서는 Zstandard 압축이 들어옵니다. Brotli에 준하는 압축률을 더 낮은 CPU 비용으로 얻는다는 성격의 알고리즘입니다. 이 글에서는 압축의 기본 거래 구조를 먼저 짚고, Zstandard가 1인 서비스에서 어떤 상황에 실익이 되는지, 그리고 수치는 직접 재는 방법까지 적어보겠습니다. 아래 .NET 11 관련 내용은 현재 프리뷰 기준이며 GA까지 바뀔 수 있습니다.

압축은 CPU와 대역폭을 맞바꾸는 일입니다

응답 압축의 원리는 단순합니다. 서버가 본문을 압축해 보내고, 브라우저가 풀어서 씁니다. 브라우저는 요청 헤더로 자기가 풀 수 있는 방식을 알리고, 서버는 그중 하나를 골라 응답 헤더에 표시합니다.

여기서 중요한 것은 압축률을 높일수록 CPU를 더 쓴다는 점입니다. 같은 알고리즘 안에서도 레벨이 있고, 레벨을 올리면 크기는 줄지만 시간이 늘어납니다. 그래서 선택은 세 축을 같이 봐야 합니다.

방식성격고려할 점

gzip가장 오래됐고 지원 범위가 가장 넓음호환성이 필요할 때의 기본값으로 둡니다
Brotli압축률이 좋아 정적 자산에 유리높은 레벨은 CPU 비용이 큽니다
ZstandardBrotli 수준 압축률을 더 낮은 CPU로 목표클라이언트 지원 여부를 확인해야 합니다

표에 수치를 적지 않은 것은 의도적입니다. 압축 결과는 콘텐츠의 성격, 레벨 설정, 응답 크기 분포에 따라 크게 달라져서, 남의 벤치마크 숫자를 그대로 가져오면 판단이 어긋나기 쉽습니다. 저는 이런 항목은 항상 제 서비스 데이터로 다시 잽니다.

압축이 의미 있는 콘텐츠와 무의미한 콘텐츠

압축을 켜기 전에 무엇을 압축할지부터 정하는 편이 낫습니다. 대상이 잘못되면 CPU만 쓰고 크기는 그대로입니다.

• 효과가 큰 것: HTML, CSS, JavaScript, JSON, SVG, 텍스트 로그처럼 반복이 많은 텍스트입니다. API 응답 JSON은 특히 필드 이름이 반복되어 잘 줄어듭니다.

• 효과가 거의 없는 것: JPEG, PNG, WebP, MP4처럼 이미 압축된 형식입니다. 여기에 다시 압축을 걸면 시간만 쓰고 크기는 제자리이거나 오히려 커질 수 있습니다.

• 애매한 것: 아주 작은 응답입니다. 헤더와 처리 비용을 감안하면 몇백 바이트짜리를 압축해서 얻는 것이 크지 않습니다. 최소 크기 기준을 두는 이유가 여기 있습니다.

그리고 정적 파일은 요청마다 압축하지 말고 미리 압축해두는 편이 낫습니다. 빌드 시점에 압축본을 만들어 함께 배포하면 런타임 CPU를 아예 쓰지 않습니다. 같은 파일을 하루에 수천 번 압축하는 것은 낭비입니다. 동적 응답만 실시간 압축 대상으로 남기면 CPU 그래프가 훨씬 조용해집니다.

1인 서비스에서 CPU가 병목일 때

혼자 운영하는 서비스는 대체로 작은 인스턴스 한두 대로 돌아갑니다. 이 조건에서는 CPU가 먼저 바닥납니다. 요청 처리, 직렬화, 템플릿 렌더링, 데이터베이스 대기까지 한 대가 다 감당하기 때문입니다.

이런 환경에서 Zstandard가 주는 실익은 같은 압축률을 더 싸게 얻는다는 데 있습니다. 그동안 CPU가 아까워서 Brotli를 낮은 레벨로 쓰거나 gzip에 머물러 있었다면, 선택지가 하나 늘어나는 셈입니다. 반대로 CPU가 한가하고 대역폭 요금이 부담인 상황이라면 우선순위가 달라집니다. 그래서 결론은 항상 같습니다. 지금 무엇이 부족한지 먼저 확인하고 고릅니다.

Kestrel 쪽 개선이 체감되는 상황

.NET 11에서는 압축 외에도 Kestrel 자체의 개선이 예정되어 있습니다. 두 가지가 눈에 띕니다.

• 짧은 연결의 TLS 핸드셰이크 오버헤드 개선: 연결을 오래 유지하지 않고 짧게 붙었다 떨어지는 클라이언트가 많을 때 이득이 큽니다. 모바일 네트워크에서 오는 요청, 여러 곳에서 짧게 호출하는 API가 여기 해당합니다. 반대로 연결을 길게 유지하는 구조라면 체감이 작습니다.

• 잘못된 요청의 효율적 거부: 공개된 서버에는 스캐너와 봇이 끊임없이 들어옵니다. 이 요청들을 더 적은 비용으로 끊어내면 정상 트래픽에 쓸 자원이 남습니다. 저처럼 작은 인스턴스로 버티는 쪽에서는 이런 절약이 생각보다 크게 작동합니다.

또한 Kestrel의 보안 관련 기본 동작도 강화되는 방향입니다. 다만 기본값이 조여지면 기존에 통과하던 요청이 거부될 수 있으므로, 업그레이드 후에는 4xx 응답 비율이 갑자기 변하지 않는지를 며칠 지켜보시는 편이 좋습니다.

수치는 직접 재야 합니다

다른 사람의 벤치마크는 방향을 알려줄 뿐 결정을 대신해주지 않습니다. 제가 쓰는 측정 순서는 이렇습니다.

• 대표 응답을 고릅니다: 트래픽 상위 엔드포인트 서너 개를 고르고, 실제 응답 본문을 파일로 저장합니다.

• 크기를 비교합니다: 같은 본문을 각 방식과 레벨로 압축해 바이트 수를 표로 만듭니다. 이 단계는 서버 없이도 할 수 있습니다.

• 시간을 잽니다: 압축에 걸린 시간을 같이 기록합니다. 크기만 보면 항상 높은 레벨이 이깁니다.

• 부하를 걸어 확인합니다: 설정을 바꿔가며 동시 요청을 넣고 CPU 사용률과 응답 시간 분포를 봅니다. 평균보다 상위 백분위 값을 봐야 실제 체감에 가깝습니다.

• 한 번에 하나만 바꿉니다: 알고리즘과 레벨을 동시에 바꾸면 어느 쪽 영향인지 알 수 없습니다.

측정 결과는 서비스 문서에 날짜와 함께 남겨둡니다. 반년 뒤 응답 구조가 바뀌면 결론도 바뀌기 때문에, 언제 잰 값인지가 값 자체만큼 중요합니다.

마치며

응답 압축은 CPU와 대역폭의 거래입니다. 텍스트에는 효과가 크고 이미 압축된 미디어에는 무의미하며, 정적 파일은 미리 압축해두는 편이 낫습니다. Zstandard는 CPU가 병목인 작은 서비스에서 선택지를 하나 넓혀줍니다. Kestrel의 핸드셰이크와 잘못된 요청 처리 개선은 짧은 연결이 많거나 봇 트래픽이 많은 환경에서 체감이 큽니다.

오늘 할 수 있는 한 가지는 가장 트래픽이 많은 응답 하나를 파일로 저장해 압축 방식별 크기를 비교해보는 일입니다. 표 한 줄만 채워봐도 지금 설정이 과한지 부족한지 감이 잡힙니다.

반응형