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:
+23
-1
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user