Skip to content

fix(replays): Bound recording segment decompression at ingest - #129541

Open
sentry-junior[bot] wants to merge 5 commits into
masterfrom
dominikbuszowiecki/replay-994-bound-segment-decompression-at-ingest
Open

sentry-junior[bot] wants to merge 5 commits into
masterfrom
dominikbuszowiecki/replay-994-bound-segment-decompression-at-ingest

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Backend only (src/, tests/). Relay companion: getsentry/relay#6587, which applies Relay's uncompressed size limit to every recording, not only scrubbed ones.

Problem

decompress_segment inflated recording segments with an uncapped zlib.decompress before the consumer stored them. Relay bounds the compressed payload at 10 MiB, but only applies its 100 MiB uncompressed bound for orgs that have recording scrubbing enabled with a non-empty PII config. For everyone else nothing checked how large a segment became once inflated, and the consumer stored the client's compressed bytes verbatim.

Change

  • decompress_segment now decompresses incrementally via zlib.decompressobj, requesting one byte past the limit so an oversized segment is detected without ever materializing more than one byte over the limit. Segments over the limit raise DropSilently.
  • New replay.consumer.max-segment-decompressed-size option, default 100 MiB, FLAG_AUTOMATOR_MODIFIABLE — so the limit can be raised or lowered without a deploy.
  • Drops are counted under replays.consumer.recording.oversized_segment and logged with the compressed size and the limit.
  • parse_recording_event reads the option and passes it to decompress_segment as max_size, so the helper stays independent of the options store.

Behavior for well-formed and already-uncompressed segments is unchanged. zlib.decompressobj returns partial output on a truncated stream where the one-shot zlib.decompress raised, so that case is normalized back to zlib.error to preserve the existing handling. Regression tests cover truncated streams and corrupted checksums.

Rollout note

The default matches the uncompressed bound Relay already applies to scrubbing-enabled orgs, so it should be a no-op for segments produced by SDKs. If it turns out legitimate segments sit closer to the limit than expected, replays.consumer.recording.oversized_segment will show it and the option can be raised immediately. Worth watching that metric before considering this settled.

Refs REPLAY-994

--

View Session [Sentry]

DominikB2014 and others added 4 commits September 1, 2026 13:40
Recording segments were inflated with an uncapped `zlib.decompress` before being stored. Relay bounds the compressed payload at 10 MiB, but only applies its uncompressed bound for orgs with recording scrubbing and a non-empty PII config, so a highly compressible segment could be stored with no bound on its inflated size.

Decompress incrementally against a byte cap instead, and drop segments that exceed it. The limit comes from a new `replay.consumer.max-segment-decompressed-size` option (default 100 MiB) so it can be tuned without a deploy; drops are counted under `replays.consumer.recording.oversized_segment`.

A follow-up applies the same bound on the read path.

Refs REPLAY-994
…replay-994-bound-segment-decompression-at-ingest

# Conflicts:
#	src/sentry/replays/consumers/recording.py

Co-Authored-By: Dominik Buszowiecki <44422760+DominikB2014@users.noreply.github.com>
Read the option where it is used instead of threading it through
process_message and parse_recording_event.

Refs REPLAY-994

Co-Authored-By: Dominik Buszowiecki <44422760+DominikB2014@users.noreply.github.com>
decompress_segment reads replay.consumer.max-segment-decompressed-size from
the options store, which falls through to the database on a cache miss. The
unit tests calling it without a DB only passed when another test had already
warmed the options cache, and failed when run on their own.

Refs REPLAY-994

Co-Authored-By: Dominik Buszowiecki <44422760+DominikB2014@users.noreply.github.com>
@linear-code

linear-code Bot commented Nov 2, 2026

Copy link
Copy Markdown

REPLAY-994

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Nov 2, 2026
@DominikB2014

Copy link
Copy Markdown
Contributor

@cursor review

@DominikB2014
DominikB2014 marked this pull request as ready for review November 2, 2026 20:32
@DominikB2014
DominikB2014 requested review from a team as code owners November 2, 2026 20:32

@vjeranc vjeranc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good place for the bound: it caps an existing decompression pass, and replay processing reuses the decompressed bytes.

Read replay.consumer.max-segment-decompressed-size in parse_recording_event
and pass it to decompress_segment, so the helper no longer depends on the
options store and its unit tests no longer need the database.

Add regression tests for truncated zlib streams and corrupted checksums,
since decompressobj returns partial output on truncation where
zlib.decompress raised.

Refs REPLAY-994

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants