perf(db): 干掉列表/计数热路径上的「整表拼单聚合」,并给数据库留出 1 核

生产实测(2026-10-03,order_site):postgres 在 22.6 小时里烧了 12.2 CPU·小时
(均 0.54 核),却因 2 核配额累计被 cgroup 节流 9,337 秒;top 12 条语句占全部
DB CPU 的 69.4%,其中三条最热的就是「工单列表 / 计数 / 大厅候选集」。

一、查询形状(根因,不是缺索引)
接单大厅那条 COUNT 被调 71,539 次、均 103ms,EXPLAIN(ANALYZE,BUFFERS) 显示
82.8ms 里 35ms 花在「把整张 work_order_shares 聚合一遍再 join」:
Index Only Scan rows=22,987 → GroupAggregate → 最终只用到 11 行;同一条 SQL 里
还有第二遍同样的全局聚合(filter_share_summary)。已改成按工单限定的关联子查询
(走 idx_work_order_shares_work_order):
- buildWorkOrderWhere 的 excludeFilledSharing 过滤条件;
- buildHallCandidateIdsSql 的 candidate_shares。
同参数实测:50.3ms → 2.1ms、35.9ms → 3.8ms、28.4ms → 1.8ms,buffer 10,145 → 187,
三种参数下结果逐行一致(11/11、5/5、11/11)。
有意保留 operational_share_summary 那一处全局聚合:全表统计(103,073 行)时
关联子查询要 103,073 次探测(330ms),一次聚合只要 55ms;已有用例钉住「只允许剩一处」。

二、容器与数据库参数
- POSTGRES_CPUS 2 → 3(4 核机器,同机其它容器实测只用 0.2-0.4 核且几乎不节流);
- max_parallel_workers_per_gather 2 → 1(>1 时一条查询要 leader+2 worker=3 核,必撞配额);
- checkpoint_timeout 300 → 900s、max_wal_size 1GB → 4GB(43 天累计 21,778 次检查点,
  sar 里就是每 20-40 分钟一轮写突发)。

三、迁移 092:热表 autovacuum 调参
可见性图陈旧导致 index-only scan 回表 5,580 次;work_orders/order_items/orders/
kuaishou_industry_vouchers/fulfillment_tasks 死元组 1.9 万-5.3 万、上次清理在前一天
甚至 9/29。把这些表的 scale_factor 0.2→0.05、analyze 0.1→0.02、cost_limit 200→800,
把「攒批猛扫」拆成「小步勤清」,并打开 5s 以上的 autovacuum 日志便于观测。

用例:新增 share-summary-scope.test.ts(钉住热路径不再出现整表聚合、只保留一处且有注释说明),
env-documentation 白名单补 3 个新增部署变量。全量 599 通过,typecheck/lint/check:sql 通过。
This commit is contained in:
yml2213
2026-10-03 17:21:21 +08:00
parent ccbcb7cbae
commit 905236d83a
6 changed files with 204 additions and 21 deletions
+8 -1
View File
@@ -37,11 +37,18 @@ POSTGRES_LOG_MIN_DURATION_STATEMENT_MS=500
# 服务器设置;数据库独占或内存更足时可上调 shared_buffers 与
# effective_cache_size(后者只是规划器提示,不实际占用内存):
POSTGRES_MEM_LIMIT=1.5g
POSTGRES_CPUS=2
# 4 核共用机器上的推荐值:postgres 是最大单一消耗者,2 核会被突发打满并触发
# cgroup 节流(生产实测累计被节流 2.6 小时);同机其它容器实测只用 0.2–0.4 核。
POSTGRES_CPUS=3
POSTGRES_SHARED_BUFFERS=384MB
POSTGRES_EFFECTIVE_CACHE_SIZE=4GB
POSTGRES_WORK_MEM=16MB
POSTGRES_MAINTENANCE_WORK_MEM=256MB
# 单条查询并行度:>1 时一条查询要 leader + 2 worker = 3 核,容易撞容器配额被节流
POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER=1
# 检查点频率与 WAL 上限:放宽后写突发从「5 分钟一轮」变成「15 分钟一轮」
POSTGRES_CHECKPOINT_TIMEOUT=900
POSTGRES_MAX_WAL_SIZE=4GB
# 后端与 Web 容器限额(内存紧张的小机器可相应收紧)
BACKEND_MEM_LIMIT=768m
BACKEND_CPUS=1.5