2026-04-27 13:39:50 +08:00
|
|
|
use aether_scheduler_core::{
|
2026-05-05 11:23:54 +08:00
|
|
|
build_scheduler_affinity_cache_key_for_api_key_id_with_client_session, ClientSessionAffinity,
|
|
|
|
|
SchedulerAffinityTarget, SchedulerMinimalCandidateSelectionCandidate,
|
2026-04-27 13:39:50 +08:00
|
|
|
};
|
|
|
|
|
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::{GatewayAuthApiKeySnapshot, PlannerAppState};
|
2026-04-27 13:39:50 +08:00
|
|
|
use crate::scheduler::affinity::SCHEDULER_AFFINITY_TTL;
|
|
|
|
|
|
|
|
|
|
const PLANNER_SCHEDULER_AFFINITY_MAX_ENTRIES: usize = 10_000;
|
|
|
|
|
|
|
|
|
|
pub(crate) fn read_cached_scheduler_affinity_target(
|
|
|
|
|
state: PlannerAppState<'_>,
|
|
|
|
|
auth_snapshot: Option<&GatewayAuthApiKeySnapshot>,
|
2026-05-05 11:23:54 +08:00
|
|
|
client_session_affinity: Option<&ClientSessionAffinity>,
|
2026-04-27 13:39:50 +08:00
|
|
|
client_api_format: &str,
|
|
|
|
|
requested_model: Option<&str>,
|
|
|
|
|
) -> Option<SchedulerAffinityTarget> {
|
|
|
|
|
let requested_model = requested_model
|
|
|
|
|
.map(str::trim)
|
|
|
|
|
.filter(|value| !value.is_empty())?;
|
|
|
|
|
let api_key_id = auth_snapshot
|
|
|
|
|
.map(|snapshot| snapshot.api_key_id.trim())
|
|
|
|
|
.filter(|value| !value.is_empty())?;
|
2026-05-05 11:23:54 +08:00
|
|
|
let cache_key = build_scheduler_affinity_cache_key_for_api_key_id_with_client_session(
|
2026-04-27 13:39:50 +08:00
|
|
|
api_key_id,
|
|
|
|
|
client_api_format,
|
|
|
|
|
requested_model,
|
2026-05-05 11:23:54 +08:00
|
|
|
client_session_affinity,
|
2026-04-27 13:39:50 +08:00
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
state
|
|
|
|
|
.app()
|
|
|
|
|
.read_scheduler_affinity_target(&cache_key, SCHEDULER_AFFINITY_TTL)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn remember_scheduler_affinity_for_candidate(
|
|
|
|
|
state: PlannerAppState<'_>,
|
|
|
|
|
auth_snapshot: Option<&GatewayAuthApiKeySnapshot>,
|
2026-05-05 11:23:54 +08:00
|
|
|
client_session_affinity: Option<&ClientSessionAffinity>,
|
2026-04-27 13:39:50 +08:00
|
|
|
client_api_format: &str,
|
|
|
|
|
requested_model: &str,
|
|
|
|
|
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
2026-05-11 01:45:49 +08:00
|
|
|
) {
|
|
|
|
|
remember_scheduler_affinity_for_candidate_at_epoch(
|
|
|
|
|
state,
|
|
|
|
|
auth_snapshot,
|
|
|
|
|
client_session_affinity,
|
|
|
|
|
client_api_format,
|
|
|
|
|
requested_model,
|
|
|
|
|
candidate,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn remember_scheduler_affinity_for_candidate_at_epoch(
|
|
|
|
|
state: PlannerAppState<'_>,
|
|
|
|
|
auth_snapshot: Option<&GatewayAuthApiKeySnapshot>,
|
|
|
|
|
client_session_affinity: Option<&ClientSessionAffinity>,
|
|
|
|
|
client_api_format: &str,
|
|
|
|
|
requested_model: &str,
|
|
|
|
|
candidate: &SchedulerMinimalCandidateSelectionCandidate,
|
|
|
|
|
expected_epoch: Option<u64>,
|
2026-04-27 13:39:50 +08:00
|
|
|
) {
|
|
|
|
|
let Some(api_key_id) = auth_snapshot
|
|
|
|
|
.map(|snapshot| snapshot.api_key_id.trim())
|
|
|
|
|
.filter(|value| !value.is_empty())
|
|
|
|
|
else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-05-05 11:23:54 +08:00
|
|
|
let Some(cache_key) = build_scheduler_affinity_cache_key_for_api_key_id_with_client_session(
|
2026-04-27 13:39:50 +08:00
|
|
|
api_key_id,
|
|
|
|
|
client_api_format,
|
|
|
|
|
requested_model,
|
2026-05-05 11:23:54 +08:00
|
|
|
client_session_affinity,
|
2026-04-27 13:39:50 +08:00
|
|
|
) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-11 01:45:49 +08:00
|
|
|
let _ = state.app().remember_scheduler_affinity_target_for_epoch(
|
2026-04-27 13:39:50 +08:00
|
|
|
&cache_key,
|
|
|
|
|
SchedulerAffinityTarget {
|
|
|
|
|
provider_id: candidate.provider_id.clone(),
|
|
|
|
|
endpoint_id: candidate.endpoint_id.clone(),
|
|
|
|
|
key_id: candidate.key_id.clone(),
|
|
|
|
|
},
|
|
|
|
|
SCHEDULER_AFFINITY_TTL,
|
|
|
|
|
PLANNER_SCHEDULER_AFFINITY_MAX_ENTRIES,
|
2026-05-11 01:45:49 +08:00
|
|
|
expected_epoch,
|
2026-04-27 13:39:50 +08:00
|
|
|
);
|
|
|
|
|
}
|