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
+16 -1
View File
@@ -26,11 +26,26 @@ services:
"work_mem=${POSTGRES_WORK_MEM:-16MB}",
"-c",
"maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-256MB}",
# 并行度 1:生产实测单条大查询一旦开 2 个 parallel worker,就需要
# 「leader + 2 worker = 3 核」,而容器配额只有 2 核 → 必然撞配额被节流
# (累计 nr_throttled 94,197 次 / throttled_usec 9,337s)。重查询已优化到
# 10ms 量级,用不到并行。
"-c",
"max_parallel_workers_per_gather=${POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER:-1}",
# 检查点:默认 5 分钟一次 + max_wal_size 1GB,生产 43 天累计 21,778 次检查点,
# sar 里就是每 20–40 分钟一轮写突发(wtps 130、8MB/s)。放宽到 15 分钟 / 4GB。
"-c",
"checkpoint_timeout=${POSTGRES_CHECKPOINT_TIMEOUT:-900}",
"-c",
"max_wal_size=${POSTGRES_MAX_WAL_SIZE:-4GB}",
]
restart: unless-stopped
# 让 PostgreSQL 优雅关闭有足够时间完成检查点;无限制时 OOM 可能拖垮整机。
mem_limit: ${POSTGRES_MEM_LIMIT:-1.5g}
cpus: ${POSTGRES_CPUS:-2}
# 3 核:这台机器 4 核,postgres 是最大单一消耗者(22.6h 烧 12.2 CPU·h、均 0.54 核),
# 却被 2 核配额卡住,累计 2.6 小时处于被节流状态。同机其它容器实测只用 0.2–0.4 核,
# 且几乎不节流,所以这里让出 1 核给数据库的突发。
cpus: ${POSTGRES_CPUS:-3}
shm_size: 512mb
stop_grace_period: 60s
logging: *json-log-rotation