Skip to content

Route brotli through the compression-rs autogated router - #7158

Open
guybedford wants to merge 2 commits into
mainfrom
gbedford/rust-brotli
Open

Route brotli through the compression-rs autogated router#7158
guybedford wants to merge 2 commits into
mainfrom
gbedford/rust-brotli

Conversation

@guybedford

Copy link
Copy Markdown
Contributor

This extends the compression-rs autogate from zlib to brotli, using the same routing module technique in #7132.

This results in both the Content-Encoding: br configuration, and Node.js usage delegating to the Rust version when the autogate is enabled.

Headline numbers, measuring the fetch Content-Encoding: br configuration (quality 5, lgwin 19, 1MB compressible data in 16KB chunks, opt build):

workload C brotli rust-brotli delta
compress 90.5 MiB/s 69.0 MiB/s -24%
decompress 999 MiB/s 671 MiB/s -33%

With the latest Rust nightly and SIMD features of the Rust brotli implementation (not yet included in this PR) we get:

workload nightly + simd
compress -22%
decompress -25%

The main question here is thus whether we are okay with a 25-30% slowdown, with the SIMD and newer nightly Rust / LLVM closer to 20%. Hopefully that improves further over time as well.

For node:zlib the gap depends on quality: at node's default q11 the one-shot compress measures ~10-18% slower on 1MB inputs, with the q4/q5 range matching the streaming numbers above. Compressed output sizes match the C library within noise at all measured qualities (worst case +2.7% at q5 on one input; q11 outputs effectively identical). The NPOSTFIX/NDIRECT encoder params are not supported by rust-brotli's encoder and report failure under the gate; decoder params are accepted and ignored.

The unprefixed BrotliEncoder*/BrotliDecoder* symbol names are now owned by a compiled routing layer, src/workerd/util/brotli-router.c++, which forwards each call to one of the two implementations linked into the binary: the C brotli library (rebuilt with the routed entry points renamed to brotli_c_* by build/BUILD.brotli) or rust-brotli (exported under brotli_rs_* prefixes by src/rust/brotli-rs). The branch is set process-globally from the compression-rs autogate alongside the existing zlib router.

Every consumer resolves the brotli/encode.h / brotli/decode.h declarations to the routing layer, so the gate covers fetch Content-Encoding: br encoding/decoding and response-stream observation (kj-brotli), and node:zlib's brotli modes (the shared Brotli{Encoder,Decoder}Context machinery).

  • @brotli moves from the BCR module to the upstream release archive with our own build file so the C implementation can be compiled under prefixed names, mirroring build/BUILD.zlib
  • src/rust/brotli-rs implements the routed C API subset over the brotli/brotli-decompressor crates: allocation uses the Rust global allocator (the C allocator callbacks are accepted and ignored), decoder error strings are byte-identical to the C library's, and the crates' ffi-api feature stays off since it would export the unprefixed names
  • a new kj_test verifies the router selects the matching implementation on both sides of the gate
  • a new bench-brotli benchmark measures the two implementations on the kj-http streaming workload

The router test, node:zlib brotli tests, compression stream tests, and the full test suite pass in both gate states.

The unprefixed brotli symbol names (BrotliEncoderCompressStream,
BrotliDecoderDecompressStream, ...) are now owned by a compiled routing
layer, src/workerd/util/brotli-router.c++, which forwards each call to
one of the two implementations linked into the binary: the C brotli
library (rebuilt with the routed entry points renamed to brotli_c_* by
build/BUILD.brotli) or rust-brotli (exported under brotli_rs_* prefixes
by src/rust/brotli-rs, with the crate's unprefixed ffi-api exports left
off). The branch is set process-globally from the compression-rs
autogate alongside the existing zlib router whenever the gate state
changes.

Since every consumer resolves the declarations in brotli/encode.h and
brotli/decode.h to the routing layer, the gate now covers node:zlib's
brotli modes, the shared Brotli{Encoder,Decoder}Context machinery behind
web streams, and the kj-brotli streams used for fetch content-encoding
and response-stream observation. @Brotli moves from the BCR module to
the upstream release archive with our own build file so the C
implementation can be compiled under prefixed names, mirroring
build/BUILD.zlib.

src/rust/brotli-rs implements the routed C API subset over the crates'
Rust APIs: allocation uses the Rust global allocator (the C allocator
callbacks are accepted and ignored) and decoder error strings match the
C library's. A new kj_test verifies the router selects the matching
implementation on both sides of the gate, and a new bench-brotli
benchmark measures the two implementations on the kj-http streaming
workload (rust-brotli currently ~25% slower on compress at quality 5 and
~35% slower on decompress).
@guybedford
guybedford requested review from a team as code owners August 28, 2026 00:58
Comment thread src/rust/brotli-rs/lib.rs
Comment thread src/rust/brotli-rs/lib.rs Outdated
@ask-bonk

This comment was marked as resolved.

…typedef

The brotli C API permits a null next_out with zero available_out (and null
next_in with zero available_in; BrotliContext::clearBuffers produces exactly
that), so only advance the cursors when bytes were actually consumed or
written.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant