fix: harden bucket routing review follow-up
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::cluster::{AdmissionStats, Cluster};
|
||||
use crate::config::{BucketConfig, Config, ModelConfig};
|
||||
use crate::instance::Instance;
|
||||
@@ -46,17 +48,17 @@ impl BucketedService {
|
||||
&self.buckets[bucket_id as usize]
|
||||
}
|
||||
|
||||
pub fn route_and_admit(&mut self, req: &RequestRecord, now: f64) -> AdmissionStats {
|
||||
pub fn route_and_admit(&mut self, req: &RequestRecord, now: f64) -> Result<AdmissionStats> {
|
||||
let bucket_views = self
|
||||
.buckets
|
||||
.iter()
|
||||
.map(|bucket| bucket.cluster.bucket_view(bucket.id, &bucket.cfg))
|
||||
.collect::<Vec<_>>();
|
||||
let global = self.global_router.route(req, &bucket_views, now);
|
||||
let global = self.global_router.route(req, &bucket_views, now)?;
|
||||
let bucket = &mut self.buckets[global.chosen_bucket as usize];
|
||||
bucket
|
||||
Ok(bucket
|
||||
.cluster
|
||||
.route_and_admit_with_global(req, now, &global)
|
||||
.route_and_admit_with_global(req, now, &global))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +169,19 @@ mod tests {
|
||||
fn strict_input_length_routes_into_matching_bucket() {
|
||||
let cfg = test_config();
|
||||
let mut service = BucketedService::new(&cfg, &cfg.model);
|
||||
let stats = service.route_and_admit(&req(1, 24, &[10, 11]), 0.0);
|
||||
let stats = service
|
||||
.route_and_admit(&req(1, 24, &[10, 11]), 0.0)
|
||||
.unwrap();
|
||||
assert_eq!(stats.bucket, 0);
|
||||
assert_eq!(stats.decision.chosen_bucket, 0);
|
||||
assert_eq!(
|
||||
stats.decision.global_reason,
|
||||
"unique bucket range contains input_length"
|
||||
);
|
||||
assert_eq!(
|
||||
stats.decision.local_reason,
|
||||
"argmin(kv_used + alpha * queue_len)"
|
||||
);
|
||||
assert_eq!(service.bucket(0).instances().len(), 2);
|
||||
}
|
||||
|
||||
@@ -177,10 +189,45 @@ mod tests {
|
||||
fn bucket_meta_store_is_isolated() {
|
||||
let cfg = test_config();
|
||||
let mut service = BucketedService::new(&cfg, &cfg.model);
|
||||
let _ = service.route_and_admit(&req(1, 24, &[10, 11]), 0.0);
|
||||
let long_stats = service.route_and_admit(&req(2, 64, &[10, 11, 12, 13]), 1.0);
|
||||
let _ = service
|
||||
.route_and_admit(&req(1, 24, &[10, 11]), 0.0)
|
||||
.unwrap();
|
||||
let long_stats = service
|
||||
.route_and_admit(&req(2, 64, &[10, 11, 12, 13]), 1.0)
|
||||
.unwrap();
|
||||
assert_eq!(long_stats.bucket, 1);
|
||||
assert_eq!(long_stats.remote_hit_blocks, 0);
|
||||
assert_eq!(long_stats.l1_hit_blocks, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmatched_input_length_returns_recoverable_error() {
|
||||
let mut cfg = test_config();
|
||||
cfg.cluster.buckets[1].input_length_min = 40;
|
||||
let mut service = BucketedService::new(&cfg, &cfg.model);
|
||||
|
||||
let err = service
|
||||
.route_and_admit(&req(3, 36, &[20, 21, 22]), 0.0)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("no bucket"));
|
||||
assert!(err.to_string().contains("input_length=36"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bucket_score_placeholder_reports_strict_fallback() {
|
||||
let mut cfg = test_config();
|
||||
cfg.cluster.global_router.mode = GlobalRouterMode::BucketScore;
|
||||
let mut service = BucketedService::new(&cfg, &cfg.model);
|
||||
|
||||
let stats = service
|
||||
.route_and_admit(&req(4, 24, &[30, 31]), 0.0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(stats.decision.global_mode, "strict_input_length");
|
||||
assert!(stats
|
||||
.decision
|
||||
.global_reason
|
||||
.contains("bucket_score is not implemented"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user