Skip to content

Brotli and Gzip compression support#15

Open
ismasan wants to merge 4 commits intomainfrom
brotli
Open

Brotli and Gzip compression support#15
ismasan wants to merge 4 commits intomainfrom
brotli

Conversation

@ismasan
Copy link
Collaborator

@ismasan ismasan commented Mar 16, 2026

Compression

SSE data (JSON + HTML) is highly compressible, and long-lived connections benefit significantly from compression. This SDK supports opt-in Brotli and gzip compression for SSE streams.

Enabling compression

Per-instance:

datastar = Datastar.new(request:, response:, view_context:, compression: true)

Or globally:

Datastar.configure do |config|
  config.compression = true
end

When enabled, the SDK negotiates compression with the client via the Accept-Encoding header and sets the appropriate Content-Encoding response header. If the client does not support compression, responses are sent uncompressed.

Brotli vs gzip

Brotli (:br) is preferred by default as it offers better compression ratios. It requires the host app to require the brotli gem. Gzip uses Ruby built-in zlib and requires no extra dependencies.

To use Brotli, add the gem to your Gemfile:

gem 'brotli'

Configuration options

Datastar.configure do |config|
  # Enable compression (default: false)
  # true enables both :br and :gzip (br preferred)
  config.compression = true

  # Or pass an array of encodings (first = preferred)
  config.compression = [:br, :gzip]

  # Per-encoder options via [symbol, options] pairs
  config.compression = [[:br, { quality: 5 }], :gzip]
end

You can also set these per-instance:

datastar = Datastar.new(
  request:, response:, view_context:,
  compression: [:gzip]              # only gzip, no brotli
)

# Or with per-encoder options
datastar = Datastar.new(
  request:, response:, view_context:,
  compression: [[:gzip, { level: 1 }]]
)

Per-encoder options

Options are passed directly to the underlying compressor via the array form. Available options depend on the encoder.

Gzip (via Zlib::Deflate):

Option Default Description
:level Zlib::DEFAULT_COMPRESSION Compression level (0-9). 0 = none, 1 = fastest, 9 = smallest. Zlib::BEST_SPEED and Zlib::BEST_COMPRESSION also work.
:mem_level 8 Memory usage (1-9). Higher uses more memory for better compression.
:strategy Zlib::DEFAULT_STRATEGY Algorithm strategy. Alternatives: Zlib::FILTERED, Zlib::HUFFMAN_ONLY, Zlib::RLE, Zlib::FIXED.

Brotli (via Brotli::Compressor, requires the brotli gem):

Option Default Description
:quality 11 Compression quality (0-11). Lower is faster, higher compresses better.
:lgwin 22 Base-2 log of sliding window size (10-24).
:lgblock 0 (auto) Base-2 log of max input block size (16-24, or 0 for auto).
:mode :generic Compression mode: :generic, :text, or :font. :text is a good choice for SSE (UTF-8 HTML/JSON).

Proxy considerations

Even with X-Accel-Buffering: no (set by default), some proxies like Nginx may buffer compressed responses. You may need to add proxy_buffering off to your Nginx configuration when using compression with SSE.

Benchmarks

There's a simple benchmark script in ./benchmarks/compression.rb

Run with bundle exec benchmarks/compression.rb

Datastar SSE Compression Benchmark
==========================================================================================

Scenario                No Compress         Gzip    Saved       Brotli    Saved
------------------------------------------------------------------------------------------
Table 10 rows                9.2 KB        633 B    93.3%        456 B    95.2%
Table 50 rows               45.9 KB       1.6 KB    96.6%        998 B    97.9%
Table 200 rows             184.2 KB       5.0 KB    97.3%       2.8 KB    98.5%
Table 1000 rows            923.0 KB      23.3 KB    97.5%      11.2 KB    98.8%
Dashboard 5 cards           11.9 KB        963 B    92.1%        736 B    94.0%
Dashboard 20 cards          47.2 KB       2.1 KB    95.5%       1.4 KB    96.9%
Dashboard 50 cards         117.9 KB       4.3 KB    96.3%       2.7 KB    97.7%


Streaming: individual row patches over one SSE connection
==========================================================================================

Scenario                No Compress         Gzip    Saved       Brotli    Saved
------------------------------------------------------------------------------------------
10 row patches               9.4 KB        731 B    92.4%        562 B    94.2%
50 row patches              47.4 KB       2.3 KB    95.2%       1.7 KB    96.4%
200 row patches            190.3 KB       8.2 KB    95.7%       6.0 KB    96.8%
1000 row patches           954.1 KB      39.5 KB    95.9%      29.7 KB    96.9%

Notes:
  - Single-event sizes include full SSE framing (event: / data: prefixes)
  - Gzip: default compression level, gzip framing (window_bits=31)
  - Brotli: default quality (11) with mode: :text
  - Streaming rows: each row is a separate patch_elements SSE event
    over one persistent compressed connection. The compressor dictionary
    builds up across events, improving ratios for repetitive markup.

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