oxidecomputer / dropshot

expose REST APIs from a Rust program
Apache License 2.0
791 stars 73 forks source link

Gzip response bodies #221

Open david-crespo opened 2 years ago

david-crespo commented 2 years ago

Substantially reduce response sizes by gzipping response body and adding content-encoding: gzip to headers. Probably simplest to make this configurable server-wide. This issue is inspired by me imagining a multi-MB response containing serial console contents.

Potential extra features

ahl commented 2 years ago

This is an interesting idea. In what situations would we expect the additional latency introduced by compress/decompress to be less than the transmission latency saved? I could imagine this would be most valuable on high-latency and/or low-bandwidth connections.

Would you expect dropshot to ignore this for pre-compressed data such as jpg, png or pre-compressed js/css objects?

david-crespo commented 2 years ago

Good point about images, it looks like the recommendation is not to gzip images because it doesn't make them smaller, and could in fact make them bigger. Same is true for pre-compressed (not only minified) static text assets. In that case we would need the server to know whether the asset being served is already compressed and pass through compressed files unmodified. Putting right content-encoding header on the response would require also knowing the compression algorithm used (browser supported options here).

I see people discussing thresholds for whether compression is worth it primarily in terms of response size. I thought of this issue because of potentially large responses like the serial console. Even on a fast connection, I think you're usually going to see a latency benefit from fast server-side compression. Bandwidth savings aside, off the top of my head I'd guess your download speed would have to be consistently faster than compression throughput to come out worse off in terms of latency. I see Google using gzip in GCP with dynamic JSON responses as small as 2 KB, though they may be concerned about bandwidth too. In short, the conventional wisdom seems to be that the latency trade pays off nearly all the time, or at least, even if your very fastest clients are slightly hurt by it, that's far outweighed by the gains for slower clients.

This (old) article argues that it doesn't make sense to compress things smaller than a TCP packet, presumably because you're still waiting for the entire packet? (Out of my wheelhouse.) From what I can tell, people seem to like minimum size thresholds of around a few KB. It probably doesn't matter that much unless we expect to have a lot of tiny responses.

Some useful links I found while looking around:

https://stackoverflow.com/a/32454901/604986 https://stackoverflow.com/a/32454901/604986 https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer#text_compression_with_gzip

david-crespo commented 2 years ago

Found an example from Tower of a nice way to handle precompressed assets by putting the compressed and uncompressed ones side by side.

a client with an Accept-Encoding header that allows the gzip encoding will receive the file dir/foo.txt.gz instead of dir/foo.txt. If the precompressed file is not available, or the client doesn’t support it, the uncompressed version will be served instead.

https://docs.rs/tower-http/latest/tower_http/services/fs/struct.ServeDir.html#method.precompressed_gzip

Presumably for images you would have to use some kind is_image file extension matcher. One easy way would be to use the mime type produced by mime_guess and check if starts with image/. Another way would be to flip it and use an allowlist for compressible extensions, so, e.g., we might only compress JSON and HTML responses and .json, .js, and .css files.

david-crespo commented 4 months ago

While thinking about https://github.com/oxidecomputer/console/issues/2029, it occurred to me gzip should be very effective at reducing the size of big lists due to all the repetition of keys. And boy is it. This is a real response from dogfood containing 127 disks.

 55k disks.json
8.8k disks.json.gz

A counterpoint here is that 55k is already so small that there is no point in compressing. However, if we want to be able to scale nicely to fetching 1000 things, we're talking 433k vs. 69k (assuming linear scaling of gzip size savings, which might be conservative), so it gets more plausible.