From de9d00c1e9e8f1bda533c16acda76cbf6e3b5326 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 28 May 2026 09:49:45 +0200 Subject: [PATCH 1/7] ref: Route raw profiles through objectstore service Extract StoreRawProfile into the objectstore service and replace inline raw profile blob with objectstore key reference in the Kafka message. Co-Authored-By: Claude --- .../src/processing/profile_chunks/mod.rs | 32 +++++-- relay-server/src/services/objectstore.rs | 91 ++++++++++++++++++- relay-server/src/services/store.rs | 26 +++--- 3 files changed, 127 insertions(+), 22 deletions(-) diff --git a/relay-server/src/processing/profile_chunks/mod.rs b/relay-server/src/processing/profile_chunks/mod.rs index 812cc838410..d639268d577 100644 --- a/relay-server/src/processing/profile_chunks/mod.rs +++ b/relay-server/src/processing/profile_chunks/mod.rs @@ -230,6 +230,7 @@ impl Forward for ProfileChunkOutput { s: processing::forward::StoreHandle<'_>, ctx: processing::ForwardContext<'_>, ) -> Result<(), Rejected<()>> { + use crate::services::objectstore::StoreRawProfile; use crate::services::store::StoreProfileChunk; let expanded = match self { @@ -243,12 +244,31 @@ impl Forward for ProfileChunkOutput { let retention_days = ctx.event_retention().standard; for chunk in expanded.split(|e| e.chunks) { - s.send_to_store(chunk.map(|chunk, _| StoreProfileChunk { - retention_days, - payload: chunk.payload, - quantities: chunk.quantities, - raw_profile: chunk.raw_profile, - })); + if chunk.raw_profile.is_some() { + s.send_to_objectstore(chunk.map(|chunk, _| { + let raw_profile = chunk.raw_profile.unwrap(); + StoreRawProfile { + payload: raw_profile.payload, + content_type: raw_profile.content_type, + store_message: StoreProfileChunk { + retention_days, + payload: chunk.payload, + quantities: chunk.quantities, + raw_profile_object_store_key: None, + raw_profile_content_type: None, + }, + retention: retention_days, + } + })); + } else { + s.send_to_store(chunk.map(|chunk, _| StoreProfileChunk { + retention_days, + payload: chunk.payload, + quantities: chunk.quantities, + raw_profile_object_store_key: None, + raw_profile_content_type: None, + })); + } } Ok(()) diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index 7cdd3d8a6ef..de41ce0362e 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -20,13 +20,15 @@ use relay_system::{ use sentry_protos::snuba::v1::TraceItem; use crate::constants::DEFAULT_ATTACHMENT_RETENTION; -use crate::envelope::{Item, ItemType}; +use crate::envelope::{ContentType, Item, ItemType}; use crate::managed::{ Counted, Managed, ManagedEnvelope, ManagedResult, OutcomeError, Quantities, Rejected, }; use crate::processing::utils::store::item_id_to_uuid; use crate::services::outcome::DiscardReason; -use crate::services::store::{Store, StoreAttachment, StoreEnvelope, StoreTraceItem}; +use crate::services::store::{ + Store, StoreAttachment, StoreEnvelope, StoreProfileChunk, StoreTraceItem, +}; use crate::services::upload::ByteStream; use crate::statsd::{RelayCounters, RelayTimers}; use crate::utils::{BoundedStream, MeteredStream, RetryableStream, TakeOnce}; @@ -38,6 +40,7 @@ pub enum Objectstore { Envelope(StoreEnvelope), TraceAttachment(Managed), EventAttachment(Managed), + RawProfile(Managed), Stream(Stream, Sender>), } @@ -47,6 +50,7 @@ impl Objectstore { Self::Envelope(_) => MessageKind::Envelope, Self::TraceAttachment(_) => MessageKind::TraceAttachment, Self::EventAttachment(_) => MessageKind::EventAttachment, + Self::RawProfile(_) => MessageKind::RawProfile, Self::Stream { .. } => MessageKind::Stream, } } @@ -60,6 +64,7 @@ impl Objectstore { .count(), Self::TraceAttachment(_) => 1, Self::EventAttachment(_) => 1, + Self::RawProfile(_) => 1, Self::Stream { .. } => 1, } } @@ -91,12 +96,21 @@ impl FromMessage> for Objectstore { } } +impl FromMessage> for Objectstore { + type Response = NoResponse; + + fn from_message(message: Managed, _sender: ()) -> Self { + Self::RawProfile(message) + } +} + /// A type tag used for logging. #[derive(Debug, Clone, Copy)] enum MessageKind { Envelope, EventAttachment, TraceAttachment, + RawProfile, Stream, } @@ -106,6 +120,7 @@ impl MessageKind { Self::Envelope => "envelope", Self::EventAttachment => "attachment", Self::TraceAttachment => "attachment_v2", + Self::RawProfile => "raw_profile", Self::Stream => "stream", } } @@ -143,6 +158,28 @@ impl Counted for StoreTraceAttachment { } } +/// A raw profile (e.g. Perfetto trace) ready for objectstore upload. +/// +/// After upload, the [`StoreProfileChunk`] is forwarded to the Store service +/// with the objectstore key set, so the Kafka message carries a reference +/// instead of the full binary blob. +pub struct StoreRawProfile { + /// The raw binary profile payload to upload. + pub payload: Bytes, + /// Content type of the raw profile. + pub content_type: ContentType, + /// The profile chunk message to forward to Kafka after upload. + pub store_message: StoreProfileChunk, + /// Data retention in days. + pub retention: u16, +} + +impl Counted for StoreRawProfile { + fn quantities(&self) -> Quantities { + self.store_message.quantities() + } +} + #[derive(Debug, thiserror::Error)] #[error("objectstore upload failed")] pub struct Error { @@ -302,12 +339,15 @@ impl ObjectstoreService { .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); let trace_attachments = Usecase::new("trace_attachments") .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); + let profiles = Usecase::new("profiles") + .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); let inner = ObjectstoreServiceInner { store, objectstore_client, event_attachments, trace_attachments, + profiles, timeout: Duration::from_secs(*timeout), stream_timeout: Duration::from_secs(*stream_timeout), retry_interval: Duration::from_secs_f64(*retry_delay), @@ -344,6 +384,11 @@ impl LoadShed for ObjectstoreService { Objectstore::TraceAttachment(managed) => { let _ = managed.reject_err(error); } + Objectstore::RawProfile(managed) => { + self.inner + .store + .send(managed.map(|profile, _| profile.store_message)); + } Objectstore::Stream(_, sender) => { sender.send(Err(error)); } @@ -357,6 +402,7 @@ struct ObjectstoreServiceInner { objectstore_client: Client, event_attachments: Usecase, trace_attachments: Usecase, + profiles: Usecase, timeout: Duration, stream_timeout: Duration, retry_interval: Duration, @@ -381,6 +427,9 @@ impl ObjectstoreServiceInner { Objectstore::EventAttachment(attachment) => { self.handle_event_attachment(attachment).await; } + Objectstore::RawProfile(profile) => { + self.handle_raw_profile(profile).await; + } Objectstore::Stream(stream, sender) => { let result = self.handle_stream(stream).await; if let Err(error) = &result { @@ -546,6 +595,44 @@ impl ObjectstoreServiceInner { Ok(()) } + async fn handle_raw_profile(&self, managed: Managed) { + let scoping = managed.scoping(); + let session = self + .profiles + .for_project(scoping.organization_id.value(), scoping.project_id.value()) + .session(&self.objectstore_client); + + let payload = managed.payload.clone(); + let content_type = managed.content_type; + let retention = managed.retention; + + let mut store_message = managed.map(|profile, _| profile.store_message); + + if let Ok(session) = session + && !payload.is_empty() + { + let result = self + .upload_bytes(MessageKind::RawProfile, &session, payload, retention, None) + .await; + + match result { + Ok(stored_key) => { + store_message.modify(|msg, _| { + msg.raw_profile_object_store_key = Some(stored_key.into_inner()); + msg.raw_profile_content_type = Some(content_type); + }); + } + Err(error) => { + error.log(MessageKind::RawProfile); + } + } + } + + // Always forward to store even if the raw profile upload failed, + // to ensure the kafka message is produced. + self.store.send(store_message); + } + async fn handle_stream(&self, stream: Stream) -> Result { let Stream { organization_id, diff --git a/relay-server/src/services/store.rs b/relay-server/src/services/store.rs index 1bf6cd7ea92..6e84feda571 100644 --- a/relay-server/src/services/store.rs +++ b/relay-server/src/services/store.rs @@ -36,7 +36,6 @@ use relay_threading::AsyncPool; use crate::envelope::{AttachmentPlaceholder, AttachmentType, ContentType, Item, ItemType}; use crate::managed::{Counted, Managed, ManagedEnvelope, OutcomeError, Quantities, Rejected}; use crate::metrics::{ArrayEncoding, BucketEncoder, MetricOutcomes}; -use crate::processing::profile_chunks::RawProfile; use crate::service::ServiceError; use crate::services::global_config::GlobalConfigHandle; use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome}; @@ -159,11 +158,10 @@ pub struct StoreProfileChunk { /// /// Quantities are different for backend and ui profile chunks. pub quantities: Quantities, - /// Raw binary profile blob (e.g. Perfetto trace). - /// - /// Sent alongside the expanded JSON payload because the expansion only extracts a - /// minimum of information; the raw profile is preserved for further processing downstream. - pub raw_profile: Option, + /// Objectstore key where the raw profile blob is stored. + pub raw_profile_object_store_key: Option, + /// Content type of the raw profile (e.g. Perfetto trace). + pub raw_profile_content_type: Option, } impl Counted for StoreProfileChunk { @@ -854,8 +852,8 @@ impl StoreService { scoping.project_id.to_string(), )]), payload: message.payload, - raw_profile: message.raw_profile.as_ref().map(|r| r.payload.clone()), - raw_profile_content_type: message.raw_profile.map(|r| r.content_type), + raw_profile_object_store_key: message.raw_profile_object_store_key, + raw_profile_content_type: message.raw_profile_content_type, }; self.produce(KafkaTopic::Profiles, KafkaMessage::ProfileChunk(message)) @@ -1697,7 +1695,7 @@ struct ProfileChunkKafkaMessage { headers: BTreeMap, payload: Bytes, #[serde(skip_serializing_if = "Option::is_none")] - raw_profile: Option, + raw_profile_object_store_key: Option, #[serde(skip_serializing_if = "Option::is_none")] raw_profile_content_type: Option, } @@ -1935,18 +1933,18 @@ mod tests { retention_days: 90, headers: BTreeMap::new(), payload: Bytes::from(b"{\"profile\":true}".as_ref()), - raw_profile: None, + raw_profile_object_store_key: None, raw_profile_content_type: None, }; let json = serde_json::to_value(&message).unwrap(); assert_eq!(json["organization_id"], 1); assert_eq!(json["project_id"], 42); - assert!(json.get("raw_profile").is_none()); + assert!(json.get("raw_profile_object_store_key").is_none()); assert!(json.get("raw_profile_content_type").is_none()); } #[test] - fn test_profile_chunk_kafka_message_with_raw_profile() { + fn test_profile_chunk_kafka_message_with_raw_profile_key() { let message = ProfileChunkKafkaMessage { organization_id: OrganizationId::new(1), project_id: ProjectId::new(42), @@ -1954,13 +1952,13 @@ mod tests { retention_days: 90, headers: BTreeMap::new(), payload: Bytes::from(b"{\"profile\":true}".as_ref()), - raw_profile: Some(Bytes::from(b"perfetto-binary-data".as_ref())), + raw_profile_object_store_key: Some("abc123def456".to_owned()), raw_profile_content_type: Some(crate::envelope::ContentType::PerfettoTrace), }; let json = serde_json::to_value(&message).unwrap(); assert_eq!(json["organization_id"], 1); assert_eq!(json["project_id"], 42); - assert!(json.get("raw_profile").is_some()); + assert_eq!(json["raw_profile_object_store_key"], "abc123def456"); assert_eq!( json["raw_profile_content_type"], "application/x-perfetto-trace" From 3a83bc5bc1d6bfb9ad8b8f09e395b1e1f5e35191 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 28 May 2026 10:03:40 +0200 Subject: [PATCH 2/7] ref(objectstore): Rename raw profile usecase to profiles_raw Align the objectstore usecase identifier with the naming convention so it can be easily distinguished from a future profiles_v2 usecase. Co-Authored-By: Claude --- relay-server/src/services/objectstore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index de41ce0362e..4a6879ef68b 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -120,7 +120,7 @@ impl MessageKind { Self::Envelope => "envelope", Self::EventAttachment => "attachment", Self::TraceAttachment => "attachment_v2", - Self::RawProfile => "raw_profile", + Self::RawProfile => "profiles_raw", Self::Stream => "stream", } } From 2d7a8288657811faf147e0e447d4e2aad4893ab7 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 28 May 2026 10:36:48 +0200 Subject: [PATCH 3/7] fix: Ensure Kafka message is produced when objectstore is unavailable Add try_send_to_objectstore to StoreHandle, which returns the message when the objectstore service is not configured. Profile chunks fall back to sending the StoreProfileChunk directly to Store, preventing data loss. Also log session creation errors matching the pattern in other objectstore handlers. Co-Authored-By: Claude --- relay-server/src/processing/forward.rs | 16 +++++++++ .../src/processing/profile_chunks/mod.rs | 7 ++-- relay-server/src/services/objectstore.rs | 34 ++++++++++--------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/relay-server/src/processing/forward.rs b/relay-server/src/processing/forward.rs index a4ecb1832cd..7fa51de507f 100644 --- a/relay-server/src/processing/forward.rs +++ b/relay-server/src/processing/forward.rs @@ -57,6 +57,22 @@ impl<'a> StoreHandle<'a> { } } + /// Tries to send a message to the [`Objectstore`] service. + /// + /// Returns the message back if the service is not configured, + /// allowing the caller to handle the fallback. + pub fn try_send_to_objectstore(&self, message: M) -> Option + where + Objectstore: FromMessage, + { + if let Some(objectstore) = self.objectstore { + objectstore.send(message); + None + } else { + Some(message) + } + } + /// Dispatches an envelopes to either the [`Objectstore`] or [`Store`] service. pub fn send_envelope(&self, envelope: ManagedEnvelope) { use crate::services::store::StoreEnvelope; diff --git a/relay-server/src/processing/profile_chunks/mod.rs b/relay-server/src/processing/profile_chunks/mod.rs index d639268d577..7dbc20bd191 100644 --- a/relay-server/src/processing/profile_chunks/mod.rs +++ b/relay-server/src/processing/profile_chunks/mod.rs @@ -245,7 +245,7 @@ impl Forward for ProfileChunkOutput { for chunk in expanded.split(|e| e.chunks) { if chunk.raw_profile.is_some() { - s.send_to_objectstore(chunk.map(|chunk, _| { + let msg = chunk.map(|chunk, _| { let raw_profile = chunk.raw_profile.unwrap(); StoreRawProfile { payload: raw_profile.payload, @@ -259,7 +259,10 @@ impl Forward for ProfileChunkOutput { }, retention: retention_days, } - })); + }); + if let Some(unsent) = s.try_send_to_objectstore(msg) { + s.send_to_store(unsent.map(|profile, _| profile.store_message)); + } } else { s.send_to_store(chunk.map(|chunk, _| StoreProfileChunk { retention_days, diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index 4a6879ef68b..d4c21f6e3b6 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -608,24 +608,26 @@ impl ObjectstoreServiceInner { let mut store_message = managed.map(|profile, _| profile.store_message); - if let Ok(session) = session - && !payload.is_empty() - { - let result = self - .upload_bytes(MessageKind::RawProfile, &session, payload, retention, None) - .await; - - match result { - Ok(stored_key) => { - store_message.modify(|msg, _| { - msg.raw_profile_object_store_key = Some(stored_key.into_inner()); - msg.raw_profile_content_type = Some(content_type); - }); - } - Err(error) => { - error.log(MessageKind::RawProfile); + match session { + Err(error) => Error::from(error).log(MessageKind::RawProfile), + Ok(session) if !payload.is_empty() => { + let result = self + .upload_bytes(MessageKind::RawProfile, &session, payload, retention, None) + .await; + + match result { + Ok(stored_key) => { + store_message.modify(|msg, _| { + msg.raw_profile_object_store_key = Some(stored_key.into_inner()); + msg.raw_profile_content_type = Some(content_type); + }); + } + Err(error) => { + error.log(MessageKind::RawProfile); + } } } + Ok(_) => {} } // Always forward to store even if the raw profile upload failed, From eb1a0e7d73a29ba118ce65cf0284f49489f0088a Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 28 May 2026 12:28:07 +0200 Subject: [PATCH 4/7] fix(objectstore): Use profiles_raw namespace in Usecase constructor The rename in 3a83bc5bc1 only updated MessageKind::as_str() but missed the Usecase::new() call, causing raw profiles to be stored under the generic "profiles" namespace instead of "profiles_raw". Co-Authored-By: Claude Opus 4.6 --- relay-server/src/services/objectstore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index d4c21f6e3b6..d954c7916d2 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -339,7 +339,7 @@ impl ObjectstoreService { .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); let trace_attachments = Usecase::new("trace_attachments") .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); - let profiles = Usecase::new("profiles") + let profiles = Usecase::new("profiles_raw") .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); let inner = ObjectstoreServiceInner { From 907aa48b61e04e75b969361ce28c2d4b75b2e9ff Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Fri, 29 May 2026 11:18:14 +0200 Subject: [PATCH 5/7] feat(profiling): Store content type on raw profile objectstore upload Co-Authored-By: Claude --- relay-server/src/services/objectstore.rs | 41 ++++++++++++++-- .../test_profile_chunks_perfetto.py | 47 ++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index d954c7916d2..9144bba17e7 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -473,6 +473,7 @@ impl ObjectstoreServiceInner { attachment.payload(), retention, None, + None, ) .await; @@ -518,6 +519,7 @@ impl ObjectstoreServiceInner { attachment.attachment.payload(), attachment.retention, None, + None, ) .await; @@ -581,6 +583,7 @@ impl ObjectstoreServiceInner { body, retention, Some(key), + None, ) .await .reject(&trace_item)?; @@ -612,7 +615,14 @@ impl ObjectstoreServiceInner { Err(error) => Error::from(error).log(MessageKind::RawProfile), Ok(session) if !payload.is_empty() => { let result = self - .upload_bytes(MessageKind::RawProfile, &session, payload, retention, None) + .upload_bytes( + MessageKind::RawProfile, + &session, + payload, + retention, + None, + Some(content_type), + ) .await; match result { @@ -653,6 +663,7 @@ impl ObjectstoreServiceInner { Some(key), Body::Stream(TakeOnce::new(stream)), None, + None, ) .await } @@ -664,10 +675,18 @@ impl ObjectstoreServiceInner { payload: Bytes, retention: u16, key: Option, + content_type: Option, ) -> Result { let retention_hours = retention.checked_mul(24); - self.upload(kind, session, key, Body::Bytes(payload), retention_hours) - .await + self.upload( + kind, + session, + key, + Body::Bytes(payload), + retention_hours, + content_type, + ) + .await } async fn upload( @@ -677,6 +696,7 @@ impl ObjectstoreServiceInner { key: Option, body: Body, retention_hours: Option, + content_type: Option, ) -> Result { let mut attempts = 0; let timeout = match &body { @@ -691,8 +711,15 @@ impl ObjectstoreServiceInner { }; attempts += 1; result.replace( - self.attempt_upload(kind, session, key.clone(), body, retention_hours) - .await, + self.attempt_upload( + kind, + session, + key.clone(), + body, + retention_hours, + content_type, + ) + .await, ); if attempts < self.max_attempts.get() @@ -731,12 +758,16 @@ impl ObjectstoreServiceInner { key: Option, body: BodyAttempt, retention_hours: Option, + content_type: Option, ) -> Result { let mut request = match body { BodyAttempt::Bytes(bytes) => session.put(bytes), BodyAttempt::Stream(stream) => session.put_stream(stream.boxed()), }; + if let Some(content_type) = content_type { + request = request.content_type(content_type.as_str()); + } if let Some(retention_hours) = retention_hours { request = request.expiration_policy(ExpirationPolicy::TimeToLive( Duration::from_hours(retention_hours.into()), diff --git a/tests/integration/test_profile_chunks_perfetto.py b/tests/integration/test_profile_chunks_perfetto.py index f2a99b7adc7..a0b133ade82 100644 --- a/tests/integration/test_profile_chunks_perfetto.py +++ b/tests/integration/test_profile_chunks_perfetto.py @@ -103,6 +103,49 @@ def test_perfetto_profile_chunk_end_to_end( assert isinstance(tid, str) assert "name" in meta and isinstance(meta["name"], str) - assert "raw_profile" in profile, "expected raw_profile in Kafka message" - assert len(profile["raw_profile"]) == 97252, "raw_profile size mismatch" + assert profile[ + "raw_profile_object_store_key" + ], "expected raw_profile_object_store_key in Kafka message" assert profile.get("raw_profile_content_type") == "application/x-perfetto-trace" + + +def test_perfetto_profile_chunk_objectstore_content_type( + mini_sentry, + relay_with_processing, + outcomes_consumer, + profiles_consumer, + objectstore, +): + """ + Verifies that the raw Perfetto trace is uploaded to objectstore with its + content type preserved on the stored object, so a subsequent GET returns + the correct `Content-Type`. + """ + profiles_consumer = profiles_consumer() + outcomes_consumer = outcomes_consumer() + + project_id = 42 + project_config = mini_sentry.add_full_project_config(project_id)["config"] + project_config.setdefault("features", []).extend( + [ + "organizations:continuous-profiling", + "organizations:continuous-profiling-perfetto", + ] + ) + + upstream = relay_with_processing(TEST_CONFIG) + + with open(PERFETTO_ENVELOPE_FIXTURE, "rb") as f: + envelope = Envelope.deserialize_from(f) + + upstream.send_envelope(project_id, envelope) + + outcomes_consumer.assert_empty() + + profile, _ = profiles_consumer.get_profile() + object_store_key = profile["raw_profile_object_store_key"] + assert object_store_key, "expected raw_profile_object_store_key in Kafka message" + + stored = objectstore("profiles_raw", project_id).get(object_store_key) + assert stored.metadata.content_type == "application/x-perfetto-trace" + assert len(stored.payload.read()) == 97252 From 22f3895f65f75eefa90efa3b3b67343f3ec95ee4 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 8 Jun 2026 08:19:43 +0200 Subject: [PATCH 6/7] Update relay-server/src/services/objectstore.rs Co-authored-by: Sebastian Zivota --- relay-server/src/services/objectstore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index 9144bba17e7..6c9b0a3e662 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -120,7 +120,7 @@ impl MessageKind { Self::Envelope => "envelope", Self::EventAttachment => "attachment", Self::TraceAttachment => "attachment_v2", - Self::RawProfile => "profiles_raw", + Self::RawProfile => "profile_raw", Self::Stream => "stream", } } From dc7c95bfd60010b49479945f41f8574a267e0b87 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 8 Jun 2026 09:26:19 +0200 Subject: [PATCH 7/7] Add custom retention constant for raw profiles --- relay-server/src/constants.rs | 3 +++ relay-server/src/services/objectstore.rs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/relay-server/src/constants.rs b/relay-server/src/constants.rs index 89d0532e9ed..bf7184f192a 100644 --- a/relay-server/src/constants.rs +++ b/relay-server/src/constants.rs @@ -42,6 +42,9 @@ pub const DEFAULT_CHECK_IN_CLIENT: &str = "relay-http"; #[cfg(feature = "processing")] pub const DEFAULT_ATTACHMENT_RETENTION: Duration = Duration::from_hours(24 * 30); +#[cfg(feature = "processing")] +pub const DEFAULT_PROFILE_RETENTION: Duration = Duration::from_hours(24 * 90); + /// Magic number indicating the dying message file is encoded by sentry-switch SDK. pub const NNSWITCH_SENTRY_MAGIC: &[u8] = b"sntr"; diff --git a/relay-server/src/services/objectstore.rs b/relay-server/src/services/objectstore.rs index 6c9b0a3e662..eace29c04f5 100644 --- a/relay-server/src/services/objectstore.rs +++ b/relay-server/src/services/objectstore.rs @@ -19,7 +19,7 @@ use relay_system::{ }; use sentry_protos::snuba::v1::TraceItem; -use crate::constants::DEFAULT_ATTACHMENT_RETENTION; +use crate::constants::{DEFAULT_ATTACHMENT_RETENTION, DEFAULT_PROFILE_RETENTION}; use crate::envelope::{ContentType, Item, ItemType}; use crate::managed::{ Counted, Managed, ManagedEnvelope, ManagedResult, OutcomeError, Quantities, Rejected, @@ -339,8 +339,8 @@ impl ObjectstoreService { .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); let trace_attachments = Usecase::new("trace_attachments") .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); - let profiles = Usecase::new("profiles_raw") - .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_ATTACHMENT_RETENTION)); + let profiles = Usecase::new("profile_raw") + .with_expiration_policy(ExpirationPolicy::TimeToLive(DEFAULT_PROFILE_RETENTION)); let inner = ObjectstoreServiceInner { store,