fix: keep conversion priority below load balance

This commit is contained in:
yao177
2026-05-16 04:33:43 +00:00
parent dc1009798d
commit 232976c14a
2 changed files with 155 additions and 12 deletions

View File

@@ -439,6 +439,127 @@ mod tests {
); );
} }
#[test]
fn load_balance_distribution_can_precede_conversion_priority() {
let mut same_format_low_priority = candidate("same", 10, 0, Some(10));
same_format_low_priority.format_preference = (0, 0);
let mut cross_format_high_priority = candidate("cross", 0, 0, Some(0));
cross_format_high_priority.format_preference = (1, 1);
let probe_same = candidate("same", 0, 0, Some(0));
let mut probe_cross = candidate("cross", 0, 0, Some(0));
probe_cross.format_preference = (0, 0);
let seed = (0..512)
.find(|seed| {
ranked_ids(
&[probe_same.clone(), probe_cross.clone()],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: *seed,
},
)
.first()
.is_some_and(|provider| provider == "provider-same")
})
.expect("test seed should put same-format provider first by load balance");
assert_eq!(
ranked_ids(
&[same_format_low_priority, cross_format_high_priority],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: seed,
},
),
vec!["provider-same", "provider-cross"]
);
}
#[test]
fn load_balance_conversion_priority_applies_within_provider_distribution() {
let mut same_format_low_priority = candidate("same", 10, 0, Some(10));
same_format_low_priority.provider_id = "provider-shared".to_string();
same_format_low_priority.format_preference = (0, 0);
let mut cross_format_high_priority = candidate("cross", 0, 0, Some(0));
cross_format_high_priority.provider_id = "provider-shared".to_string();
cross_format_high_priority.format_preference = (1, 1);
let mut probe_same = same_format_low_priority.clone();
probe_same.provider_priority = 0;
let mut probe_cross = cross_format_high_priority.clone();
probe_cross.format_preference = (0, 0);
let seed = (0..512)
.find(|seed| {
ranked_keys(
&[probe_same.clone(), probe_cross.clone()],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: *seed,
},
)
.first()
.is_some_and(|key| key == "key-same")
})
.expect("test seed should put same-format key first by load balance tiebreaker");
assert_eq!(
ranked_keys(
&[same_format_low_priority, cross_format_high_priority],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: seed,
},
),
vec!["key-cross", "key-same"]
);
}
#[test]
fn load_balance_distribution_can_precede_format_preference() {
let same_format = candidate("same", 0, 0, Some(0));
let mut cross_format = candidate("cross", 0, 0, Some(0));
cross_format.format_preference = (1, 1);
let mut probe_cross = cross_format.clone();
probe_cross.format_preference = (0, 0);
let seed = (0..512)
.find(|seed| {
ranked_ids(
&[same_format.clone(), probe_cross.clone()],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: *seed,
},
)
.first()
.is_some_and(|provider| provider == "provider-cross")
})
.expect("test seed should put cross-format provider first by load balance");
assert_eq!(
ranked_ids(
&[same_format, cross_format],
SchedulerRankingContext {
priority_mode: SchedulerPriorityMode::Provider,
ranking_mode: SchedulerRankingMode::LoadBalance,
include_health: false,
load_balance_seed: seed,
},
),
vec!["provider-cross", "provider-same"]
);
}
#[test] #[test]
fn load_balance_provider_mode_randomizes_providers_then_uses_internal_key_priority() { fn load_balance_provider_mode_randomizes_providers_then_uses_internal_key_priority() {
let mut provider_a_primary = candidate("a-primary", 0, 0, Some(0)); let mut provider_a_primary = candidate("a-primary", 0, 0, Some(0));

View File

@@ -65,13 +65,29 @@ fn compare_load_balance_base(
.cmp(&right.capability_priority) .cmp(&right.capability_priority)
.then_with(|| compare_cross_format_demotion(left, right)) .then_with(|| compare_cross_format_demotion(left, right))
.then_with(|| compare_demoted_format_preference(left, right)) .then_with(|| compare_demoted_format_preference(left, right))
.then_with(|| compare_load_balance_distribution_slot(left, right, context))
.then_with(|| compare_conversion_priority_slot(left, right, context.priority_mode))
.then_with(|| compare_load_balance_distribution_tiebreakers(left, right, context))
.then_with(|| compare_format_preference(left, right)) .then_with(|| compare_format_preference(left, right))
.then_with(|| compare_load_balance_distribution(left, right, context))
.then_with(|| compare_candidate_identity_for_ranking(left, right)) .then_with(|| compare_candidate_identity_for_ranking(left, right))
.then(left.original_index.cmp(&right.original_index)) .then(left.original_index.cmp(&right.original_index))
} }
fn compare_load_balance_distribution( fn compare_conversion_priority_slot(
left: &SchedulerRankableCandidate,
right: &SchedulerRankableCandidate,
priority_mode: crate::SchedulerPriorityMode,
) -> Ordering {
if left.demote_cross_format || right.demote_cross_format {
return Ordering::Equal;
}
if left.format_preference.0 == 0 && right.format_preference.0 == 0 {
return Ordering::Equal;
}
compare_candidate_priority_slot(left, right, priority_mode)
}
fn compare_load_balance_distribution_slot(
left: &SchedulerRankableCandidate, left: &SchedulerRankableCandidate,
right: &SchedulerRankableCandidate, right: &SchedulerRankableCandidate,
context: SchedulerRankingContext, context: SchedulerRankingContext,
@@ -79,16 +95,6 @@ fn compare_load_balance_distribution(
match context.priority_mode { match context.priority_mode {
crate::SchedulerPriorityMode::Provider => { crate::SchedulerPriorityMode::Provider => {
compare_seeded_provider_hash(left, right, context.load_balance_seed) compare_seeded_provider_hash(left, right, context.load_balance_seed)
.then_with(|| {
if left.provider_id == right.provider_id {
left.key_internal_priority.cmp(&right.key_internal_priority)
} else {
Ordering::Equal
}
})
.then_with(|| {
compare_seeded_candidate_hash(left, right, context.load_balance_seed, "key")
})
} }
crate::SchedulerPriorityMode::GlobalKey => { crate::SchedulerPriorityMode::GlobalKey => {
compare_seeded_candidate_hash(left, right, context.load_balance_seed, "global-key") compare_seeded_candidate_hash(left, right, context.load_balance_seed, "global-key")
@@ -96,6 +102,22 @@ fn compare_load_balance_distribution(
} }
} }
fn compare_load_balance_distribution_tiebreakers(
left: &SchedulerRankableCandidate,
right: &SchedulerRankableCandidate,
context: SchedulerRankingContext,
) -> Ordering {
match context.priority_mode {
crate::SchedulerPriorityMode::Provider => if left.provider_id == right.provider_id {
left.key_internal_priority.cmp(&right.key_internal_priority)
} else {
Ordering::Equal
}
.then_with(|| compare_seeded_candidate_hash(left, right, context.load_balance_seed, "key")),
crate::SchedulerPriorityMode::GlobalKey => Ordering::Equal,
}
}
fn compare_health( fn compare_health(
left: &SchedulerRankableCandidate, left: &SchedulerRankableCandidate,
right: &SchedulerRankableCandidate, right: &SchedulerRankableCandidate,