perf(usage): heatmap 改为数据库端按天聚合查询

将 admin 和 user heatmap 从逐条加载 usage audit 记录后在应用层聚合,
改为通过 SQL GROUP BY DATE 在数据库端直接按天汇总, 大幅减少数据传输量。

新增 UsageDailyHeatmapQuery / StoredUsageDailySummary 类型,
在 trait、SQL、内存实现中均补齐 summarize_usage_daily_heatmap 方法。

同时优化 docker-compose: postgres 增加空闲事务超时与 keepalive 参数,
gateway 增加健康检查配置。
This commit is contained in:
fawney19
2026-04-15 02:11:32 +08:00
parent 05fbbac493
commit 98ad1172b0
11 changed files with 289 additions and 54 deletions

View File

@@ -3,6 +3,6 @@ mod types;
pub use types::{
parse_usage_body_ref, usage_body_ref, StoredProviderApiKeyUsageSummary,
StoredProviderUsageSummary, StoredProviderUsageWindow, StoredRequestUsageAudit,
UpsertUsageRecord, UsageAuditListQuery, UsageBodyField, UsageReadRepository, UsageRepository,
UsageWriteRepository,
StoredUsageDailySummary, UpsertUsageRecord, UsageAuditListQuery, UsageBodyField,
UsageDailyHeatmapQuery, UsageReadRepository, UsageRepository, UsageWriteRepository,
};

View File

@@ -482,6 +482,25 @@ pub struct UsageAuditListQuery {
pub model: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageDailyHeatmapQuery {
pub created_from_unix_secs: u64,
pub user_id: Option<String>,
/// When true, exclude rows with status in ('pending', 'streaming') (admin heatmap).
/// When false, only include rows with billing_status = 'settled' and total_cost_usd > 0 (user heatmap).
pub admin_mode: bool,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageDailySummary {
/// Date as "YYYY-MM-DD"
pub date: String,
pub requests: u64,
pub total_tokens: u64,
pub total_cost_usd: f64,
pub actual_total_cost_usd: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageBodyField {
@@ -586,6 +605,11 @@ pub trait UsageReadRepository: Send + Sync {
provider_id: &str,
since_unix_secs: u64,
) -> Result<StoredProviderUsageSummary, crate::DataLayerError>;
async fn summarize_usage_daily_heatmap(
&self,
query: &UsageDailyHeatmapQuery,
) -> Result<Vec<StoredUsageDailySummary>, crate::DataLayerError>;
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]