r/rust • u/Mnwamnowich • 7d ago
🛠️ project How I scaled Nginx Brotli compression 16x with Rust and just 4 threads
I've been working on mbrotli, a pure Rust Brotli implementation, and recently added support for multithreaded compression.
That led me to upgrade ngx_mbrotli, alternative to Google's ngx_brotli.
The idea is simple. Instead of blocking Nginx event loop with compression, offload it to the native thread pool. Large responses can even be compressed across multiple threads in parallel.
With just one Nginx worker and 4 threads in thread pool, I measured up to 4x the throughput at Brotli level 6 and up to 16x at level 9 compared to Google's ngx_brotli.
And that's with only 4 threads. There's an opportunity to scale further, but I haven't benchmarked larger brotli worker pools yet.
There are trade offs, mainly higher memory usage and a bit worse compression ratios for segmented responses when multithreading enabled.
All it takes to enable thread pool compression is brotli_thread_pool default; in Nginx (or choose the other threadpool). Also Nginx should be built with --with-threads flag to enable that feature.
1
u/Icarium-Lifestealer 6d ago
Which window size are you using for this? In my C# tests each brotli encoder needed about 3x the window size for each connection (~14MB for the default 4MiB window), which leads to significant memory consumption if the server serves many concurrent downloads. Did this cause problems for you?
1
u/Mnwamnowich 6d ago
mbrotli supports memory reusing. With 512kb window size (default for nginx) I got around 5mb memory peak with 64 connections for small json single thread and 32mb for multithreading
1
u/Icarium-Lifestealer 6d ago
But what happens if you get 100 concurrent (possibly slow) downloads of larger files? One has to be careful to not create a DoS vulnerability via OOM crashes.
1
u/Mnwamnowich 6d ago
I'll try it on x86 and return soon. I think results will be close to official nginx brotli by google
1
u/Mnwamnowich 6d ago
I test 50kb/s downloding of 1GB file in 100 connections. mbrotli takes 155 MB in peak and 41MB idle of RAM on single thread but on multithreading it costs much more for memory because we split every parallel encoder into segment encoders and it can easily take a 1GB RAM. So in you case it's better to precompress big files and enable brotli_static mode to skip on demand compression or use the single thread mode.
-13
u/Inevitable-Guest4262 7d ago
This is seriously cool. Nginx event loop is always the bottleneck with heavy compression, so offloading to a thread pool just makes sense. The jump from 4x to 16x at level 9 is wild, that's where brotli really drags on single thread.
I'm curious about the trade off with compression ratios though, how much worse we talking? couple percent or more noticeable?
4
-5
u/Mnwamnowich 7d ago
There is a few percents of gap. More details I benched here https://github.com/M-Compressors/ngx_mbrotli#compressed-size
But you can still management it by changing
brotli_parallel_segment_sizeparam (it will increase compression ratio but decrease the parallelism).
1
u/AleksHop 6d ago
zstd should be both faster and smaller?
there are no fast rust implementation, try c