Skip to content

Commit ed2c3e1

Browse files
feat(planner): deploy DDSketch from MILP plans (#806)
* feat(planner): deploy DDSketch from MILP plans * fix(planner): validate DDSketch alpha
1 parent d54bcea commit ed2c3e1

5 files changed

Lines changed: 185 additions & 63 deletions

File tree

‎Cargo.lock‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎asap-planner-rs/src/bin/optimizer_cli.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ struct Args {
7777
)]
7878
allow_undeployable_families: bool,
7979

80-
/// MILP only. YAML workload facts: per metric, `cardinality` per label
81-
/// set, including the set of all its labels (the series count).
80+
/// MILP only. YAML workload facts: per metric, positive `value_range`
81+
/// and `cardinality` per label set, including all labels (the series count).
8282
#[arg(
8383
long = "workload-facts",
8484
required_if_eq("milp", "true"),

‎asap-planner-rs/src/optimizer/milp.rs‎

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rqe_optimizer::candidates::build_all_candidates;
66
use rqe_optimizer::enumerate::unservable;
77
use rqe_optimizer::milp::{minimize, MilpSolution, Objective};
88
use rqe_optimizer::{
9-
validate_facts, AccuracyDirection, AtomicCostEntry, Capability, LabelSet, Raqe, WorkloadFacts,
9+
table_accuracy, validate_facts, AtomicCostEntry, Capability, LabelSet, Raqe, WorkloadFacts,
1010
};
1111
use thiserror::Error;
1212

@@ -15,11 +15,6 @@ use crate::config::input::ControllerConfig;
1515
use super::pipeline::{extract_hinted_items, OptimizerPipelineError};
1616
use super::solution::OptimizerItem;
1717

18-
/// `query_accuracy` keys in sketch-bench's cost export. Each is the worst case
19-
/// its comparator reports.
20-
const RELATIVE_ERROR: &str = "relative_error";
21-
const MAX_RANK_ERROR: &str = "max_rank_err";
22-
const PRECISION_AT_K: &str = "precision_at_k";
2318
/// Slack on accuracy tolerances so `1 - sla` rounding (`1 - 0.9 =
2419
/// 0.0999...98`) doesn't reject a row measured exactly at the boundary.
2520
const SLA_EPSILON: f64 = 1e-9;
@@ -55,8 +50,13 @@ pub fn solve_milp(
5550
objective: Objective,
5651
allow_undeployable_families: bool,
5752
) -> Result<MilpSolution, MilpError> {
58-
let deployments =
59-
build_all_candidates(&workload.raqes, costs, facts, allow_undeployable_families);
53+
let deployments = build_all_candidates(
54+
&workload.raqes,
55+
costs,
56+
facts,
57+
allow_undeployable_families,
58+
&table_accuracy,
59+
);
6060
tracing::debug!(
6161
candidates = deployments.len(),
6262
cost_rows = costs.len(),
@@ -71,12 +71,18 @@ pub fn solve_milp(
7171
.filter(|&(i, _)| i == 0 || workload.raqe_items[i] != workload.raqe_items[i - 1])
7272
.map(|(_, raqe)| raqe.clone())
7373
.collect();
74-
let missing = unservable(&one_per_item, &deployments, facts);
74+
let missing = unservable(&one_per_item, &deployments, facts, &table_accuracy);
7575
if !missing.is_empty() {
7676
return Err(MilpError::Unservable(missing));
7777
}
78-
let solution = minimize(&workload.raqes, &deployments, facts, objective)
79-
.map_err(|e| MilpError::Solver(e.to_string()))?;
78+
let solution = minimize(
79+
&workload.raqes,
80+
&deployments,
81+
facts,
82+
objective,
83+
&table_accuracy,
84+
)
85+
.map_err(|e| MilpError::Solver(e.to_string()))?;
8086
tracing::debug!(
8187
objective = objective.value(&solution.plan_cost),
8288
cpu_secs_per_sec = solution.plan_cost.cpu_secs_per_sec(),
@@ -129,9 +135,7 @@ pub fn build_milp_workload(
129135
grouping_labels = ?raqe.grouping_labels,
130136
lookback_ms = raqe.lookback_ms,
131137
interval_ms = raqe.interval_ms,
132-
accuracy_metric = %raqe.accuracy_metric,
133138
accuracy_sla = raqe.accuracy_sla,
134-
accuracy_direction = ?raqe.accuracy_direction,
135139
latency_sla_ms = ?raqe.latency_sla_ms,
136140
"milp inputs: item -> raqe"
137141
);
@@ -182,8 +186,7 @@ fn item_to_raqe(item: &OptimizerItem) -> Result<Raqe, MilpError> {
182186
accuracy_sla: item.accuracy_sla,
183187
});
184188
}
185-
let (accuracy_metric, accuracy_sla, accuracy_direction) =
186-
accuracy_target(capability, item.accuracy_sla);
189+
let accuracy_sla = accuracy_target(capability, item.accuracy_sla);
187190

188191
// TopK keeps one heap per `topk by` bucket; its `grouping_labels` is the
189192
// output label set (every label), which would cost one heap per series.
@@ -204,9 +207,7 @@ fn item_to_raqe(item: &OptimizerItem) -> Result<Raqe, MilpError> {
204207
metric: req.metric.clone(),
205208
spatial_filter: req.spatial_filter_normalized.clone(),
206209
grouping_labels,
207-
accuracy_metric: accuracy_metric.to_string(),
208210
accuracy_sla,
209-
accuracy_direction,
210211
latency_sla_ms: item.latency_sla_ms,
211212
})
212213
}
@@ -232,24 +233,17 @@ fn capability(statistic: Statistic, topk_count_events: Option<bool>) -> Option<C
232233

