From e2b7486ddee86503a96d13fe1e9c7c5ca20b8dcb Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Wed, 27 Aug 2025 10:52:02 -0500 Subject: [PATCH 1/7] Add option which disables publishing to Relay --- src/sentry/options/defaults.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sentry/options/defaults.py b/src/sentry/options/defaults.py index efd7218d9823b1..b52526ea614790 100644 --- a/src/sentry/options/defaults.py +++ b/src/sentry/options/defaults.py @@ -483,6 +483,13 @@ default=False, flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE, ) +# Whether or not Relay replay-event publishing to Snuba is disabled. +register( + "replay.relay-snuba-publishing-disabled", + type=Bool, + default=False, + flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE, +) # Billing skip for mobile replay orgs. register( "replay.replay-video.billing-skip-org-ids", From 794e9f4e218d43910dbf969e3a20cc69e906c813 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Wed, 27 Aug 2025 11:14:26 -0500 Subject: [PATCH 2/7] Add to global config --- src/sentry/relay/globalconfig.py | 1 + tests/sentry/api/endpoints/test_relay_globalconfig_v3.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/sentry/relay/globalconfig.py b/src/sentry/relay/globalconfig.py index 4ae6f83747b8b7..f9b94ddef29d83 100644 --- a/src/sentry/relay/globalconfig.py +++ b/src/sentry/relay/globalconfig.py @@ -25,6 +25,7 @@ "relay.span-extraction.sample-rate", "relay.span-normalization.allowed_hosts", "relay.drop-transaction-attachments", + "replay.relay-snuba-publishing-disabled", ] diff --git a/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py b/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py index 49dc6b81976caf..c1ed9ee01784c1 100644 --- a/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py +++ b/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py @@ -42,6 +42,7 @@ def inner(version, global_): "profiling.profile_metrics.unsampled_profiles.sample_rate": 1.0, "relay.span-usage-metric": True, "relay.cardinality-limiter.mode": "passive", + "replay.relay-snuba-publishing-disabled": False, "relay.metric-bucket-distribution-encodings": { "custom": "array", "metric_stats": "array", From 7b9b68c0d97d24ec20e68d333af9cc2df74fec7d Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Wed, 27 Aug 2025 11:18:15 -0500 Subject: [PATCH 3/7] Use non-default value --- tests/sentry/api/endpoints/test_relay_globalconfig_v3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py b/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py index c1ed9ee01784c1..9e86d2aa64c4d7 100644 --- a/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py +++ b/tests/sentry/api/endpoints/test_relay_globalconfig_v3.py @@ -42,7 +42,7 @@ def inner(version, global_): "profiling.profile_metrics.unsampled_profiles.sample_rate": 1.0, "relay.span-usage-metric": True, "relay.cardinality-limiter.mode": "passive", - "replay.relay-snuba-publishing-disabled": False, + "replay.relay-snuba-publishing-disabled": True, "relay.metric-bucket-distribution-encodings": { "custom": "array", "metric_stats": "array", From c7bf1e5c4e3d09674cba08d93ae902b6645b11f9 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Thu, 28 Aug 2025 09:09:18 -0500 Subject: [PATCH 4/7] Add handling for new Kafka field --- src/sentry/replays/consumers/recording.py | 1 + src/sentry/replays/usecases/ingest/__init__.py | 9 +++++++++ tests/sentry/replays/unit/consumers/test_recording.py | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/src/sentry/replays/consumers/recording.py b/src/sentry/replays/consumers/recording.py index 3930dbcbb642a3..aaf0f98cb72a79 100644 --- a/src/sentry/replays/consumers/recording.py +++ b/src/sentry/replays/consumers/recording.py @@ -146,6 +146,7 @@ def parse_recording_event(message: bytes) -> Event: "replay_id": recording["replay_id"], "retention_days": recording["retention_days"], "segment_id": segment_id, + "should_publish_replay_event": recording.get("replay_relay_snuba_publish_disabled"), }, "payload_compressed": compressed, "payload": decompressed, diff --git a/src/sentry/replays/usecases/ingest/__init__.py b/src/sentry/replays/usecases/ingest/__init__.py index fae9e4547798dd..2c97f2eaba67f5 100644 --- a/src/sentry/replays/usecases/ingest/__init__.py +++ b/src/sentry/replays/usecases/ingest/__init__.py @@ -11,6 +11,7 @@ from sentry.constants import DataCategory from sentry.logging.handlers import SamplingFilter from sentry.models.project import Project +from sentry.replays.lib.kafka import publish_replay_event from sentry.replays.lib.storage import _make_recording_filename, storage_kv from sentry.replays.usecases.ingest.event_logger import ( emit_click_events, @@ -47,6 +48,7 @@ class EventContext(TypedDict): replay_id: str retention_days: int segment_id: int + should_publish_replay_event: bool | None class Event(TypedDict): @@ -176,6 +178,13 @@ def commit_recording_message(recording: ProcessedEvent) -> None: recording.context["received"], ) + metrics.incr( + "replays.should_publish_replay_event", + tags={"value": recording.context["should_publish_replay_event"]}, + ) + if recording.context["should_publish_replay_event"] and recording.replay_event: + publish_replay_event(json.dumps(recording.replay_event)) + # Write to replay-event consumer. if recording.actions_event: emit_replay_events( diff --git a/tests/sentry/replays/unit/consumers/test_recording.py b/tests/sentry/replays/unit/consumers/test_recording.py index 1272179735f9aa..bb4dfd4070981c 100644 --- a/tests/sentry/replays/unit/consumers/test_recording.py +++ b/tests/sentry/replays/unit/consumers/test_recording.py @@ -159,6 +159,7 @@ def test_parse_recording_event_success() -> None: "replay_id": "test-replay-id", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, "payload_compressed": compressed_payload, "payload": original_payload, @@ -206,6 +207,7 @@ def test_parse_recording_event_with_replay_event() -> None: "replay_id": "test-replay-id", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, "payload_compressed": compressed_payload, "payload": original_payload, @@ -303,6 +305,7 @@ def test_process_message_compressed() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, filedata=b"x\x9c\x8b\xaeV*\xa9,HU\xb2RP*I-.Q\xd2QPJI,I\x04\xf1\x8b\xf3sS\x15R\xcbR\xf3J\x14\xc0B\xb5\xb1\x00F\x9f\x0e\x8d", filename="30/4/1/42", @@ -354,6 +357,7 @@ def test_process_message_uncompressed() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, filedata=b"x\x9c\x8b\xaeV*\xa9,HU\xb2RP*I-.Q\xd2QPJI,I\x04\xf1\x8b\xf3sS\x15R\xcbR\xf3J\x14\xc0B\xb5\xb1\x00F\x9f\x0e\x8d", filename="30/4/1/42", @@ -405,6 +409,7 @@ def test_process_message_compressed_with_video() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, filedata=zlib.compress(pack(original_payload, b"hello")), filename="30/4/1/42", @@ -566,6 +571,7 @@ def make_valid_processed_event() -> ProcessedEvent: "replay_id": "1", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": None, }, filedata=compressed_payload, filename="30/4/1/42", From b1707050381403c699eac81c29cf78839d3b1b52 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Thu, 28 Aug 2025 09:10:55 -0500 Subject: [PATCH 5/7] Rename field --- src/sentry/replays/consumers/recording.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/replays/consumers/recording.py b/src/sentry/replays/consumers/recording.py index aaf0f98cb72a79..a9296b39780704 100644 --- a/src/sentry/replays/consumers/recording.py +++ b/src/sentry/replays/consumers/recording.py @@ -146,7 +146,7 @@ def parse_recording_event(message: bytes) -> Event: "replay_id": recording["replay_id"], "retention_days": recording["retention_days"], "segment_id": segment_id, - "should_publish_replay_event": recording.get("replay_relay_snuba_publish_disabled"), + "should_publish_replay_event": recording.get("relay_snuba_publish_disabled"), }, "payload_compressed": compressed, "payload": decompressed, From 3e7410c33658edf41571baac5a7221935e188f65 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Thu, 28 Aug 2025 10:55:16 -0500 Subject: [PATCH 6/7] Relay publishing state is always boolean --- src/sentry/replays/consumers/recording.py | 6 +++++- .../sentry/replays/unit/consumers/test_recording.py | 12 ++++++------ tests/sentry/replays/unit/test_ingest.py | 4 ++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/sentry/replays/consumers/recording.py b/src/sentry/replays/consumers/recording.py index a9296b39780704..abcb703eeb09db 100644 --- a/src/sentry/replays/consumers/recording.py +++ b/src/sentry/replays/consumers/recording.py @@ -137,6 +137,10 @@ def parse_recording_event(message: bytes) -> Event: else: replay_video = None + relay_snuba_publish_disabled = recording.get("relay_snuba_publish_disabled", False) + if not isinstance(relay_snuba_publish_disabled, bool): + relay_snuba_publish_disabled = False + return { "context": { "key_id": recording.get("key_id"), @@ -146,7 +150,7 @@ def parse_recording_event(message: bytes) -> Event: "replay_id": recording["replay_id"], "retention_days": recording["retention_days"], "segment_id": segment_id, - "should_publish_replay_event": recording.get("relay_snuba_publish_disabled"), + "should_publish_replay_event": relay_snuba_publish_disabled, }, "payload_compressed": compressed, "payload": decompressed, diff --git a/tests/sentry/replays/unit/consumers/test_recording.py b/tests/sentry/replays/unit/consumers/test_recording.py index bb4dfd4070981c..f35137be936814 100644 --- a/tests/sentry/replays/unit/consumers/test_recording.py +++ b/tests/sentry/replays/unit/consumers/test_recording.py @@ -159,7 +159,7 @@ def test_parse_recording_event_success() -> None: "replay_id": "test-replay-id", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, "payload_compressed": compressed_payload, "payload": original_payload, @@ -207,7 +207,7 @@ def test_parse_recording_event_with_replay_event() -> None: "replay_id": "test-replay-id", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, "payload_compressed": compressed_payload, "payload": original_payload, @@ -305,7 +305,7 @@ def test_process_message_compressed() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, filedata=b"x\x9c\x8b\xaeV*\xa9,HU\xb2RP*I-.Q\xd2QPJI,I\x04\xf1\x8b\xf3sS\x15R\xcbR\xf3J\x14\xc0B\xb5\xb1\x00F\x9f\x0e\x8d", filename="30/4/1/42", @@ -357,7 +357,7 @@ def test_process_message_uncompressed() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, filedata=b"x\x9c\x8b\xaeV*\xa9,HU\xb2RP*I-.Q\xd2QPJI,I\x04\xf1\x8b\xf3sS\x15R\xcbR\xf3J\x14\xc0B\xb5\xb1\x00F\x9f\x0e\x8d", filename="30/4/1/42", @@ -409,7 +409,7 @@ def test_process_message_compressed_with_video() -> None: "replay_id": "1", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, filedata=zlib.compress(pack(original_payload, b"hello")), filename="30/4/1/42", @@ -571,7 +571,7 @@ def make_valid_processed_event() -> ProcessedEvent: "replay_id": "1", "retention_days": 30, "segment_id": 42, - "should_publish_replay_event": None, + "should_publish_replay_event": False, }, filedata=compressed_payload, filename="30/4/1/42", diff --git a/tests/sentry/replays/unit/test_ingest.py b/tests/sentry/replays/unit/test_ingest.py index 542c7b89fecd94..29c92e02c9c263 100644 --- a/tests/sentry/replays/unit/test_ingest.py +++ b/tests/sentry/replays/unit/test_ingest.py @@ -29,6 +29,7 @@ def test_process_recording_event_without_video() -> None: "replay_id": "test-replay-id", "retention_days": 30, "segment_id": 42, + "should_publish_replay_event": False, }, "payload": payload, "payload_compressed": payload_compressed, @@ -64,6 +65,7 @@ def test_process_recording_event_with_video() -> None: "replay_id": "video-replay-id", "retention_days": 90, "segment_id": 1, + "should_publish_replay_event": False, }, "payload": payload, "payload_compressed": payload_compressed, @@ -99,6 +101,7 @@ def test_parse_replay_events_empty() -> None: "replay_id": "1", "retention_days": 1, "segment_id": 1, + "should_publish_replay_event": False, }, "payload": b"[]", "payload_compressed": b"", @@ -121,6 +124,7 @@ def test_parse_replay_events_invalid_json() -> None: "replay_id": "1", "retention_days": 1, "segment_id": 1, + "should_publish_replay_event": False, }, "payload": b"hello, world!", "payload_compressed": b"", From 7b489f0203130f9255f3cdcf794e059b39208514 Mon Sep 17 00:00:00 2001 From: Colton Allen Date: Thu, 28 Aug 2025 10:57:32 -0500 Subject: [PATCH 7/7] Clean up --- src/sentry/replays/consumers/recording.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sentry/replays/consumers/recording.py b/src/sentry/replays/consumers/recording.py index abcb703eeb09db..961a288a0e5701 100644 --- a/src/sentry/replays/consumers/recording.py +++ b/src/sentry/replays/consumers/recording.py @@ -138,7 +138,12 @@ def parse_recording_event(message: bytes) -> Event: replay_video = None relay_snuba_publish_disabled = recording.get("relay_snuba_publish_disabled", False) - if not isinstance(relay_snuba_publish_disabled, bool): + + # No matter what value we receive "True" is the only value that can influence our behavior. + # Otherwise we default to "False" which means our consumer does nothing. Its only when Relay + # reports that it has disabled itself that we publish to the Snuba consumer. Any other value + # is invalid and means we should _not_ publish to Snuba. + if relay_snuba_publish_disabled is not True: relay_snuba_publish_disabled = False return {