perf(db): add statement_timeout; skip retry on pool Timeout errors
- 连接配置 statement_timeout(默认 30s,STATEMENT_TIMEOUT_SECS 可调), 防慢查询长时间占用连接拖垮池(L6) - get_conn 对 Timeout(池满)错误立即返回不再 sleep 重试,避免雪崩; 仅 Backend/Postgres 错误才退避重试
This commit is contained in:
parent
179897ba6f
commit
1e2e3c9332
@ -42,6 +42,7 @@ RATE_LIMIT_UPLOAD_BURST=15
|
|||||||
RATE_LIMIT_IMAGE_PER_SEC=10
|
RATE_LIMIT_IMAGE_PER_SEC=10
|
||||||
RATE_LIMIT_IMAGE_BURST=50
|
RATE_LIMIT_IMAGE_BURST=50
|
||||||
DB_POOL_SIZE=20 # database connection pool size
|
DB_POOL_SIZE=20 # database connection pool size
|
||||||
|
STATEMENT_TIMEOUT_SECS=30 # per-query timeout; slow queries are canceled to protect the pool
|
||||||
SSR_CACHE_SECS=3600 # incremental SSR cache TTL
|
SSR_CACHE_SECS=3600 # incremental SSR cache TTL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -15,10 +15,19 @@ use tokio_postgres::NoTls;
|
|||||||
/// 最大连接数可通过 `DB_POOL_SIZE` 环境变量调整,默认 20。
|
/// 最大连接数可通过 `DB_POOL_SIZE` 环境变量调整,默认 20。
|
||||||
pub static DB_POOL: LazyLock<Pool> = LazyLock::new(|| {
|
pub static DB_POOL: LazyLock<Pool> = LazyLock::new(|| {
|
||||||
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set");
|
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set");
|
||||||
let pg_cfg = db_url
|
let mut pg_cfg = db_url
|
||||||
.parse::<tokio_postgres::Config>()
|
.parse::<tokio_postgres::Config>()
|
||||||
.expect("Invalid DATABASE_URL format");
|
.expect("Invalid DATABASE_URL format");
|
||||||
|
|
||||||
|
// statement_timeout:防止单条慢查询(如全表扫搜索)长时间占用连接拖垮池。
|
||||||
|
// 默认 30s,可由 STATEMENT_TIMEOUT_SECS 覆盖(L6)。
|
||||||
|
let statement_timeout_secs = std::env::var("STATEMENT_TIMEOUT_SECS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
|
.unwrap_or(30);
|
||||||
|
// 通过 libpq options 传递 GUC;tokio-postgres 在建连时执行。
|
||||||
|
pg_cfg.options(format!("-c statement_timeout={}", statement_timeout_secs * 1000));
|
||||||
|
|
||||||
// 使用 Fast 回收策略:归还连接时不额外发 SELECT 1 验证,直接复用。
|
// 使用 Fast 回收策略:归还连接时不额外发 SELECT 1 验证,直接复用。
|
||||||
// Verified 在高并发下会为每次 get() 增加一次往返;Fast 依赖 tokio-postgres
|
// Verified 在高并发下会为每次 get() 增加一次往返;Fast 依赖 tokio-postgres
|
||||||
// 在使用时自然报错,由 get_conn 的重试层兜底。
|
// 在使用时自然报错,由 get_conn 的重试层兜底。
|
||||||
@ -44,8 +53,9 @@ pub static DB_POOL: LazyLock<Pool> = LazyLock::new(|| {
|
|||||||
|
|
||||||
/// 从全局连接池获取一个数据库连接,失败时按指数退避 + jitter 重试。
|
/// 从全局连接池获取一个数据库连接,失败时按指数退避 + jitter 重试。
|
||||||
///
|
///
|
||||||
/// 退避策略见 `retry::backoff_for`。jitter 使用 `rand` 生成 [0,1) 随机数,
|
/// 退避策略见 `retry::backoff_for`。仅对 Backend/Postgres 错误(DB 不可达)重试;
|
||||||
/// 避免多请求同步重试形成惊群。若所有尝试均失败,返回最后一次的 PoolError。
|
/// Timeout(池满)直接返回,让上层限流兜底,避免雪崩(L6)。
|
||||||
|
/// 若所有重试均失败,返回最后一次的 PoolError。
|
||||||
pub async fn get_conn() -> Result<deadpool_postgres::Object, deadpool_postgres::PoolError> {
|
pub async fn get_conn() -> Result<deadpool_postgres::Object, deadpool_postgres::PoolError> {
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
@ -54,17 +64,23 @@ pub async fn get_conn() -> Result<deadpool_postgres::Object, deadpool_postgres::
|
|||||||
match DB_POOL.get().await {
|
match DB_POOL.get().await {
|
||||||
Ok(conn) => return Ok(conn),
|
Ok(conn) => return Ok(conn),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// Timeout(池满)不重试:快速失败让上层限流兜底,避免雪崩。
|
||||||
|
// Backend/Postgres(DB 不可达)才退避重试。
|
||||||
|
let is_timeout = matches!(e, deadpool_postgres::PoolError::Timeout(_));
|
||||||
last_err = Some(e);
|
last_err = Some(e);
|
||||||
if attempt < crate::db::retry::MAX_RETRIES {
|
if !is_timeout && attempt < crate::db::retry::MAX_RETRIES {
|
||||||
let jitter = rand::thread_rng().gen::<f64>();
|
let jitter = rand::thread_rng().gen::<f64>();
|
||||||
let delay = crate::db::retry::backoff_for(attempt, jitter);
|
let delay = crate::db::retry::backoff_for(attempt, jitter);
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"DB connection attempt {} failed, retrying in {:?}: {:?}",
|
"DB connection attempt {} failed (backend error), retrying in {:?}: {:?}",
|
||||||
attempt + 1,
|
attempt + 1,
|
||||||
delay,
|
delay,
|
||||||
last_err.as_ref().unwrap(),
|
last_err.as_ref().unwrap(),
|
||||||
);
|
);
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
|
} else if is_timeout {
|
||||||
|
// 池满:立即返回,不再 sleep。
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user