233234
/// `accuracy_sla` is required accuracy: error metrics must stay within
234235
/// `1 - accuracy_sla`, and top-k precision must reach `accuracy_sla`.
235-
fn accuracy_target(
236-
capability: Capability,
237-
accuracy_sla: f64,
238-
) -> (&'static str, f64, AccuracyDirection) {
236+
fn accuracy_target(capability: Capability, accuracy_sla: f64) -> f64 {
239237
let max_error = 1.0 - accuracy_sla + SLA_EPSILON;
240238
match capability {
241239
Capability::Sum
242240
| Capability::Count
243241
| Capability::Min
244242
| Capability::Max
245243
| Capability::RateOrIncrease
246-
| Capability::Cardinality => (RELATIVE_ERROR, max_error, AccuracyDirection::LowerIsBetter),
247-
Capability::Quantile => (MAX_RANK_ERROR, max_error, AccuracyDirection::LowerIsBetter),
248-
Capability::TopKByValue | Capability::TopKByCount => (
249-
PRECISION_AT_K,
250-
accuracy_sla - SLA_EPSILON,
251-
AccuracyDirection::HigherIsBetter,
252-
),
244+
| Capability::Cardinality
245+
| Capability::Quantile => max_error,
246+
Capability::TopKByValue | Capability::TopKByCount => accuracy_sla - SLA_EPSILON,
253247
}
254248
}
255249

@@ -265,6 +259,7 @@ pub(super) mod tests {
265259
const FACTS: &str = r#"
266260
metrics:
267261
- metric: http_requests_total
262+
value_range: [1.0, 1000.0]
268263
groups:
269264
- labels: [instance, job]
270265
cardinality: 100
@@ -316,11 +311,11 @@ metrics:
316311

317312
pub(crate) fn costs() -> Vec<AtomicCostEntry> {
318313
vec![
319-
cost("exact-sum", &[(RELATIVE_ERROR, 0.0)]),
320-
cost("kll-percall", &[(MAX_RANK_ERROR, 0.005)]),
314+
cost("exact-sum", &[("relative_error", 0.0)]),
315+
cost("kll-percall", &[("mean_rank_err", 0.005)]),
321316
cost(
322317
"cms-heap-topk-fastpath-vector2d",
323-
&[(PRECISION_AT_K, 0.995)],
318+
&[("precision_at_k", 0.995)],
324319
),
325320
]
326321
}
@@ -341,11 +336,13 @@ metrics:
341336
"quantile_over_time(0.99, http_requests_total[5m])",
342337
0.9,
343338
));
344-
assert!(w.raqes[0].accuracy_ok(0.1));
345-
assert!(!w.raqes[0].accuracy_ok(0.1001));
339+
assert!(w.raqes[0].meets_sla("kll-percall", Some(0.1)));
340+
assert!(w.raqes[0].meets_sla("dd", Some(0.1)));
341+
assert!(!w.raqes[0].meets_sla("kll-percall", Some(0.1001)));
342+
assert!(!w.raqes[0].meets_sla("dd", Some(0.1001)));
346343
let w = workload(&group("topk(5, http_requests_total)", 0.9));
347-
assert!(w.raqes[0].accuracy_ok(0.9));
348-
assert!(!w.raqes[0].accuracy_ok(0.8999));
344+
assert!(w.raqes[0].meets_sla("cms-heap-topk-fastpath-vector2d", Some(0.9)));
345+
assert!(!w.raqes[0].meets_sla("cms-heap-topk-fastpath-vector2d", Some(0.8999)));
349346
}
350347

351348
#[test]
@@ -374,8 +371,6 @@ metrics:
374371
let r = &w.raqes[0];
375372
assert_eq!(r.capability, Capability::Sum);
376373
assert_eq!(r.grouping_labels, labels(&["job"]));
377-
assert_eq!(r.accuracy_metric, RELATIVE_ERROR);
378-
assert_eq!(r.accuracy_direction, AccuracyDirection::LowerIsBetter);
379374
assert!((r.accuracy_sla - 0.01).abs() < 1e-6);
380375
assert_eq!(r.interval_ms, 60_000);
381376
assert_eq!(r.latency_sla_ms, None);
@@ -410,14 +405,14 @@ metrics:
410405
}
411406

412407
#[test]
413-
fn quantile_uses_max_rank_error() {
408+
fn quantile_uses_the_error_ceiling() {
414409
let w = workload(&group(
415410
"quantile_over_time(0.99, http_requests_total[5m])",
416411
0.99,
417412
));
418413
let r = &w.raqes[0];
419414
assert_eq!(r.capability, Capability::Quantile);
420-
assert_eq!(r.accuracy_metric, MAX_RANK_ERROR);
415+
assert!((r.accuracy_sla - 0.01).abs() < 1e-6);
421416
assert_eq!(r.lookback_ms, 300_000);
422417
}
423418

@@ -426,8 +421,6 @@ metrics:
426421
let w = workload(&group("topk by (job) (5, http_requests_total)", 0.9));
427422
let r = &w.raqes[0];
428423
assert_eq!(r.capability, Capability::TopKByValue);
429-
assert_eq!(r.accuracy_metric, PRECISION_AT_K);
430-
assert_eq!(r.accuracy_direction, AccuracyDirection::HigherIsBetter);
431424
assert!((r.accuracy_sla - 0.9).abs() < 1e-6);
432425
// Not the all-labels output set, which would cost one heap per series.
433426
assert_eq!(r.grouping_labels, labels(&["job"]));

0 commit comments

Comments
 (0)