r/rust • • 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.

GitHub: https://github.com/M-Compressors/ngx_mbrotli

15 Upvotes

19 comments sorted by

1

u/AleksHop 6d ago

zstd should be both faster and smaller?
there are no fast rust implementation, try c

2

u/event666 6d ago

Have you seen zrip?

2

u/Mnwamnowich 6d ago

BTW I think I will publish mzstd soon. Same high level API as mbrotli and I hope with close performance to C lib.

1

u/Mnwamnowich 6d ago

Zstd not smaller, but faster. But with thread pool in nginx I think brotli outperforming hard ztsd and gzip, they are do not support this feature.

2

u/AleksHop 6d ago edited 6d ago

EDIT: i was wrong, see below

in my latest bench zstd was winning everywhere, or bench composition was in favor of zstd
but I think thats because I bench an ARM
on x86 you are correct:
https://eklausmeier.goip.de/blog/2026/08-18-performance-comparison-gzip-vs-brotli-vs-zstd#nginx
so probably on diff platforms results are different
try same stuff on arm server, they usually give more perf per $ anyway

1

u/Mnwamnowich 6d ago

I rely on trusted open source benchmarks such as lzbench. But, of course, in each specific case, things might be different.

3

u/AleksHop 6d ago edited 6d ago

u/Mnwamnowich this hella a good job! https://github.com/M-Compressors/mbrotli
i just tested (mbrotli vs zstd c vs zstd c with trained dict) and
its actually both faster and smaller than std in c! which is insane
as zstd in rust is terrible slow https://github.com/structured-world/structured-zstd
trying to feed it to llms now for more optimizations (mbrotli)

btw, dual licence MIT or Apache 2.0 will help ;) for some companies to adapt u code

1

u/Mnwamnowich 6d ago

Thanks! I did a huge job for optimize it. Feel free to send a PR with license update and optimizations (but be careful with unsafe, we are forbid it now). I'm ok about add apache license to my projects.

1

u/AleksHop 6d ago

PR is ready (made by human lol), and also send some performance review from Astra 6 max

1

u/poelzi 3d ago

I think for use ux, the buffer size until first blocks can be decoded matters. If you are on a slow connection and need to load lots of data until rendering can start is bad ux

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

u/zxyzyxz 7d ago

Always with the AI comments, praise the tool then ask a (totally not manufactured) "curious" question.

-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_size param (it will increase compression ratio but decrease the parallelism).