Skip to content

Commit 6d87298

Browse files
authored
feat(dynamic-sampling): Introduce a new rule to raise the floor of the sample rate (#4801)
Every project can define a minimum ("floor") sample rate. Relay will evaluate two rules for each transaction for the base sample rate. trace rule: as usual. floor rule: ensures the minimum rate for the project. Relay should base its dynamic sampling decision on the larger of the two rates. Balancing rules should be applied to the floor rule to apply consistent up-sampling of rare transactions Example ``` Trace sample rate: 0.05 Floor Rule: 0.5 Transaction Rule: x * 1.5 ``` Result: ``` Base rate: 0.5 > 0.05 => 0.5 After applying balancing rules: 0.5 * 1.5 = 0.75 ``` --- Only the first rule is applied to make sure local transaction based rules apply before trace based rules. Refs: TET-565
1 parent 351f967 commit 6d87298

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
**Features**:
66

7+
- Implements a minimum sample rate dynamic sampling rule. ([#4801](https://github.com/getsentry/relay/pull/4801))
78
- Convert NEL reports into logs. ([#4813](https://github.com/getsentry/relay/pull/4813)
89
- Add parsing for _Nintendo Switch_ to populate `os.name="Nintendo OS"`. ([#4821](https://github.com/getsentry/relay/pull/4821))
910

relay-sampling/src/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ pub enum SamplingValue {
162162
/// The limit of how many times this rule will be sampled before this rule is invalid.
163163
limit: i64,
164164
},
165+
166+
/// A minimum sample rate.
167+
///
168+
/// The sample rate specified in the rule will be used as a minimum over the otherwise used
169+
/// sample rate.
170+
///
171+
/// Only the first matching minimum sample rate will be applied.
172+
MinimumSampleRate {
173+
/// The minimum sample rate used to raise the chosen sample rate.
174+
value: f64,
175+
},
165176
}
166177

167178
/// Defines what a dynamic sampling rule applies to.

relay-sampling/src/evaluation.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ pub struct SamplingEvaluator<'a> {
158158
now: DateTime<Utc>,
159159
rule_ids: Vec<RuleId>,
160160
factor: f64,
161+
minimum_sample_rate: Option<f64>,
161162
reservoir: Option<&'a ReservoirEvaluator<'a>>,
162163
}
163164

@@ -168,6 +169,7 @@ impl<'a> SamplingEvaluator<'a> {
168169
now,
169170
rule_ids: vec![],
170171
factor: 1.0,
172+
minimum_sample_rate: None,
171173
reservoir: Some(reservoir),
172174
}
173175
}
@@ -178,6 +180,7 @@ impl<'a> SamplingEvaluator<'a> {
178180
now,
179181
rule_ids: vec![],
180182
factor: 1.0,
183+
minimum_sample_rate: None,
181184
reservoir: None,
182185
}
183186
}
@@ -232,7 +235,8 @@ impl<'a> SamplingEvaluator<'a> {
232235
}
233236
SamplingValue::SampleRate { value } => {
234237
let sample_rate = rule.apply_decaying_fn(value, self.now)?;
235-
let adjusted = (sample_rate * self.factor).clamp(0.0, 1.0);
238+
let minimum_sample_rate = self.minimum_sample_rate.unwrap_or(0.0);
239+
let adjusted = (sample_rate.max(minimum_sample_rate) * self.factor).clamp(0.0, 1.0);
236240

237241
self.rule_ids.push(rule.id);
238242
Some(adjusted)
@@ -252,6 +256,13 @@ impl<'a> SamplingEvaluator<'a> {
252256
// If the reservoir has not yet reached its limit, we want to sample 100%.
253257
Some(1.0)
254258
}
259+
SamplingValue::MinimumSampleRate { value } => {
260+
if self.minimum_sample_rate.is_none() {
261+
self.minimum_sample_rate = Some(rule.apply_decaying_fn(value, self.now)?);
262+
self.rule_ids.push(rule.id);
263+
}
264+
None
265+
}
255266
}
256267
}
257268
}
@@ -537,6 +548,30 @@ mod tests {
537548
assert_eq!(get_sampling_match(&rules, &dsc).await.sample_rate(), 0.1);
538549
}
539550

551+
#[tokio::test]
552+
async fn test_minimum_sample_rate() {
553+
let rules = simple_sampling_rules(vec![
554+
(RuleCondition::all(), SamplingValue::Factor { value: 1.5 }),
555+
(
556+
RuleCondition::all(),
557+
SamplingValue::MinimumSampleRate { value: 0.5 },
558+
),
559+
// Only the first matching minimum is applied.
560+
(
561+
RuleCondition::all(),
562+
SamplingValue::MinimumSampleRate { value: 1.0 },
563+
),
564+
(
565+
RuleCondition::all(),
566+
SamplingValue::SampleRate { value: 0.05 },
567+
),
568+
]);
569+
let dsc = mocked_dsc_with_getter_values(vec![]);
570+
571+
// max(0.05, 0.5) * 1.5 = 0.75
572+
assert_eq!(get_sampling_match(&rules, &dsc).await.sample_rate(), 0.75);
573+
}
574+
540575
fn mocked_sampling_rule() -> SamplingRule {
541576
SamplingRule {
542577
condition: RuleCondition::all(),

0 commit comments

Comments
 (0)