mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(gateway): 重构 usage 数据层、迁移系统与系统导入
数据库迁移: - 引入 baseline v2 bootstrap,空库首次启动自动初始化 - 服务启动不再自动执行迁移,需显式 `--migrate` 运行 - 新增 pending migration 检测,schema 落后时拒绝启动 Usage 数据层: - usage body 存储外部化为独立 blob 表 - 新增 HTTP audit 表拆分存储请求/响应头与 body ref - 后台清理任务支持 legacy body ref 元数据迁移 - usage runtime 写入迁移到专用 tokio runtime(独立线程池, 8MB 栈) 系统导入/导出: - 支持用户、API Keys、钱包数据的完整导入 - 兼容 legacy 与 v1.3+ 两种导出格式 其他改进: - executor outcome 增加 runtime miss 诊断上下文 - 主 tokio runtime 栈大小调整为 8MB - 前端 provider 管理支持 base URL 配置 - dev.sh 支持 --migrate 参数
This commit is contained in:
@@ -6,6 +6,10 @@ use aether_data_contracts::repository::candidates::RequestCandidateStatus;
|
||||
use aether_scheduler_core::{
|
||||
parse_request_candidate_report_context, SchedulerRequestCandidateStatusUpdate,
|
||||
};
|
||||
use aether_usage_runtime::{
|
||||
build_lifecycle_usage_seed, build_stream_terminal_usage_payload_seed,
|
||||
build_sync_terminal_usage_payload_seed, build_terminal_usage_context_seed,
|
||||
};
|
||||
use async_stream::stream;
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::Response;
|
||||
@@ -62,6 +66,36 @@ use crate::usage::submit_stream_report;
|
||||
use crate::usage::{GatewayStreamReportRequest, GatewaySyncReportRequest};
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
fn record_sync_terminal_usage(
|
||||
state: &AppState,
|
||||
plan: &ExecutionPlan,
|
||||
report_context: Option<&serde_json::Value>,
|
||||
payload: &GatewaySyncReportRequest,
|
||||
) {
|
||||
let context_seed = build_terminal_usage_context_seed(plan, report_context);
|
||||
let payload_seed = build_sync_terminal_usage_payload_seed(payload);
|
||||
state
|
||||
.usage_runtime
|
||||
.record_sync_terminal(state.data.as_ref(), &context_seed, &payload_seed);
|
||||
}
|
||||
|
||||
fn record_stream_terminal_usage(
|
||||
state: &AppState,
|
||||
plan: &ExecutionPlan,
|
||||
report_context: Option<&serde_json::Value>,
|
||||
payload: &GatewayStreamReportRequest,
|
||||
cancelled: bool,
|
||||
) {
|
||||
let context_seed = build_terminal_usage_context_seed(plan, report_context);
|
||||
let payload_seed = build_stream_terminal_usage_payload_seed(payload);
|
||||
state.usage_runtime.record_stream_terminal(
|
||||
state.data.as_ref(),
|
||||
&context_seed,
|
||||
&payload_seed,
|
||||
cancelled,
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)] // internal function, grouping would add unnecessary indirection
|
||||
pub(crate) async fn execute_execution_runtime_stream(
|
||||
state: &AppState,
|
||||
@@ -73,10 +107,10 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
mut report_context: Option<serde_json::Value>,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
ensure_execution_request_candidate_slot(state, &mut plan, &mut report_context).await;
|
||||
let lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
|
||||
state
|
||||
.usage_runtime
|
||||
.record_pending(state.data.as_ref(), &plan, report_context.as_ref())
|
||||
.await;
|
||||
.record_pending(state.data.as_ref(), &lifecycle_seed);
|
||||
let plan_request_id_for_log = short_request_id(plan.request_id.as_str());
|
||||
let provider_name = plan.provider_name.as_deref().unwrap_or("-");
|
||||
let endpoint_id = plan.endpoint_id.as_str();
|
||||
@@ -348,6 +382,7 @@ async fn execute_stream_from_frame_stream(
|
||||
let candidate_id = plan.candidate_id.as_deref();
|
||||
let provider_name = plan.provider_name.as_deref().unwrap_or("-");
|
||||
let model_name = plan.model_name.as_deref().unwrap_or("-");
|
||||
let lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
|
||||
let candidate_index = parse_request_candidate_report_context(report_context.as_ref())
|
||||
.and_then(|context| context.candidate_index)
|
||||
.map(|value| value.to_string())
|
||||
@@ -527,15 +562,7 @@ async fn execute_stream_from_frame_stream(
|
||||
body_base64: body_base64.clone(),
|
||||
telemetry: None,
|
||||
};
|
||||
state
|
||||
.usage_runtime
|
||||
.record_sync_terminal(
|
||||
state.data.as_ref(),
|
||||
&plan,
|
||||
report_context.as_ref(),
|
||||
&usage_payload,
|
||||
)
|
||||
.await;
|
||||
record_sync_terminal_usage(state, &plan, report_context.as_ref(), &usage_payload);
|
||||
let terminal_unix_secs = current_request_candidate_unix_ms();
|
||||
record_local_request_candidate_status(
|
||||
state,
|
||||
@@ -739,15 +766,12 @@ async fn execute_stream_from_frame_stream(
|
||||
body_base64: None,
|
||||
telemetry: prefetched_telemetry.clone(),
|
||||
};
|
||||
state
|
||||
.usage_runtime
|
||||
.record_sync_terminal(
|
||||
state.data.as_ref(),
|
||||
&plan,
|
||||
report_context.as_ref(),
|
||||
&payload,
|
||||
)
|
||||
.await;
|
||||
record_sync_terminal_usage(
|
||||
state,
|
||||
&plan,
|
||||
report_context.as_ref(),
|
||||
&payload,
|
||||
);
|
||||
let response = submit_local_core_error_or_sync_finalize(
|
||||
state, trace_id, decision, payload,
|
||||
)
|
||||
@@ -876,17 +900,12 @@ async fn execute_stream_from_frame_stream(
|
||||
}
|
||||
|
||||
let candidate_started_unix_secs = current_request_candidate_unix_ms();
|
||||
state
|
||||
.usage_runtime
|
||||
.record_stream_started(
|
||||
state.data.as_ref(),
|
||||
&plan,
|
||||
report_context.as_ref(),
|
||||
status_code,
|
||||
&headers,
|
||||
prefetched_telemetry.as_ref(),
|
||||
)
|
||||
.await;
|
||||
state.usage_runtime.record_stream_started(
|
||||
state.data.as_ref(),
|
||||
&lifecycle_seed,
|
||||
status_code,
|
||||
prefetched_telemetry.as_ref(),
|
||||
);
|
||||
record_local_request_candidate_status(
|
||||
state,
|
||||
&plan,
|
||||
@@ -912,6 +931,7 @@ async fn execute_stream_from_frame_stream(
|
||||
let headers_for_report = headers.clone();
|
||||
let report_kind_owned = report_kind.clone();
|
||||
let report_context_owned = report_context.clone();
|
||||
let lifecycle_seed_for_report = lifecycle_seed.clone();
|
||||
let provider_prefetched_body_for_report = provider_prefetched_body.clone();
|
||||
let prefetched_body_for_report = prefetched_body.clone();
|
||||
let prefetched_chunks_for_body = prefetched_chunks.clone();
|
||||
@@ -1066,17 +1086,12 @@ async fn execute_stream_from_frame_stream(
|
||||
);
|
||||
telemetry = Some(frame_telemetry.clone());
|
||||
if should_refresh_stream_usage {
|
||||
state_for_report
|
||||
.usage_runtime
|
||||
.record_stream_started(
|
||||
state_for_report.data.as_ref(),
|
||||
&plan_for_report,
|
||||
report_context_owned.as_ref(),
|
||||
status_code,
|
||||
&headers_for_report,
|
||||
Some(&frame_telemetry),
|
||||
)
|
||||
.await;
|
||||
state_for_report.usage_runtime.record_stream_started(
|
||||
state_for_report.data.as_ref(),
|
||||
&lifecycle_seed_for_report,
|
||||
status_code,
|
||||
Some(&frame_telemetry),
|
||||
);
|
||||
usage_stream_telemetry = Some(frame_telemetry);
|
||||
}
|
||||
}
|
||||
@@ -1230,30 +1245,25 @@ async fn execute_stream_from_frame_stream(
|
||||
trace_id = %trace_id_owned,
|
||||
"gateway skipped stream report because downstream disconnected before completion"
|
||||
);
|
||||
state_for_report
|
||||
.usage_runtime
|
||||
.record_stream_terminal(
|
||||
state_for_report.data.as_ref(),
|
||||
&plan_for_report,
|
||||
report_context_owned.as_ref(),
|
||||
&GatewayStreamReportRequest {
|
||||
trace_id: trace_id_owned.clone(),
|
||||
report_kind: report_kind_owned.clone().unwrap_or_default(),
|
||||
report_context: report_context_owned.clone(),
|
||||
status_code: 499,
|
||||
headers: headers_for_report.clone(),
|
||||
provider_body_base64: (!provider_buffered_body.is_empty()).then(|| {
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.encode(&provider_buffered_body)
|
||||
}),
|
||||
client_body_base64: (!buffered_body.is_empty()).then(|| {
|
||||
base64::engine::general_purpose::STANDARD.encode(&buffered_body)
|
||||
}),
|
||||
telemetry: telemetry.clone(),
|
||||
},
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
record_stream_terminal_usage(
|
||||
&state_for_report,
|
||||
&plan_for_report,
|
||||
report_context_owned.as_ref(),
|
||||
&GatewayStreamReportRequest {
|
||||
trace_id: trace_id_owned.clone(),
|
||||
report_kind: report_kind_owned.clone().unwrap_or_default(),
|
||||
report_context: report_context_owned.clone(),
|
||||
status_code: 499,
|
||||
headers: headers_for_report.clone(),
|
||||
provider_body_base64: (!provider_buffered_body.is_empty()).then(|| {
|
||||
base64::engine::general_purpose::STANDARD.encode(&provider_buffered_body)
|
||||
}),
|
||||
client_body_base64: (!buffered_body.is_empty())
|
||||
.then(|| base64::engine::general_purpose::STANDARD.encode(&buffered_body)),
|
||||
telemetry: telemetry.clone(),
|
||||
},
|
||||
true,
|
||||
);
|
||||
record_local_request_candidate_status(
|
||||
&state_for_report,
|
||||
&plan_for_report,
|
||||
@@ -1301,16 +1311,13 @@ async fn execute_stream_from_frame_stream(
|
||||
.then(|| base64::engine::general_purpose::STANDARD.encode(&buffered_body)),
|
||||
telemetry: telemetry.clone(),
|
||||
};
|
||||
state_for_report
|
||||
.usage_runtime
|
||||
.record_stream_terminal(
|
||||
state_for_report.data.as_ref(),
|
||||
&plan_for_report,
|
||||
report_context_owned.as_ref(),
|
||||
&usage_payload,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
record_stream_terminal_usage(
|
||||
&state_for_report,
|
||||
&plan_for_report,
|
||||
report_context_owned.as_ref(),
|
||||
&usage_payload,
|
||||
false,
|
||||
);
|
||||
record_local_request_candidate_status(
|
||||
&state_for_report,
|
||||
&plan_for_report,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use aether_contracts::{ExecutionError, ExecutionPlan, ExecutionTelemetry};
|
||||
use aether_data_contracts::repository::candidates::RequestCandidateStatus;
|
||||
use aether_scheduler_core::SchedulerRequestCandidateStatusUpdate;
|
||||
use aether_usage_runtime::{
|
||||
build_sync_terminal_usage_payload_seed, build_terminal_usage_context_seed,
|
||||
};
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
use base64::Engine as _;
|
||||
@@ -119,10 +122,11 @@ async fn record_stream_sync_failure(
|
||||
failure: &StreamFailureReport,
|
||||
started_at_unix_ms: Option<u64>,
|
||||
) {
|
||||
let context_seed = build_terminal_usage_context_seed(plan, report_context);
|
||||
let payload_seed = build_sync_terminal_usage_payload_seed(payload);
|
||||
state
|
||||
.usage_runtime
|
||||
.record_sync_terminal(state.data.as_ref(), plan, report_context, payload)
|
||||
.await;
|
||||
.record_sync_terminal(state.data.as_ref(), &context_seed, &payload_seed);
|
||||
let terminal_unix_secs = current_request_candidate_unix_ms();
|
||||
record_report_request_candidate_status(
|
||||
state,
|
||||
|
||||
@@ -6,6 +6,10 @@ use aether_scheduler_core::{
|
||||
execution_error_details, parse_request_candidate_report_context,
|
||||
SchedulerRequestCandidateStatusUpdate,
|
||||
};
|
||||
use aether_usage_runtime::{
|
||||
build_lifecycle_usage_seed, build_sync_terminal_usage_payload_seed,
|
||||
build_terminal_usage_context_seed,
|
||||
};
|
||||
use axum::body::Body;
|
||||
use axum::http::Response;
|
||||
use base64::Engine as _;
|
||||
@@ -55,16 +59,17 @@ struct ImplicitSyncFinalizeOutcome {
|
||||
outcome: LocalCoreSyncFinalizeOutcome,
|
||||
}
|
||||
|
||||
async fn record_sync_terminal_usage(
|
||||
fn record_sync_terminal_usage(
|
||||
state: &AppState,
|
||||
plan: &ExecutionPlan,
|
||||
report_context: Option<&serde_json::Value>,
|
||||
payload: &GatewaySyncReportRequest,
|
||||
) {
|
||||
let context_seed = build_terminal_usage_context_seed(plan, report_context);
|
||||
let payload_seed = build_sync_terminal_usage_payload_seed(payload);
|
||||
state
|
||||
.usage_runtime
|
||||
.record_sync_terminal(state.data.as_ref(), plan, report_context, payload)
|
||||
.await;
|
||||
.record_sync_terminal(state.data.as_ref(), &context_seed, &payload_seed);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -98,10 +103,10 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let candidate_started_unix_secs = current_request_candidate_unix_ms();
|
||||
let lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
|
||||
state
|
||||
.usage_runtime
|
||||
.record_pending(state.data.as_ref(), &plan, report_context.as_ref())
|
||||
.await;
|
||||
.record_pending(state.data.as_ref(), &lifecycle_seed);
|
||||
#[cfg(not(test))]
|
||||
let result = {
|
||||
match DirectSyncExecutionRuntime::new()
|
||||
@@ -349,7 +354,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
.background_report
|
||||
.as_ref()
|
||||
.unwrap_or(&implicit_finalize.payload);
|
||||
record_sync_terminal_usage(state, &plan, report_context.as_ref(), usage_payload).await;
|
||||
record_sync_terminal_usage(state, &plan, report_context.as_ref(), usage_payload);
|
||||
if let Some(report_payload) = implicit_finalize.outcome.background_report {
|
||||
spawn_sync_report(state.clone(), trace_id.to_string(), report_payload);
|
||||
} else {
|
||||
@@ -386,8 +391,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
&plan,
|
||||
payload.report_context.as_ref(),
|
||||
usage_payload,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
if let Some(report_payload) = outcome.background_report {
|
||||
spawn_sync_report(state.clone(), trace_id.to_string(), report_payload);
|
||||
} else {
|
||||
@@ -417,8 +421,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
&plan,
|
||||
payload.report_context.as_ref(),
|
||||
&outcome.report_payload,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
if let Some(snapshot) = outcome.local_task_snapshot.clone() {
|
||||
state.video_tasks.record_snapshot(snapshot.clone());
|
||||
let _ = state.upsert_video_task_snapshot(&snapshot).await?;
|
||||
@@ -454,8 +457,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
&plan,
|
||||
payload.report_context.as_ref(),
|
||||
&usage_payload,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
state
|
||||
.video_tasks
|
||||
.apply_finalize_mutation(request_path, payload.report_kind.as_str());
|
||||
@@ -505,8 +507,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
&plan,
|
||||
payload.report_context.as_ref(),
|
||||
&usage_payload,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
if let Some(error_report_kind) =
|
||||
resolve_local_sync_error_background_report_kind(payload.report_kind.as_str())
|
||||
{
|
||||
@@ -530,7 +531,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
candidate_id,
|
||||
)?));
|
||||
}
|
||||
record_sync_terminal_usage(state, &plan, payload.report_context.as_ref(), &payload).await;
|
||||
record_sync_terminal_usage(state, &plan, payload.report_context.as_ref(), &payload);
|
||||
let response =
|
||||
submit_local_core_error_or_sync_finalize(state, trace_id, decision, payload).await?;
|
||||
return Ok(Some(attach_control_metadata_headers(
|
||||
@@ -540,7 +541,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
)?));
|
||||
}
|
||||
|
||||
record_sync_terminal_usage(state, &plan, report_context.as_ref(), &base_usage_payload).await;
|
||||
record_sync_terminal_usage(state, &plan, report_context.as_ref(), &base_usage_payload);
|
||||
if let Some(report_kind) = report_kind {
|
||||
let report = GatewaySyncReportRequest {
|
||||
trace_id: trace_id.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user