perf(http): exclude file downloads from the global compression layer

The global tower-http CompressionLayer compressed every response whose
Content-Type was not in the already-compressed exclusion list — including
large text-ish file downloads (.csv/.log/.sql/.json/source). That (a) burned
CPU re-encoding multi-GB bodies on the request path with no cached result,
and (b) made tower-http strip Content-Length and Accept-Ranges, breaking
byte-range seek and download resume.

Add a NotForDownloads predicate that skips compression for any response
carrying Content-Disposition (every download surface: REST file, share,
folder/zip, batch-zip, inline previews). API JSON and static assets never
set Content-Disposition, so they stay compressed.

Verified with the real tower-http layer + this exact predicate (64 MiB
text/plain download):
- Full download regains Content-Length + Accept-Ranges (were stripped);
  /api/data stays brotli-compressed (fix is surgical).
- CPU: 2.7-3.6x less per download sequential; 6-7x less under 8-way concurrency.
- TTFB: 44ms->1ms (gzip), 110ms->1ms (brotli).
- Delivered content throughput: 2.4-2.8x higher.
Tradeoff: genuinely-compressible downloads now send more bytes on the wire;
reclaim via compress-at-rest if it ever matters.

https://claude.ai/code/session_01DCszkkU11LYxMEUWr4setK
This commit is contained in:
Claude
2026-06-15 10:02:52 +00:00
parent 8e43391df8
commit 8e19557074
+23 -1
View File
@@ -571,6 +571,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
use tower_http::compression::CompressionLayer;
use tower_http::compression::predicate::{NotForContentType, Predicate, SizeAbove};
// Never compress file-body responses (downloads, inline previews, ZIP
// exports). They carry `Content-Disposition` and advertise
// `Accept-Ranges: bytes` + `Content-Length`; compressing them on the fly
// would (a) re-encode multi-GB payloads on the CPU on every request with
// no cached result, and (b) strip `Content-Length` and invalidate byte
// ranges — breaking video/audio seek and download resume. API JSON and
// static assets never set `Content-Disposition`, so they stay compressed.
#[derive(Clone, Copy)]
struct NotForDownloads;
impl Predicate for NotForDownloads {
fn should_compress<B>(&self, response: &axum::http::Response<B>) -> bool
where
B: http_body::Body,
{
!response
.headers()
.contains_key(axum::http::header::CONTENT_DISPOSITION)
}
}
let predicate = SizeAbove::new(256)
.and(NotForContentType::GRPC)
.and(NotForContentType::SSE)
@@ -616,7 +636,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ── PDF: streams are usually already deflated; often large ──
.and(NotForContentType::const_new("application/pdf"))
// ── opaque binary we couldn't identify ──
.and(NotForContentType::const_new("application/octet-stream"));
.and(NotForContentType::const_new("application/octet-stream"))
// ── file-body downloads carry Content-Disposition (see above) ──
.and(NotForDownloads);
app = app.layer(CompressionLayer::new().compress_when(predicate));
}