mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
perf(gateway): 为 dashboard 接口添加短时响应缓存
新增 DashboardResponseCache,对 stats/daily_stats/provider_status 三个接口按用户维度缓存 15-30 秒,减少重复数据库查询。
This commit is contained in:
2
.github/workflows/build-proxy.yml
vendored
2
.github/workflows/build-proxy.yml
vendored
@@ -129,7 +129,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: aether-rust-pioneer
|
||||||
|
|
||||||
- name: Update README download links
|
- name: Update README download links
|
||||||
env:
|
env:
|
||||||
|
|||||||
28
apps/aether-gateway/src/cache/dashboard_response.rs
vendored
Normal file
28
apps/aether-gateway/src/cache/dashboard_response.rs
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use aether_cache::ExpiringMap;
|
||||||
|
|
||||||
|
const MAX_ENTRIES: usize = 256;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct DashboardResponseCache {
|
||||||
|
entries: ExpiringMap<String, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DashboardResponseCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: ExpiringMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DashboardResponseCache {
|
||||||
|
pub(crate) fn get(&self, key: &str, ttl: Duration) -> Option<Vec<u8>> {
|
||||||
|
self.entries.get_fresh(&key.to_string(), ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert(&self, key: String, value: Vec<u8>, ttl: Duration) {
|
||||||
|
self.entries.insert(key, value, ttl, MAX_ENTRIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
apps/aether-gateway/src/cache/mod.rs
vendored
2
apps/aether-gateway/src/cache/mod.rs
vendored
@@ -1,10 +1,12 @@
|
|||||||
mod auth_api_key_last_used;
|
mod auth_api_key_last_used;
|
||||||
mod auth_context;
|
mod auth_context;
|
||||||
|
mod dashboard_response;
|
||||||
mod direct_plan_bypass;
|
mod direct_plan_bypass;
|
||||||
mod scheduler_affinity;
|
mod scheduler_affinity;
|
||||||
|
|
||||||
pub(crate) use auth_api_key_last_used::AuthApiKeyLastUsedCache;
|
pub(crate) use auth_api_key_last_used::AuthApiKeyLastUsedCache;
|
||||||
pub(crate) use auth_context::AuthContextCache;
|
pub(crate) use auth_context::AuthContextCache;
|
||||||
|
pub(crate) use dashboard_response::DashboardResponseCache;
|
||||||
pub(crate) use direct_plan_bypass::DirectPlanBypassCache;
|
pub(crate) use direct_plan_bypass::DirectPlanBypassCache;
|
||||||
pub(crate) use scheduler_affinity::{
|
pub(crate) use scheduler_affinity::{
|
||||||
SchedulerAffinityCache, SchedulerAffinitySnapshotEntry, SchedulerAffinityTarget,
|
SchedulerAffinityCache, SchedulerAffinitySnapshotEntry, SchedulerAffinityTarget,
|
||||||
|
|||||||
@@ -565,6 +565,27 @@ pub(super) async fn handle_dashboard_stats_get(
|
|||||||
Err(response) => return response,
|
Err(response) => return response,
|
||||||
};
|
};
|
||||||
let is_admin = dashboard_role_is_admin(&auth.user.role);
|
let is_admin = dashboard_role_is_admin(&auth.user.role);
|
||||||
|
|
||||||
|
let cache_identity = if is_admin {
|
||||||
|
"admin"
|
||||||
|
} else {
|
||||||
|
auth.user.id.as_str()
|
||||||
|
};
|
||||||
|
let query_string = request_context
|
||||||
|
.request_query_string
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("");
|
||||||
|
let cache_key = format!("stats:{cache_identity}:{query_string}");
|
||||||
|
let cache_ttl = std::time::Duration::from_secs(15);
|
||||||
|
|
||||||
|
if let Some(cached) = state.dashboard_response_cache.get(&cache_key, cache_ttl) {
|
||||||
|
return Response::builder()
|
||||||
|
.status(http::StatusCode::OK)
|
||||||
|
.header(http::header::CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from(cached))
|
||||||
|
.unwrap_or_else(|_| http::StatusCode::INTERNAL_SERVER_ERROR.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let query = request_context.request_query_string.as_deref();
|
let query = request_context.request_query_string.as_deref();
|
||||||
let summary_range = match dashboard_parse_stats_range(query) {
|
let summary_range = match dashboard_parse_stats_range(query) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -723,7 +744,7 @@ pub(super) async fn handle_dashboard_stats_get(
|
|||||||
},
|
},
|
||||||
"token_breakdown": token_breakdown,
|
"token_breakdown": token_breakdown,
|
||||||
});
|
});
|
||||||
return Json(payload).into_response();
|
return dashboard_cached_json_response(state, cache_key, cache_ttl, &payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
let wallet = if state.has_wallet_data_reader() {
|
let wallet = if state.has_wallet_data_reader() {
|
||||||
@@ -801,7 +822,7 @@ pub(super) async fn handle_dashboard_stats_get(
|
|||||||
"token_breakdown": token_breakdown,
|
"token_breakdown": token_breakdown,
|
||||||
"monthly_cost": dashboard_round_f64(period_totals.total_cost_usd, 4),
|
"monthly_cost": dashboard_round_f64(period_totals.total_cost_usd, 4),
|
||||||
});
|
});
|
||||||
Json(payload).into_response()
|
dashboard_cached_json_response(state, cache_key, cache_ttl, &payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dashboard_daily_aggregate_record(
|
fn dashboard_daily_aggregate_record(
|
||||||
@@ -841,6 +862,27 @@ pub(super) async fn handle_dashboard_daily_stats_get(
|
|||||||
Err(response) => return response,
|
Err(response) => return response,
|
||||||
};
|
};
|
||||||
let is_admin = dashboard_role_is_admin(&auth.user.role);
|
let is_admin = dashboard_role_is_admin(&auth.user.role);
|
||||||
|
|
||||||
|
let cache_identity = if is_admin {
|
||||||
|
"admin"
|
||||||
|
} else {
|
||||||
|
auth.user.id.as_str()
|
||||||
|
};
|
||||||
|
let query_string = request_context
|
||||||
|
.request_query_string
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("");
|
||||||
|
let cache_key = format!("daily:{cache_identity}:{query_string}");
|
||||||
|
let cache_ttl = std::time::Duration::from_secs(30);
|
||||||
|
|
||||||
|
if let Some(cached) = state.dashboard_response_cache.get(&cache_key, cache_ttl) {
|
||||||
|
return Response::builder()
|
||||||
|
.status(http::StatusCode::OK)
|
||||||
|
.header(http::header::CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from(cached))
|
||||||
|
.unwrap_or_else(|_| http::StatusCode::INTERNAL_SERVER_ERROR.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let query = request_context.request_query_string.as_deref();
|
let query = request_context.request_query_string.as_deref();
|
||||||
let range = match dashboard_parse_daily_range(query) {
|
let range = match dashboard_parse_daily_range(query) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -1030,7 +1072,7 @@ pub(super) async fn handle_dashboard_daily_stats_get(
|
|||||||
if let Some(provider_summary_payload) = provider_summary_payload {
|
if let Some(provider_summary_payload) = provider_summary_payload {
|
||||||
payload["provider_summary"] = json!(provider_summary_payload);
|
payload["provider_summary"] = json!(provider_summary_payload);
|
||||||
}
|
}
|
||||||
Json(payload).into_response()
|
dashboard_cached_json_response(state, cache_key, cache_ttl, &payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn handle_dashboard_recent_requests_get(
|
pub(super) async fn handle_dashboard_recent_requests_get(
|
||||||
@@ -1134,6 +1176,23 @@ pub(super) async fn handle_dashboard_provider_status_get(
|
|||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(response) => return response,
|
Err(response) => return response,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cache_identity = if dashboard_role_is_admin(&auth.user.role) {
|
||||||
|
"admin"
|
||||||
|
} else {
|
||||||
|
auth.user.id.as_str()
|
||||||
|
};
|
||||||
|
let cache_key = format!("provider:{cache_identity}");
|
||||||
|
let cache_ttl = std::time::Duration::from_secs(20);
|
||||||
|
|
||||||
|
if let Some(cached) = state.dashboard_response_cache.get(&cache_key, cache_ttl) {
|
||||||
|
return Response::builder()
|
||||||
|
.status(http::StatusCode::OK)
|
||||||
|
.header(http::header::CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from(cached))
|
||||||
|
.unwrap_or_else(|_| http::StatusCode::INTERNAL_SERVER_ERROR.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let providers = match state.list_provider_catalog_providers(true).await {
|
let providers = match state.list_provider_catalog_providers(true).await {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1209,7 +1268,8 @@ pub(super) async fn handle_dashboard_provider_status_get(
|
|||||||
entries.truncate(limit);
|
entries.truncate(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
Json(json!({ "providers": entries })).into_response()
|
let payload = json!({ "providers": entries });
|
||||||
|
dashboard_cached_json_response(state, cache_key, cache_ttl, &payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dashboard_parse_limit(
|
fn dashboard_parse_limit(
|
||||||
@@ -1248,6 +1308,23 @@ fn dashboard_bad_request_response(detail: String) -> Response<Body> {
|
|||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dashboard_cached_json_response(
|
||||||
|
state: &AppState,
|
||||||
|
cache_key: String,
|
||||||
|
cache_ttl: std::time::Duration,
|
||||||
|
payload: &serde_json::Value,
|
||||||
|
) -> Response<Body> {
|
||||||
|
let bytes = serde_json::to_vec(payload).unwrap_or_default();
|
||||||
|
state
|
||||||
|
.dashboard_response_cache
|
||||||
|
.insert(cache_key, bytes.clone(), cache_ttl);
|
||||||
|
Response::builder()
|
||||||
|
.status(http::StatusCode::OK)
|
||||||
|
.header(http::header::CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from(bytes))
|
||||||
|
.unwrap_or_else(|_| http::StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
fn dashboard_backend_unavailable_response(detail: &'static str) -> Response<Body> {
|
fn dashboard_backend_unavailable_response(detail: &'static str) -> Response<Body> {
|
||||||
(
|
(
|
||||||
http::StatusCode::SERVICE_UNAVAILABLE,
|
http::StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use aether_runtime::{ConcurrencyGate, DistributedConcurrencyGate};
|
|||||||
|
|
||||||
use super::super::async_task::{VideoTaskPollerConfig, VideoTaskService};
|
use super::super::async_task::{VideoTaskPollerConfig, VideoTaskService};
|
||||||
use super::super::cache::{
|
use super::super::cache::{
|
||||||
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
|
AuthApiKeyLastUsedCache, AuthContextCache, DashboardResponseCache, DirectPlanBypassCache,
|
||||||
|
SchedulerAffinityCache,
|
||||||
};
|
};
|
||||||
use super::super::data::GatewayDataState;
|
use super::super::data::GatewayDataState;
|
||||||
use super::super::fallback_metrics;
|
use super::super::fallback_metrics;
|
||||||
@@ -35,6 +36,7 @@ pub struct AppState {
|
|||||||
pub(crate) oauth_refresh: Arc<provider_transport::LocalOAuthRefreshCoordinator>,
|
pub(crate) oauth_refresh: Arc<provider_transport::LocalOAuthRefreshCoordinator>,
|
||||||
pub(crate) direct_plan_bypass_cache: Arc<DirectPlanBypassCache>,
|
pub(crate) direct_plan_bypass_cache: Arc<DirectPlanBypassCache>,
|
||||||
pub(crate) scheduler_affinity_cache: Arc<SchedulerAffinityCache>,
|
pub(crate) scheduler_affinity_cache: Arc<SchedulerAffinityCache>,
|
||||||
|
pub(crate) dashboard_response_cache: Arc<DashboardResponseCache>,
|
||||||
pub(crate) fallback_metrics: Arc<fallback_metrics::GatewayFallbackMetrics>,
|
pub(crate) fallback_metrics: Arc<fallback_metrics::GatewayFallbackMetrics>,
|
||||||
pub(crate) frontdoor_cors: Option<Arc<FrontdoorCorsConfig>>,
|
pub(crate) frontdoor_cors: Option<Arc<FrontdoorCorsConfig>>,
|
||||||
pub(crate) frontdoor_user_rpm: Arc<FrontdoorUserRpmLimiter>,
|
pub(crate) frontdoor_user_rpm: Arc<FrontdoorUserRpmLimiter>,
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ use super::super::async_task::{
|
|||||||
spawn_video_task_poller, VideoTaskPollerConfig, VideoTaskService, VideoTaskTruthSourceMode,
|
spawn_video_task_poller, VideoTaskPollerConfig, VideoTaskService, VideoTaskTruthSourceMode,
|
||||||
};
|
};
|
||||||
use super::super::cache::{
|
use super::super::cache::{
|
||||||
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
|
AuthApiKeyLastUsedCache, AuthContextCache, DashboardResponseCache, DirectPlanBypassCache,
|
||||||
SchedulerAffinitySnapshotEntry, SchedulerAffinityTarget,
|
SchedulerAffinityCache, SchedulerAffinitySnapshotEntry, SchedulerAffinityTarget,
|
||||||
};
|
};
|
||||||
use super::super::data::{GatewayDataConfig, GatewayDataState};
|
use super::super::data::{GatewayDataConfig, GatewayDataState};
|
||||||
use super::super::fallback_metrics;
|
use super::super::fallback_metrics;
|
||||||
@@ -144,6 +144,7 @@ impl AppState {
|
|||||||
oauth_refresh: Arc::new(provider_transport::LocalOAuthRefreshCoordinator::new()),
|
oauth_refresh: Arc::new(provider_transport::LocalOAuthRefreshCoordinator::new()),
|
||||||
direct_plan_bypass_cache: Arc::new(DirectPlanBypassCache::default()),
|
direct_plan_bypass_cache: Arc::new(DirectPlanBypassCache::default()),
|
||||||
scheduler_affinity_cache: Arc::new(SchedulerAffinityCache::default()),
|
scheduler_affinity_cache: Arc::new(SchedulerAffinityCache::default()),
|
||||||
|
dashboard_response_cache: Arc::new(DashboardResponseCache::default()),
|
||||||
fallback_metrics: Arc::new(fallback_metrics::GatewayFallbackMetrics::default()),
|
fallback_metrics: Arc::new(fallback_metrics::GatewayFallbackMetrics::default()),
|
||||||
frontdoor_cors: None,
|
frontdoor_cors: None,
|
||||||
frontdoor_user_rpm: Arc::new(FrontdoorUserRpmLimiter::new(
|
frontdoor_user_rpm: Arc::new(FrontdoorUserRpmLimiter::new(
|
||||||
|
|||||||
Reference in New Issue
Block a user