Skip to content

Commit 35f5d86

Browse files
authored
ref(cache): Move buffering of pending envelope to ProjectCache (#1879)
These changes moving buffering of the incoming envelopes in the `ProjectCache`. Current implementation still keeps, so called queue in memory and using `HashMap` with a composite key `QueueKey {key, sampling_key}`, where `sampling_key` can be the same as a key if there is no sampling project identified. The values to these keys are `Vec` of boxed `Envelope` with their `EnvelopeContext`. Once we get an update for project state, we check all variants of `QueueKey` which contains the current `ProjectKey` and if all the project states are cached we try to flush buffered envelopes indexed by these `QeueuKey`. The envelops will be buffered if: * the project state is not fetched yet * root project is here but the sampling project state is not fetched yet * the sampling project state is here but the root project is not fetched yet This change also removes all the buffering from the `Project` and reduces its responsibility. Now it just keeps its own state and configuration and the envelope handling is done outside of it.
1 parent 6161ae8 commit 35f5d86

File tree

4 files changed

+215
-170
lines changed

4 files changed

+215
-170
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Revert back the addition of metric names as tag on Sentry errors when relay drops metrics. ([#1873](https://github.com/getsentry/relay/pull/1873))
2222
- Tag the dynamic sampling decision on `count_per_root_project` to measure effective sample rates. ([#1870](https://github.com/getsentry/relay/pull/1870))
2323
- Deprecate fields on the profiling sample format. ([#1878](https://github.com/getsentry/relay/pull/1878))
24+
- Move the pending envelopes buffering into the project cache. ([#1879](https://github.com/getsentry/relay/pull/1879))
2425

2526
## 23.2.0
2627

relay-server/src/actors/project.rs

Lines changed: 6 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::VecDeque;
21
use std::sync::Arc;
32
use std::time::Duration;
43

@@ -20,16 +19,15 @@ use relay_system::BroadcastChannel;
2019

2120
use crate::actors::envelopes::{EnvelopeManager, SendMetrics};
2221
use crate::actors::outcome::{DiscardReason, Outcome};
23-
use crate::actors::processor::{EnvelopeProcessor, ProcessEnvelope};
24-
use crate::actors::project_cache::{
25-
AddSamplingState, CheckedEnvelope, ProjectCache, RequestUpdate,
26-
};
22+
#[cfg(feature = "processing")]
23+
use crate::actors::processor::EnvelopeProcessor;
24+
use crate::actors::project_cache::{CheckedEnvelope, ProjectCache, RequestUpdate};
2725
use crate::envelope::Envelope;
2826
use crate::extractors::RequestMeta;
2927

3028
use crate::service::Registry;
3129
use crate::statsd::RelayCounters;
32-
use crate::utils::{self, EnvelopeContext, EnvelopeLimiter, MetricsLimiter, RetryBackoff};
30+
use crate::utils::{EnvelopeContext, EnvelopeLimiter, MetricsLimiter, RetryBackoff};
3331

3432
#[cfg(feature = "processing")]
3533
use crate::actors::processor::RateLimitFlushBuckets;
@@ -399,8 +397,6 @@ pub struct Project {
399397
config: Arc<Config>,
400398
state: Option<Arc<ProjectState>>,
401399
state_channel: Option<StateChannel>,
402-
pending_validations: VecDeque<(Box<Envelope>, EnvelopeContext)>,
403-
pending_sampling: VecDeque<ProcessEnvelope>,
404400
rate_limits: RateLimits,
405401
last_no_cache: Instant,
406402
}
@@ -416,8 +412,6 @@ impl Project {
416412
config,
417413
state: None,
418414
state_channel: None,
419-
pending_validations: VecDeque::new(),
420-
pending_sampling: VecDeque::new(),
421415
rate_limits: RateLimits::new(),
422416
last_no_cache: Instant::now(),
423417
}
@@ -453,7 +447,7 @@ impl Project {
453447
/// Returns the project state if it is not expired.
454448
///
455449
/// Convenience wrapper around [`expiry_state`](Self::expiry_state).
456-
fn valid_state(&self) -> Option<Arc<ProjectState>> {
450+
pub fn valid_state(&self) -> Option<Arc<ProjectState>> {
457451
match self.expiry_state() {
458452
ExpiryState::Updated(state) => Some(state),
459453
ExpiryState::Stale(state) => Some(state),
@@ -642,93 +636,9 @@ impl Project {
642636
self.get_cached_state(no_cache);
643637
}
644638

645-
/// Validates the envelope and submits the envelope to the next stage.
646-
///
647-
/// If this project is disabled or rate limited, corresponding items are dropped from the
648-
/// envelope. Remaining items in the Envelope are forwarded:
649-
/// - If the envelope needs dynamic sampling, this sends [`AddSamplingState`] to the
650-
/// [`ProjectCache`] to add the required project state.
651-
/// - Otherwise, the envelope is directly submitted to the [`EnvelopeProcessor`].
652-
fn flush_validation(
653-
&mut self,
654-
envelope: Box<Envelope>,
655-
envelope_context: EnvelopeContext,
656-
project_state: Arc<ProjectState>,
657-
) {
658-
if let Ok(checked) = self.check_envelope(envelope, envelope_context) {
659-
if let Some((envelope, envelope_context)) = checked.envelope {
660-
let mut process = ProcessEnvelope {
661-
envelope,
662-
envelope_context,
663-
project_state,
664-
sampling_project_state: None,
665-
};
666-
667-
if let Some(sampling_key) = utils::get_sampling_key(&process.envelope) {
668-
let own_key = process
669-
.project_state
670-
.get_public_key_config()
671-
.map(|c| c.public_key);
672-
673-
if Some(sampling_key) == own_key {
674-
process.sampling_project_state = Some(process.project_state.clone());
675-
EnvelopeProcessor::from_registry().send(process);
676-
} else {
677-
ProjectCache::from_registry()
678-
.send(AddSamplingState::new(sampling_key, process));
679-
}
680-
} else {
681-
EnvelopeProcessor::from_registry().send(process);
682-
}
683-
}
684-
}
685-
}
686-
687-
/// Enqueues an envelope for validation.
688-
///
689-
/// If the project state is up to date, the message will be immediately sent to the next stage.
690-
/// Otherwise, this queues the envelope and flushes it when the project has been updated.
691-
///
692-
/// This method will trigger an update of the project state internally if the state is stale or
693-
/// outdated.
694-
pub fn enqueue_validation(&mut self, envelope: Box<Envelope>, context: EnvelopeContext) {
695-
match self.get_cached_state(envelope.meta().no_cache()) {
696-
Some(state) if !state.invalid() => self.flush_validation(envelope, context, state),
697-
_ => self.pending_validations.push_back((envelope, context)),
698-
}
699-
}
700-
701-
/// Adds the project state for dynamic sampling and submits the Envelope for processing.
702-
fn flush_sampling(&self, mut message: ProcessEnvelope) {
703-
// Intentionally ignore all errors. Fallback sampling behavior applies in this case.
704-
if let Some(state) = self.valid_state().filter(|state| !state.invalid()) {
705-
// Never use rules from another organization.
706-
if state.organization_id == message.project_state.organization_id {
707-
message.sampling_project_state = Some(state);
708-
}
709-
}
710-
711-
EnvelopeProcessor::from_registry().send(message);
712-
}
713-
714-
/// Enqueues an envelope for adding a dynamic sampling project state.
715-
///
716-
/// If the project state is up to date, the message will be immediately submitted for
717-
/// processing. Otherwise, this queues the envelope and flushes it when the project has been
718-
/// updated.
719-
///
720-
/// This method will trigger an update of the project state internally if the state is stale or
721-
/// outdated.
722-
pub fn enqueue_sampling(&mut self, message: ProcessEnvelope) {
723-
match self.get_cached_state(message.envelope.meta().no_cache()) {
724-
Some(_) => self.flush_sampling(message),
725-
None => self.pending_sampling.push_back(message),
726-
}
727-
}
728-
729639
/// Replaces the internal project state with a new one and triggers pending actions.
730640
///
731-
/// This flushes pending envelopes from [`ValidateEnvelope`] and [`AddSamplingState`] and
641+
/// This flushes pending envelopes from [`ValidateEnvelope`] and
732642
/// notifies all pending receivers from [`get_state`](Self::get_state).
733643
///
734644
/// `no_cache` should be passed from the requesting call. Updates with `no_cache` will always
@@ -775,16 +685,6 @@ impl Project {
775685
return;
776686
}
777687

778-
// Flush all queued `ValidateEnvelope` messages
779-
while let Some((envelope, context)) = self.pending_validations.pop_front() {
780-
self.flush_validation(envelope, context, state.clone());
781-
}
782-
783-
// Flush all queued `AddSamplingState` messages
784-
while let Some(message) = self.pending_sampling.pop_front() {
785-
self.flush_sampling(message);
786-
}
787-
788688
// Flush all waiting recipients.
789689
relay_log::debug!("project state {} updated", self.project_key);
790690
channel.inner.send(state);
@@ -921,18 +821,6 @@ impl Project {
921821
}
922822
}
923823

924-
impl Drop for Project {
925-
fn drop(&mut self) {
926-
let count = self.pending_validations.len() + self.pending_sampling.len();
927-
if count > 0 {
928-
relay_log::with_scope(
929-
|scope| scope.set_tag("project_key", self.project_key),
930-
|| relay_log::error!("dropped project with {} envelopes", count),
931-
);
932-
}
933-
}
934-
}
935-
936824
#[cfg(test)]
937825
mod tests {
938826
use std::sync::Arc;

0 commit comments

Comments
 (0)