mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 20:50:20 +08:00
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:
@@ -21,7 +21,9 @@ use super::{
|
||||
VideoTaskModelCount, VideoTaskQueryFilter, VideoTaskStatusCount, WalletLookupKey,
|
||||
WalletMutationOutcome,
|
||||
};
|
||||
use aether_data_contracts::repository::usage::UsageAuditListQuery;
|
||||
use aether_data_contracts::repository::usage::{
|
||||
StoredUsageDailySummary, UsageAuditListQuery, UsageDailyHeatmapQuery,
|
||||
};
|
||||
use aether_video_tasks_core::read_data_backed_video_task_response;
|
||||
|
||||
impl GatewayDataState {
|
||||
@@ -664,6 +666,16 @@ impl GatewayDataState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
|
||||
match &self.usage_reader {
|
||||
Some(repository) => repository.summarize_usage_daily_heatmap(query).await,
|
||||
None => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn list_recent_usage_audits(
|
||||
&self,
|
||||
user_id: Option<&str>,
|
||||
|
||||
+63
-11
@@ -1,15 +1,17 @@
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
use crate::GatewayError;
|
||||
use aether_admin::observability::stats::round_to;
|
||||
use aether_admin::observability::usage::{
|
||||
admin_usage_data_unavailable_response, admin_usage_heatmap_json,
|
||||
ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
|
||||
admin_usage_data_unavailable_response, ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
|
||||
};
|
||||
use aether_data_contracts::repository::usage::UsageAuditListQuery;
|
||||
use aether_data_contracts::repository::usage::UsageDailyHeatmapQuery;
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(super) async fn build_admin_usage_heatmap_response(
|
||||
state: &AdminAppState<'_>,
|
||||
@@ -19,14 +21,64 @@ pub(super) async fn build_admin_usage_heatmap_response(
|
||||
ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
|
||||
));
|
||||
}
|
||||
let now_unix_secs = u64::try_from(chrono::Utc::now().timestamp()).unwrap_or_default();
|
||||
let created_from_unix_secs = now_unix_secs.saturating_sub(365 * 24 * 3600);
|
||||
let mut usage = state
|
||||
.list_usage_audits(&UsageAuditListQuery {
|
||||
created_from_unix_secs: Some(created_from_unix_secs),
|
||||
..Default::default()
|
||||
let today = chrono::Utc::now().date_naive();
|
||||
let start_date = today
|
||||
.checked_sub_signed(chrono::Duration::days(364))
|
||||
.unwrap_or(today);
|
||||
let created_from_unix_secs = u64::try_from(
|
||||
chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
|
||||
start_date.and_hms_opt(0, 0, 0).unwrap_or_default(),
|
||||
chrono::Utc,
|
||||
)
|
||||
.timestamp(),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let summaries = state
|
||||
.summarize_usage_daily_heatmap(&UsageDailyHeatmapQuery {
|
||||
created_from_unix_secs,
|
||||
user_id: None,
|
||||
admin_mode: true,
|
||||
})
|
||||
.await?;
|
||||
usage.retain(|item| item.status != "pending" && item.status != "streaming");
|
||||
Ok(Json(admin_usage_heatmap_json(&usage)).into_response())
|
||||
|
||||
let grouped: BTreeMap<String, _> = summaries.into_iter().map(|s| (s.date.clone(), s)).collect();
|
||||
|
||||
let mut max_requests = 0_u64;
|
||||
let mut cursor = start_date;
|
||||
let mut days = Vec::new();
|
||||
while cursor <= today {
|
||||
let date_str = cursor.to_string();
|
||||
let (requests, total_tokens, total_cost, actual_total_cost) =
|
||||
if let Some(s) = grouped.get(&date_str) {
|
||||
(
|
||||
s.requests,
|
||||
s.total_tokens,
|
||||
s.total_cost_usd,
|
||||
s.actual_total_cost_usd,
|
||||
)
|
||||
} else {
|
||||
(0, 0, 0.0, 0.0)
|
||||
};
|
||||
max_requests = max_requests.max(requests);
|
||||
days.push(json!({
|
||||
"date": date_str,
|
||||
"requests": requests,
|
||||
"total_tokens": total_tokens,
|
||||
"total_cost": round_to(total_cost, 6),
|
||||
"actual_total_cost": round_to(actual_total_cost, 6),
|
||||
}));
|
||||
cursor = cursor
|
||||
.checked_add_signed(chrono::Duration::days(1))
|
||||
.unwrap_or(today + chrono::Duration::days(1));
|
||||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
"start_date": start_date.to_string(),
|
||||
"end_date": today.to_string(),
|
||||
"total_days": days.len(),
|
||||
"max_requests": max_requests,
|
||||
"days": days,
|
||||
}))
|
||||
.into_response())
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ impl<'a> AdminAppState<'a> {
|
||||
self.app.list_usage_audits(query).await
|
||||
}
|
||||
|
||||
pub(crate) async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &aether_data_contracts::repository::usage::UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<aether_data_contracts::repository::usage::StoredUsageDailySummary>, GatewayError>
|
||||
{
|
||||
self.app.summarize_usage_daily_heatmap(query).await
|
||||
}
|
||||
|
||||
pub(crate) async fn find_request_usage_by_id(
|
||||
&self,
|
||||
usage_id: &str,
|
||||
|
||||
@@ -1026,14 +1026,14 @@ pub(super) async fn handle_users_me_usage_heatmap_get(
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let items = match state
|
||||
.list_usage_audits(&UsageAuditListQuery {
|
||||
created_from_unix_secs: Some(created_from_unix_secs),
|
||||
created_until_unix_secs: None,
|
||||
user_id: Some(auth.user.id.clone()),
|
||||
provider_name: None,
|
||||
model: None,
|
||||
})
|
||||
let summaries = match state
|
||||
.summarize_usage_daily_heatmap(
|
||||
&aether_data_contracts::repository::usage::UsageDailyHeatmapQuery {
|
||||
created_from_unix_secs,
|
||||
user_id: Some(auth.user.id.clone()),
|
||||
admin_mode: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(value) => value,
|
||||
@@ -1047,40 +1047,28 @@ pub(super) async fn handle_users_me_usage_heatmap_get(
|
||||
};
|
||||
|
||||
let include_actual_cost = auth.user.role.eq_ignore_ascii_case("admin");
|
||||
let mut daily = BTreeMap::<chrono::NaiveDate, (u64, u64, f64, f64)>::new();
|
||||
for item in items {
|
||||
if item.billing_status != "settled" || item.total_cost_usd <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
let effective = users_me_usage_effective_unix_secs(&item);
|
||||
let Some(timestamp) = chrono::DateTime::<chrono::Utc>::from_timestamp(
|
||||
i64::try_from(effective).unwrap_or_default(),
|
||||
0,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let entry = daily
|
||||
.entry(timestamp.date_naive())
|
||||
.or_insert((0, 0, 0.0, 0.0));
|
||||
entry.0 = entry.0.saturating_add(1);
|
||||
entry.1 = entry
|
||||
.1
|
||||
.saturating_add(item.total_tokens)
|
||||
.saturating_add(item.cache_creation_input_tokens)
|
||||
.saturating_add(item.cache_read_input_tokens);
|
||||
entry.2 += item.total_cost_usd;
|
||||
entry.3 += item.actual_total_cost_usd;
|
||||
}
|
||||
let grouped: std::collections::HashMap<String, _> =
|
||||
summaries.into_iter().map(|s| (s.date.clone(), s)).collect();
|
||||
|
||||
let mut max_requests = 0_u64;
|
||||
let mut cursor = start_date;
|
||||
let mut days = Vec::new();
|
||||
while cursor <= today {
|
||||
let date_str = cursor.to_string();
|
||||
let (requests, total_tokens, total_cost, actual_total_cost) =
|
||||
daily.get(&cursor).copied().unwrap_or((0, 0, 0.0, 0.0));
|
||||
if let Some(s) = grouped.get(&date_str) {
|
||||
(
|
||||
s.requests,
|
||||
s.total_tokens,
|
||||
s.total_cost_usd,
|
||||
s.actual_total_cost_usd,
|
||||
)
|
||||
} else {
|
||||
(0, 0, 0.0, 0.0)
|
||||
};
|
||||
max_requests = max_requests.max(requests);
|
||||
let mut day = json!({
|
||||
"date": cursor.to_string(),
|
||||
"date": date_str,
|
||||
"requests": requests,
|
||||
"total_tokens": total_tokens,
|
||||
"total_cost": round_to(total_cost, 6),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data_contracts::repository::{candidates, usage};
|
||||
use usage::{StoredUsageDailySummary, UsageDailyHeatmapQuery};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_request_candidates_by_request_id(
|
||||
@@ -44,6 +45,16 @@ impl AppState {
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<StoredUsageDailySummary>, GatewayError> {
|
||||
self.data
|
||||
.summarize_usage_daily_heatmap(query)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_recent_usage_audits(
|
||||
&self,
|
||||
user_id: Option<&str>,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -10,8 +10,8 @@ use serde_json::Value;
|
||||
use super::{
|
||||
strip_deprecated_usage_display_fields, usage_can_recover_terminal_failure,
|
||||
StoredProviderApiKeyUsageSummary, StoredProviderUsageSummary, StoredProviderUsageWindow,
|
||||
StoredRequestUsageAudit, UpsertUsageRecord, UsageAuditListQuery, UsageReadRepository,
|
||||
UsageWriteRepository,
|
||||
StoredRequestUsageAudit, StoredUsageDailySummary, UpsertUsageRecord, UsageAuditListQuery,
|
||||
UsageDailyHeatmapQuery, UsageReadRepository, UsageWriteRepository,
|
||||
};
|
||||
use crate::DataLayerError;
|
||||
|
||||
@@ -335,6 +335,70 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
|
||||
let items = self.by_request_id.read().expect("usage repository lock");
|
||||
let mut daily = BTreeMap::<String, (u64, u64, f64, f64)>::new();
|
||||
for item in items.values() {
|
||||
if item.created_at_unix_ms < query.created_from_unix_secs {
|
||||
continue;
|
||||
}
|
||||
if let Some(user_id) = &query.user_id {
|
||||
if item.user_id.as_deref() != Some(user_id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if query.admin_mode {
|
||||
if item.status == "pending" || item.status == "streaming" {
|
||||
continue;
|
||||
}
|
||||
} else if item.billing_status != "settled" || item.total_cost_usd <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
let ts = i64::try_from(item.created_at_unix_ms).unwrap_or_default();
|
||||
let Some(dt) = chrono::DateTime::<chrono::Utc>::from_timestamp(ts, 0) else {
|
||||
continue;
|
||||
};
|
||||
let date_key = dt.date_naive().to_string();
|
||||
let entry = daily.entry(date_key).or_insert((0, 0, 0.0, 0.0));
|
||||
entry.0 += 1;
|
||||
let cache_creation = if item.cache_creation_input_tokens == 0
|
||||
&& (item.cache_creation_ephemeral_5m_input_tokens
|
||||
+ item.cache_creation_ephemeral_1h_input_tokens)
|
||||
> 0
|
||||
{
|
||||
item.cache_creation_ephemeral_5m_input_tokens
|
||||
+ item.cache_creation_ephemeral_1h_input_tokens
|
||||
} else {
|
||||
item.cache_creation_input_tokens
|
||||
};
|
||||
entry.1 += item.input_tokens
|
||||
+ item.output_tokens
|
||||
+ cache_creation
|
||||
+ item.cache_read_input_tokens;
|
||||
entry.2 += item.total_cost_usd;
|
||||
entry.3 += item.actual_total_cost_usd;
|
||||
}
|
||||
let mut result: Vec<_> = daily
|
||||
.into_iter()
|
||||
.map(
|
||||
|(date, (requests, total_tokens, total_cost_usd, actual_total_cost_usd))| {
|
||||
StoredUsageDailySummary {
|
||||
date,
|
||||
requests,
|
||||
total_tokens,
|
||||
total_cost_usd,
|
||||
actual_total_cost_usd,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
result.sort_by(|a, b| a.date.cmp(&b.date));
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn detach_usage_body(
|
||||
|
||||
@@ -4,8 +4,8 @@ mod sql;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use aether_data_contracts::repository::usage::{
|
||||
StoredProviderApiKeyUsageSummary, StoredProviderUsageSummary, StoredProviderUsageWindow,
|
||||
StoredRequestUsageAudit, UpsertUsageRecord, UsageAuditListQuery, UsageReadRepository,
|
||||
UsageRepository, UsageWriteRepository,
|
||||
StoredRequestUsageAudit, StoredUsageDailySummary, UpsertUsageRecord, UsageAuditListQuery,
|
||||
UsageDailyHeatmapQuery, UsageReadRepository, UsageRepository, UsageWriteRepository,
|
||||
};
|
||||
pub use memory::InMemoryUsageReadRepository;
|
||||
pub use sql::SqlxUsageReadRepository;
|
||||
|
||||
@@ -14,7 +14,8 @@ use uuid::Uuid;
|
||||
use super::{
|
||||
incoming_usage_can_recover_terminal_failure, strip_deprecated_usage_display_fields,
|
||||
StoredProviderApiKeyUsageSummary, StoredProviderUsageSummary, StoredRequestUsageAudit,
|
||||
UpsertUsageRecord, UsageAuditListQuery, UsageReadRepository, UsageWriteRepository,
|
||||
StoredUsageDailySummary, UpsertUsageRecord, UsageAuditListQuery, UsageDailyHeatmapQuery,
|
||||
UsageReadRepository, UsageWriteRepository,
|
||||
};
|
||||
use crate::postgres::PostgresTransactionRunner;
|
||||
use crate::{error::SqlxResultExt, DataLayerError};
|
||||
@@ -1209,6 +1210,67 @@ impl SqlxUsageReadRepository {
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
|
||||
let mut sql = String::from(
|
||||
r#"SELECT
|
||||
DATE("usage".created_at) AS day,
|
||||
COUNT(*)::BIGINT AS requests,
|
||||
COALESCE(SUM("usage".input_tokens + "usage".output_tokens
|
||||
+ CASE
|
||||
WHEN COALESCE("usage".cache_creation_input_tokens, 0) = 0
|
||||
AND (COALESCE("usage".cache_creation_input_tokens_5m, 0) + COALESCE("usage".cache_creation_input_tokens_1h, 0)) > 0
|
||||
THEN COALESCE("usage".cache_creation_input_tokens_5m, 0) + COALESCE("usage".cache_creation_input_tokens_1h, 0)
|
||||
ELSE COALESCE("usage".cache_creation_input_tokens, 0)
|
||||
END
|
||||
+ COALESCE("usage".cache_read_input_tokens, 0)), 0)::BIGINT AS total_tokens,
|
||||
COALESCE(SUM(CAST("usage".total_cost_usd AS DOUBLE PRECISION)), 0) AS total_cost_usd,
|
||||
COALESCE(SUM(CAST("usage".actual_total_cost_usd AS DOUBLE PRECISION)), 0) AS actual_total_cost_usd
|
||||
FROM "usage"
|
||||
WHERE "usage".created_at >= TO_TIMESTAMP($1::double precision)"#,
|
||||
);
|
||||
if query.admin_mode {
|
||||
sql.push_str(" AND \"usage\".status NOT IN ('pending', 'streaming')");
|
||||
} else {
|
||||
sql.push_str(
|
||||
" AND \"usage\".billing_status = 'settled' AND CAST(\"usage\".total_cost_usd AS DOUBLE PRECISION) > 0",
|
||||
);
|
||||
}
|
||||
let mut bind_index = 2;
|
||||
if query.user_id.is_some() {
|
||||
sql.push_str(&format!(" AND \"usage\".user_id = ${bind_index}"));
|
||||
bind_index += 1;
|
||||
}
|
||||
let _ = bind_index;
|
||||
sql.push_str(" GROUP BY day ORDER BY day ASC");
|
||||
|
||||
let mut q = sqlx::query(&sql).bind(query.created_from_unix_secs as f64);
|
||||
if let Some(user_id) = &query.user_id {
|
||||
q = q.bind(user_id.clone());
|
||||
}
|
||||
|
||||
let mut rows = q.fetch(&self.pool);
|
||||
let mut items = Vec::new();
|
||||
while let Some(row) = rows.try_next().await.map_postgres_err()? {
|
||||
let day: chrono::NaiveDate = row.try_get("day").map_postgres_err()?;
|
||||
let requests: i64 = row.try_get("requests").map_postgres_err()?;
|
||||
let total_tokens: i64 = row.try_get("total_tokens").map_postgres_err()?;
|
||||
let total_cost_usd: f64 = row.try_get("total_cost_usd").map_postgres_err()?;
|
||||
let actual_total_cost_usd: f64 =
|
||||
row.try_get("actual_total_cost_usd").map_postgres_err()?;
|
||||
items.push(StoredUsageDailySummary {
|
||||
date: day.to_string(),
|
||||
requests: requests as u64,
|
||||
total_tokens: total_tokens as u64,
|
||||
total_cost_usd,
|
||||
actual_total_cost_usd,
|
||||
});
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub async fn list_recent_usage_audits(
|
||||
&self,
|
||||
user_id: Option<&str>,
|
||||
@@ -1703,6 +1765,13 @@ impl UsageReadRepository for SqlxUsageReadRepository {
|
||||
) -> Result<StoredProviderUsageSummary, DataLayerError> {
|
||||
Self::summarize_provider_usage_since(self, provider_id, since_unix_secs).await
|
||||
}
|
||||
|
||||
async fn summarize_usage_daily_heatmap(
|
||||
&self,
|
||||
query: &UsageDailyHeatmapQuery,
|
||||
) -> Result<Vec<StoredUsageDailySummary>, DataLayerError> {
|
||||
Self::summarize_usage_daily_heatmap(self, query).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
# Aether 部署配置 - 使用预构建镜像
|
||||
# Aether 部署配置 - 使用预构建镜像
|
||||
# 使用方法: docker compose up -d
|
||||
|
||||
services:
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
command: postgres -c idle_in_transaction_session_timeout=30000 -c tcp_keepalives_idle=30 -c tcp_keepalives_interval=10
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||
interval: 5s
|
||||
@@ -57,6 +58,12 @@ services:
|
||||
- "${APP_PORT:-8084}:${APP_PORT:-8084}"
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
healthcheck:
|
||||
test: ["CMD", "aether-gateway", "health", "--url", "http://127.0.0.1:${APP_PORT:-8084}/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
|
||||
Reference in New Issue
Block a user