feat(moq-json): optional group-scoped zstd compression#1897
Conversation
Add an optional `Config.compression` to moq-json that compresses each group as a single zstd stream, flushed at every frame so a snapshot followed by deltas shares one warm window (later frames reuse the earlier ones as context). Frames are magicless with no per-frame checksum, since moq-net's framing already delimits each slice. An optional shared dictionary primes the window so even a group's first frame compresses well. When compression is enabled the delta-vs-snapshot rolling budget is measured on the real (compressed) slice sizes rather than the raw JSON, so the warm window's progressively smaller deltas pack more updates into a group. The plaintext path is unchanged, and compression defaults off, so existing tracks (including the hang catalog) are byte-identical on the wire. A cumulative per-group decompressed-size cap plus zstd's windowLogMax guard against decompression bombs. `Config` becomes `#[non_exhaustive]`; a `Consumer::with_compression` constructor takes the matching settings. A cloned consumer rebuilds its (non-cloneable) decoder window by replaying the group's already-read slices. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3xwgHid4UYjeugewkxUyj
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
WalkthroughThe pull request adds optional per-group zstd compression to the 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Moves payload compression up to the application layer (
moq-json) instead of the moq-lite wire (the alternative explored in #1874 / #1889). The relay stays media-agnostic, a group reuses a single warm compression context across its frames, and we get an optional shared dictionary for free. This is the reusable primitive; the catalog dual-publish and the browser decode path are deliberate follow-ups (see Scope).What changed
moq-jsongains an optionalConfig.compression: Option<Compression>(zstd):Encoder/Decoderhold the per-group state and reset at every group boundary.experimentalfeature). This is the "remove the magic marker between frames" ask.windowLogMaxbounds per-frame window memory.Compression defaults off, so existing tracks (including the hang
catalog.json, which usesdelta_ratio: 0and no compression) are byte-identical on the wire. No moq-net wire or catalog-format change.Public API (
moq-json)Configgainscompression: Option<Compression>and becomes#[non_exhaustive](build viaConfig::default()+ field set). The one strictly-breaking bit;moq-jsonis0.0.x.Compression { level, dictionary }config (#[non_exhaustive],Default).Consumer::with_compression(track, Compression);Consumer::newstays the plaintext shortcut. A cloned consumer rebuilds its non-cloneable decoder window by replaying the group's already-read slices (precedent: theGroupConsumerhandling in feat(moq-net): rework payload compression, kept compressed in RAM #1889).ErrorgainsDecompressandTooLarge(u64).rs/moq-mux(the only in-repo caller) updated to buildConfigviadefault()+ field set.Branch targeting
Targets
main: no wire-protocol or catalog-format change (compression is off by default and the catalog is untouched), andmoq-jsonis not among the crates the breaking-change rule routes todev. The sibling moq-lite PRs targetdevbecause they change the wire; this one does not.Scope — deferred follow-ups
catalog.json+catalog.json.zdual-publish and explicit catalog format selection (so a player can skip a deprecated uncompressed track). Touchesrs/moq-muxcatalogProducer,rs/hang,js/hang,doc/concept; kept separate so this core stays small.js/jsonmirror: the browser only needs to decode a compressed catalog, which is exactly the catalog follow-up; landing the JS zstd dependency with its first real consumer rather than speculatively. (moq-jsonis not on the wire-sync table and the catalog wire bytes are unchanged here.)Test plan
cargo test -p moq-json— 34 tests incl. new compressed snapshot/delta round-trips, compressed late-joiner, mid-group cloned-consumer rebuild, dictionary round-trip, and a "compressed frame is smaller than plaintext" wire-size check; plus codec-level round-trip / cross-frame-redundancy / dictionary / garbage-rejection unit tests.cargo clippy -p moq-json -p moq-mux --all-targets— clean.cargo build -p moq-mux -p hang,cargo doc -p moq-json— clean (catalog producer compiles against the newConfig).just checkvia the pinned nix toolchain — not run here; please confirm in a real terminal before merge.🤖 Generated with Claude Code
(Written by Claude)
Generated by Claude Code