fix: harden bucket routing review follow-up

This commit is contained in:
2026-04-17 15:15:18 +08:00
parent fa381b5db3
commit 3a84c15068
3 changed files with 85 additions and 23 deletions

View File

@@ -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"));
}
}