New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(http-compression): first version #3819
base: develop
Are you sure you want to change the base?
Conversation
I just realised that during my testing using |
I benchmarked again with minified assets and gzip compression is still beneficial :)
The good news is that the minified content has much less latency than non-minified and the compression is still good (~29%). Minified Plain Content (~4.5 secs total time) Minified Gzip compression (~1.7 secs total time) |
Implementation is looking good, I like it. I'm not sure if there is an interest in providing this via a flag, IMO we should decide that it's either gonna be beneficial for most or all of our userbase and enable the feature directly. Or maybe to default to gzip enabled with a way to disable it. I think we'll need to do some testing here and we'll need to deploy it in multiple environments to see if it make any difference. On one side the loading time from 4.5sec to 1.5sec sounds great, on the other side the vendor code loading time going from 0m0.015s to 0m0.106s kinda worries me (~ 7x the original time). I'm also curious about existing deployments that are using a reverse proxy setup + gzip enabled on the reverse proxy layer, what will happen here? |
Thanks, I'm very new to GoLang so I appreciate the comment :)
Gzip compression is usually a trade-off between network speed and compression speed. Not every network/server-machine combination is the same and thus enabling it or not depends on each user's conditions and preference. This is the reason for almost every web server that I know (many from this list) to implement gzip compression as an opt-in instead of opt-out. I would strongly advise on following the same practice in Portainer.
Yes! Definitively! My local testing only represents one case and if you have the machinery to test in multiple environments that's much better. I don't expect gzip compression to help in every case because of the mentioned trade-off.
Yes, again, the trade-off between network speed vs compression speed. In general slower networks + strong CPUs will show much better performance because despite taking more time for compressing, the total time If really interested on better performance, there is another option to consider as well: compress the assets on disk with gzip and serve them in this format directlly, thus avoiding re-compression over and over of the same static content. This eliminates the compression latency of big assets such as the vendor javascript/CSS.
In deployments where there is a proxy in front of Portainer doing gzip compression and even SSL termination, then gzip compression in Portainer itself shoud not be used. Like with SSL, gzip compression should be moved entirely to the reverse proxy. This is another reason to have a flag for gzip compression and default it to false. The main reason for me to implement this feature in Portainer is precisely to cater for these deployments that do not use reverse proxies in front of Portainer. In this case, and similar to the built-in SSL options, gzip compression comes handy and is very simple to enable. Take your time to test and check this PR more carefully as you need. There is no rush! Any questions/suggestions are very welcome too. |
I realised that I did not answer the question directly. What happens here depends on the reverse proxy. Some might decompress the content from Portainer and re-compress again (dumber proxies), others might be smarter and pass-through the already compressed data. |
I just found out a Go module that implements that suggestion: https://github.com/lpar/gzipped It is a drop-in replacement for the FileServer handler and plays nicely with gziphandler so we get the best of both worlds: pass-trough compressed files (i.e. static assets) or on-the-fly compressed data (i.e. dynamic JSON responses). The only caveat is that it doesn't support directory indexes so I'll check if Portainer requires it. I will experiment with it a bit and let you know if it's worth it 👍. |
Nice, keep us posted ! |
First prototype testing shows (as expected) good results. In this benchmark, files are pre-compressed on disk (this can be done during deployment in Yarn/Grunt):
I'm using the assets without minification here but as shown before after minification there is still further compression gain anyway. Then Portainer picks the correct available file according to requested compression without having to repeatedly compress on-the-fly the static assets:
Because the only difference between these two is now only transmission time (no compression time for static assets), the compressed request is now always better than the uncompressed version. I will polish the implementation and send another commit to this PR with this. |
Just a heads-up, I submitted a PR yesterday to upstream |
@hhromic no rush, the next release where this might be integrated will be 2.0 (which is planned for release end of June). |
Sure! No rush from my side either, I def want to implement this feature correctly and well-tested. |
* can be enabled with `--compression` flag (disabled by default) * uses gzip compression with default compression level (6) * only compresses content >= 1400 bytes (gziphandler library default)
…erving * always enabled as it has almost no performance cost * automatically serves pre-compressed asset files if found on disk * recognises `.br` (Brotli) and `.gz` (gzip) compressed file extensions
* automatically compresses assets using `CompressionWebpackPlugin` * creates pre-compressed versions of assets alongside uncompressed files * uses gzip compression with high compression level (9)
Ruff 🐶 I am running in lazy mode (as per your React on this comment to leave anonymous feedback.
Commands
|
@deviantony @itsconquest In summary, I implemented:
The automatic asset files compression does not replace uncompressed files, but instead places pre-compressed files alongside the uncompressed ones for maximum compatibility. For example:
The on-the-fly HTTP compression is mostly useful for large dynamic JSON responses from Portainer. This PR is now ready for you to test and review. I'll be looking forward for your comments and feedback. In my local tests the feature is working really nice with reduced content transfers. As said before, no rush from my side! Hope you find this a nice feature. |
Great job @hhromic ! I'll add this to our review pipe for 2.0 |
Solid PR, I look forward to reviewing this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also resolve the merge conflicts.
@@ -13,11 +16,21 @@ type Handler struct { | |||
// NewHandler creates a handler to serve static files. | |||
func NewHandler(assetPublicPath string) *Handler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a "compression" boolean flag here, so that only uses gzipped when compression is needed?
Great job for the PR @hhromic, much appreciated! Before merging the PR, can you please resolve the conflicts and add a boolean parameter for the file handle to honor the cli "compression" flag? Thank you. |
Hi @yi-portainer, thanks for your feedback. I will try to come back to this at my earliest. |
We had some very good initiatives in this PR, if a containerized dev environment is what you are looking for? Do note that it is still in experiment and not yet ready for day to day consumption (hence not officially documented yet), so please be careful when playing with it. |
Summary
This PR implements HTTP compression in Portainer using both, on-the-fly compression and pre-compressed asset files serving.
Automatic asset files compression during client build is also implemented in Webpack.
Fixes #3638
Details
--compression
flag (disabled by default).br
(Brotli) and.gz
(gzip) compressed file extensionsCompressionWebpackPlugin
Benchmarks
The following illustrates the transferred content size and time taken with and without requesting HTTP compression of the static asset files. These requests are local machine to local machine.
Main CSS asset
Main JS asset
Vendor CSS asset
Vendor JS asset
SVG asset
On slow networks (for example via VPN links), the loading times are significantly reduced when using gzip compression. Note how the biggest content (vendor JavaScript bundle) gets significantly reduced.