Skip to content

Commit 396c112

Browse files
committed
use 99th percentile in slow frame
1 parent 303ea88 commit 396c112

File tree

1 file changed

+92
-21
lines changed

1 file changed

+92
-21
lines changed

indexer-selection/src/performance.rs

+92-21
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use candidate_selection::Normalized;
22

33
#[derive(Clone, Debug, Default)]
44
pub struct Performance {
5-
fast: Frame,
6-
slow: Frame,
5+
fast: ShortTerm,
6+
slow: LongTerm,
77
}
88

99
const FAST_BIAS: f64 = 0.8;
@@ -25,14 +25,8 @@ impl Performance {
2525
}
2626

2727
pub fn feedback(&mut self, success: bool, latency_ms: u16) {
28-
for frame in [&mut self.fast, &mut self.slow] {
29-
frame.total_latency_ms += latency_ms as f64;
30-
if success {
31-
frame.success_count += 1.0;
32-
} else {
33-
frame.failure_count += 1.0;
34-
}
35-
}
28+
self.fast.feedback(success, latency_ms);
29+
self.slow.feedback(success, latency_ms);
3630
}
3731

3832
pub fn decay(&mut self) {
@@ -41,26 +35,28 @@ impl Performance {
4135
}
4236

4337
fn success_rate(&self) -> Normalized {
44-
let fast = self.fast.success_rate().as_f64();
45-
let slow = self.slow.success_rate().as_f64();
46-
Normalized::new((fast * FAST_BIAS) + (slow * (1.0 - FAST_BIAS))).unwrap_or(Normalized::ONE)
38+
let fast = self.fast.success_rate();
39+
let slow = self.slow.success_rate();
40+
let success_rate = (fast * FAST_BIAS) + (slow * (1.0 - FAST_BIAS));
41+
// limit an individual indexer's success rate to 99%
42+
Normalized::new(success_rate.min(0.99)).unwrap()
4743
}
4844

4945
fn latency_ms(&self) -> u16 {
5046
let fast = self.fast.latency_ms() as f64;
51-
let slow = self.slow.latency_ms() as f64;
47+
let slow = self.slow.latency_percentile(99) as f64;
5248
((fast * FAST_BIAS) + (slow * (1.0 - FAST_BIAS))) as u16
5349
}
5450
}
5551

5652
#[derive(Clone, Debug, Default)]
57-
struct Frame {
53+
struct ShortTerm {
5854
total_latency_ms: f64,
5955
success_count: f64,
6056
failure_count: f64,
6157
}
6258

63-
impl Frame {
59+
impl ShortTerm {
6460
fn decay(&mut self, rate_hz: f64) {
6561
debug_assert!((0.0 < rate_hz) && (rate_hz < 1.0));
6662
let retain = 1.0 - rate_hz;
@@ -69,13 +65,20 @@ impl Frame {
6965
self.failure_count *= retain;
7066
}
7167

72-
fn success_rate(&self) -> Normalized {
73-
// add 1 to pull success rate upward
68+
fn feedback(&mut self, success: bool, latency_ms: u16) {
69+
self.total_latency_ms += latency_ms as f64;
70+
if success {
71+
self.success_count += 1.0;
72+
} else {
73+
self.failure_count += 1.0;
74+
}
75+
}
76+
77+
fn success_rate(&self) -> f64 {
78+
// add 1 to pull success rate upward, and avoid divide by zero
7479
let s = self.success_count + 1.0;
7580
let f = self.failure_count;
76-
let p = s / (s + f);
77-
// limit an individual indexer's success rate to 99%
78-
Normalized::new(p.min(0.99)).unwrap()
81+
s / (s + f)
7982
}
8083

8184
fn latency_ms(&self) -> u16 {
@@ -84,3 +87,71 @@ impl Frame {
8487
avg_latency_ms as u16
8588
}
8689
}
90+
91+
#[derive(Clone, Debug, Default)]
92+
struct LongTerm {
93+
latency_hist: [f32; 29],
94+
failure_count: f64,
95+
}
96+
97+
const LATENCY_BINS: [u16; 29] = [
98+
32, 64, 96, 128, // + 2^5
99+
192, 256, 320, 384, // + 2^6
100+
512, 640, 768, 896, // + 2^7
101+
1152, 1408, 1664, 1920, // + 2^8
102+
2432, 2944, 3456, 3968, // + 2^9
103+
4992, 6016, 7040, 8064, // + 2^10
104+
10112, 12160, 14208, 16256, // + 2^11
105+
20352, // + 2^12
106+
];
107+
108+
impl LongTerm {
109+
fn decay(&mut self, rate_hz: f64) {
110+
debug_assert!((0.0 < rate_hz) && (rate_hz < 1.0));
111+
let retain = 1.0 - rate_hz;
112+
self.failure_count *= retain;
113+
for count in &mut self.latency_hist {
114+
*count *= retain as f32;
115+
}
116+
}
117+
118+
fn feedback(&mut self, success: bool, latency_ms: u16) {
119+
if !success {
120+
self.failure_count += 1.0;
121+
}
122+
123+
for (count, bin_value) in self
124+
.latency_hist
125+
.iter_mut()
126+
.zip(&LATENCY_BINS)
127+
.take(LATENCY_BINS.len() - 1)
128+
{
129+
if latency_ms <= *bin_value {
130+
*count += 1.0;
131+
return;
132+
}
133+
}
134+
*self.latency_hist.last_mut().unwrap() += 1.0;
135+
}
136+
137+
fn success_rate(&self) -> f64 {
138+
// add 1 to pull success rate upward, and avoid divide by zero
139+
let total = self.latency_hist.iter().map(|c| *c as f64).sum::<f64>() + 1.0;
140+
let s = total - self.failure_count;
141+
let f = self.failure_count;
142+
s / (s + f)
143+
}
144+
145+
pub fn latency_percentile(&self, p: u8) -> u16 {
146+
debug_assert!((1..=99).contains(&p));
147+
let target = self.latency_hist.iter().map(|c| *c as f64).sum::<f64>() * (p as f64 / 100.0);
148+
let mut sum = 0.0;
149+
for (count, bin_value) in self.latency_hist.iter().zip(&LATENCY_BINS) {
150+
sum += *count as f64;
151+
if sum >= target {
152+
return *bin_value;
153+
}
154+
}
155+
panic!("failed to calculate latency percentile");
156+
}
157+
}

0 commit comments

Comments
 (0)