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:
+8
-1
@@ -37,11 +37,18 @@ POSTGRES_LOG_MIN_DURATION_STATEMENT_MS=500
|
|||||||
# 服务器设置;数据库独占或内存更足时可上调 shared_buffers 与
|
# 服务器设置;数据库独占或内存更足时可上调 shared_buffers 与
|
||||||
# effective_cache_size(后者只是规划器提示,不实际占用内存):
|
# effective_cache_size(后者只是规划器提示,不实际占用内存):
|
||||||
POSTGRES_MEM_LIMIT=1.5g
|
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_SHARED_BUFFERS=384MB
|
||||||
POSTGRES_EFFECTIVE_CACHE_SIZE=4GB
|
POSTGRES_EFFECTIVE_CACHE_SIZE=4GB
|
||||||
POSTGRES_WORK_MEM=16MB
|
POSTGRES_WORK_MEM=16MB
|
||||||
POSTGRES_MAINTENANCE_WORK_MEM=256MB
|
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 容器限额(内存紧张的小机器可相应收紧)
|
# 后端与 Web 容器限额(内存紧张的小机器可相应收紧)
|
||||||
BACKEND_MEM_LIMIT=768m
|
BACKEND_MEM_LIMIT=768m
|
||||||
BACKEND_CPUS=1.5
|
BACKEND_CPUS=1.5
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ const DEPLOYMENT_ONLY_ENV_NAMES = [
|
|||||||
'POSTGRES_EFFECTIVE_CACHE_SIZE',
|
'POSTGRES_EFFECTIVE_CACHE_SIZE',
|
||||||
'POSTGRES_WORK_MEM',
|
'POSTGRES_WORK_MEM',
|
||||||
'POSTGRES_MAINTENANCE_WORK_MEM',
|
'POSTGRES_MAINTENANCE_WORK_MEM',
|
||||||
|
'POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER',
|
||||||
|
'POSTGRES_CHECKPOINT_TIMEOUT',
|
||||||
|
'POSTGRES_MAX_WAL_SIZE',
|
||||||
'BACKEND_MEM_LIMIT',
|
'BACKEND_MEM_LIMIT',
|
||||||
'BACKEND_CPUS',
|
'BACKEND_CPUS',
|
||||||
'BACKEND_NODE_OPTIONS',
|
'BACKEND_NODE_OPTIONS',
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
-- 092_hot_table_autovacuum_tuning.sql —— 让热表的 autovacuum 更勤、更快。
|
||||||
|
--
|
||||||
|
-- 背景(2026-10-03 生产实测,库 order_site @47.114.114.225,4 核机器):
|
||||||
|
--
|
||||||
|
-- 1) 可见性图陈旧,index-only scan 退化成回表:
|
||||||
|
-- 接单大厅那条 COUNT 的 EXPLAIN 里 `Index Only Scan on work_order_shares`
|
||||||
|
-- 出现 **Heap Fetches: 5580**(22,987 行里 1/4 要回堆),10,145 个 buffer 命中
|
||||||
|
-- 换回 1 行 COUNT 结果。heap fetch 是随机页访问,比顺序扫还贵。
|
||||||
|
--
|
||||||
|
-- 2) 默认 autovacuum_vacuum_scale_factor = 0.2,等于「死元组攒到 20% 才清」:
|
||||||
|
-- work_orders 死元组 18,798(15.4%,最后一次 autovacuum 是前一天 15:42)、
|
||||||
|
-- order_items 52,803(15.8%,上次 9/29)、orders 19,610(上次 9/30)、
|
||||||
|
-- kuaishou_industry_vouchers 38,325(10.9%,1,007,929 次更新)。
|
||||||
|
-- 攒一大批再扫一次 → 单次 vacuum 又长又重,正好和「CPU 忽高忽低」的
|
||||||
|
-- 周期性尖峰、以及 sar 里每 20–40 分钟一轮的读突发(rtps 95/s、6MB/s)对上。
|
||||||
|
--
|
||||||
|
-- 3) 这几张表更新极频繁(work_orders 649,751 次更新、fulfillment_tasks 2,314,711 次),
|
||||||
|
-- 是典型的「小步勤清」比「攒批猛扫」更划算的场景。
|
||||||
|
--
|
||||||
|
-- 因此对最热的 5 张表单独调参(只改表级 storage parameter,不动全局默认):
|
||||||
|
-- autovacuum_vacuum_scale_factor 0.2 → 0.05 触发阈值降到 5% 死元组
|
||||||
|
-- autovacuum_analyze_scale_factor 0.1 → 0.02 统计信息更及时
|
||||||
|
-- autovacuum_vacuum_cost_limit 200 → 800 单次 vacuum 更快跑完(IO 更碎而不是更长)
|
||||||
|
--
|
||||||
|
-- 安全性:这些只是把「一次大扫」拆成「多次小扫」,单位时间总 IO 相近;
|
||||||
|
-- 机器白天有 60–70% 空闲(sar: idle 62.9%),碎片化清理比尖峰更可控。
|
||||||
|
-- 回滚:ALTER TABLE ... RESET (autovacuum_*)。
|
||||||
|
|
||||||
|
ALTER TABLE work_orders SET (
|
||||||
|
autovacuum_vacuum_scale_factor = 0.05,
|
||||||
|
autovacuum_analyze_scale_factor = 0.02,
|
||||||
|
autovacuum_vacuum_cost_limit = 800
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE order_items SET (
|
||||||
|
autovacuum_vacuum_scale_factor = 0.05,
|
||||||
|
autovacuum_analyze_scale_factor = 0.02,
|
||||||
|
autovacuum_vacuum_cost_limit = 800
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE orders SET (
|
||||||
|
autovacuum_vacuum_scale_factor = 0.05,
|
||||||
|
autovacuum_analyze_scale_factor = 0.02,
|
||||||
|
autovacuum_vacuum_cost_limit = 800
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE kuaishou_industry_vouchers SET (
|
||||||
|
autovacuum_vacuum_scale_factor = 0.05,
|
||||||
|
autovacuum_analyze_scale_factor = 0.02,
|
||||||
|
autovacuum_vacuum_cost_limit = 800
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE fulfillment_tasks SET (
|
||||||
|
autovacuum_vacuum_scale_factor = 0.05,
|
||||||
|
autovacuum_analyze_scale_factor = 0.02,
|
||||||
|
autovacuum_vacuum_cost_limit = 800
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 打开 autovacuum 观测(原先 log_autovacuum_min_duration = 600000,
|
||||||
|
-- 只有超过 10 分钟的 vacuum 才会留日志,等于什么都看不到)。
|
||||||
|
-- 这里改成表级不可设,只能改库级:记录超过 5 秒的自动清理,便于事后核对清理节奏。
|
||||||
|
ALTER DATABASE order_site SET log_autovacuum_min_duration = 5000;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2026-10-03 生产复盘(库 order_site @47.114.114.225):
|
||||||
|
*
|
||||||
|
* 这条「接单大厅 COUNT」50 小时里被调 71,539 次、平均 103ms,EXPLAIN 显示
|
||||||
|
* 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),
|
||||||
|
* 同参数下 50.3ms → 2.1ms,buffer 10,145 → 187,结果逐行一致。
|
||||||
|
*
|
||||||
|
* 唯一保留全局聚合的地方是 operational_share_summary(无打手范围的全表统计):
|
||||||
|
* 实测 103,073 行的全表统计里,关联子查询要 103,073 次索引探测(330ms),
|
||||||
|
* 而一次全局聚合只要 55ms,所以那里是有意保留的。这个用例把「只允许剩一处」
|
||||||
|
* 钉死,避免以后又在热路径上复制粘贴出全局聚合。
|
||||||
|
*/
|
||||||
|
async function readRepoSource() {
|
||||||
|
return readFile(new URL('./work-order-query-repo.ts', import.meta.url), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 去掉 SQL 行注释(--)与 TS 行注释(//),避免注释里提到旧写法时被误判成代码。 */
|
||||||
|
function stripComments(source: string) {
|
||||||
|
return source
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.replace(/--.*$/, '').replace(/\/\/.*$/, ''))
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
test('excludeFilledSharing 不再拼接「整表拼单聚合」,改用按工单限定的关联子查询', async () => {
|
||||||
|
const source = await readRepoSource()
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!source.includes('filter_share_summary.active_share_quantity'),
|
||||||
|
'excludeFilledSharing 的过滤条件仍引用全局聚合别名 filter_share_summary',
|
||||||
|
)
|
||||||
|
|
||||||
|
const excludeBlock = source.slice(
|
||||||
|
source.indexOf('if (excludeFilledSharing) {'),
|
||||||
|
source.indexOf('if (excludeFilledSharing) {') + 1200,
|
||||||
|
)
|
||||||
|
assert.ok(excludeBlock.includes('FROM work_order_shares wos'), '缺少按工单限定的拼单查询')
|
||||||
|
assert.ok(
|
||||||
|
excludeBlock.includes('WHERE wos.work_order_id = wo.id'),
|
||||||
|
'excludeFilledSharing 的拼单查询没有按 wo.id 限定,会退化成全表聚合',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('大厅候选集(buildHallCandidateIdsSql)不再聚合整表拼单', async () => {
|
||||||
|
const source = stripComments(await readRepoSource())
|
||||||
|
const start = source.indexOf('function buildHallCandidateIdsSql')
|
||||||
|
const hallSql = source.slice(start, start + 2600)
|
||||||
|
|
||||||
|
assert.ok(!hallSql.includes('candidate_shares'), '大厅候选集仍在用全局聚合别名 candidate_shares')
|
||||||
|
assert.ok(!hallSql.includes('GROUP BY work_order_id'), '大厅候选集仍在聚合整张 work_order_shares')
|
||||||
|
assert.ok(
|
||||||
|
hallSql.includes('WHERE wos.work_order_id = candidate.id'),
|
||||||
|
'大厅候选集的拼单份额没有按候选工单限定',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('全仓库只允许保留一处「整表拼单聚合」,且必须是无打手范围的全表统计分支', async () => {
|
||||||
|
const raw = await readRepoSource()
|
||||||
|
const source = stripComments(raw)
|
||||||
|
const globalAggregations = [
|
||||||
|
...source.matchAll(/GROUP BY\s+wos\.work_order_id|GROUP BY\s+work_order_id/g),
|
||||||
|
]
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
globalAggregations.length,
|
||||||
|
1,
|
||||||
|
`整表拼单聚合只应剩 operational_share_summary 一处,实际 ${globalAggregations.length} 处:` +
|
||||||
|
'新增的每一处都会在列表/计数热路径上被乘以每秒数次调用',
|
||||||
|
)
|
||||||
|
|
||||||
|
const onlyRemaining = globalAggregations[0]!
|
||||||
|
const around = source.slice(Math.max(0, onlyRemaining.index - 900), onlyRemaining.index + 200)
|
||||||
|
assert.ok(
|
||||||
|
around.includes('operational_share_summary'),
|
||||||
|
'保留的那处整表聚合不在 operational_share_summary 分支里,需要确认是否有意为之',
|
||||||
|
)
|
||||||
|
|
||||||
|
// 注释在 stripComments 里被去掉了,所以断言理由时用原文
|
||||||
|
const rawIndex = raw.indexOf('GROUP BY wos.work_order_id')
|
||||||
|
const rawAround = raw.slice(Math.max(0, rawIndex - 1200), rawIndex + 200)
|
||||||
|
assert.ok(
|
||||||
|
rawAround.includes('无打手范围时一次聚合全部拼单'),
|
||||||
|
'保留理由(无打手范围时一次聚合优于逐行关联)必须写在注释里,避免被误删',
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -891,16 +891,10 @@ export function buildWorkOrderWhere({
|
|||||||
}
|
}
|
||||||
let workerSharePlaceholder = ''
|
let workerSharePlaceholder = ''
|
||||||
|
|
||||||
if (excludeFilledSharing) {
|
// 旧的 excludeFilledSharing 写法:LEFT JOIN 一份「整张 work_order_shares 的聚合」
|
||||||
joins.push(`
|
// (GROUP BY work_order_id)再在 WHERE 里比较。列表/计数每次调用都要重算这份全局聚合,
|
||||||
LEFT JOIN (
|
// 但真正要用到的只是候选工单那几十行——生产 EXPLAIN 里它占了 COUNT 的主要成本。
|
||||||
SELECT
|
// 这里改成和 adminOperationalStatusExpression 同一口径的关联子查询:只按 wo.id 探测。
|
||||||
wos.work_order_id,
|
|
||||||
SUM(wos.quantity) FILTER (WHERE wos.status != 'cancelled') AS active_share_quantity
|
|
||||||
FROM work_order_shares wos
|
|
||||||
GROUP BY wos.work_order_id
|
|
||||||
) filter_share_summary ON filter_share_summary.work_order_id = wo.id`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先从打手的整单、历史单和拼单记录组成可见工单集合,再关联工单详情。
|
// 先从打手的整单、历史单和拼单记录组成可见工单集合,再关联工单详情。
|
||||||
// 这样 COUNT 和列表都不会先扫描全量工单,再逐行检查该打手的拼单状态。
|
// 这样 COUNT 和列表都不会先扫描全量工单,再逐行检查该打手的拼单状态。
|
||||||
@@ -1187,7 +1181,12 @@ export function buildWorkOrderWhere({
|
|||||||
if (excludeFilledSharing) {
|
if (excludeFilledSharing) {
|
||||||
filters.push(`(
|
filters.push(`(
|
||||||
wo.sharing_enabled IS NOT TRUE
|
wo.sharing_enabled IS NOT TRUE
|
||||||
OR COALESCE(filter_share_summary.active_share_quantity, 0) < wo.sharing_total_quantity
|
OR COALESCE((
|
||||||
|
SELECT SUM(wos.quantity)
|
||||||
|
FROM work_order_shares wos
|
||||||
|
WHERE wos.work_order_id = wo.id
|
||||||
|
AND wos.status != 'cancelled'
|
||||||
|
), 0) < wo.sharing_total_quantity
|
||||||
)`)
|
)`)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -1217,17 +1216,20 @@ function buildHallCandidateIdsSql(limitPlaceholder: string, categoryLimitsPlaceh
|
|||||||
) AS sub_category_rank
|
) AS sub_category_rank
|
||||||
FROM work_orders candidate
|
FROM work_orders candidate
|
||||||
LEFT JOIN work_categories candidate_wc ON candidate_wc.id = candidate.category_id
|
LEFT JOIN work_categories candidate_wc ON candidate_wc.id = candidate.category_id
|
||||||
LEFT JOIN (
|
|
||||||
SELECT
|
|
||||||
work_order_id,
|
|
||||||
SUM(quantity) FILTER (WHERE status != 'cancelled') AS active_share_quantity
|
|
||||||
FROM work_order_shares
|
|
||||||
GROUP BY work_order_id
|
|
||||||
) candidate_shares ON candidate_shares.work_order_id = candidate.id
|
|
||||||
WHERE candidate.status = 'open'
|
WHERE candidate.status = 'open'
|
||||||
AND (
|
AND (
|
||||||
candidate.sharing_enabled IS NOT TRUE
|
candidate.sharing_enabled IS NOT TRUE
|
||||||
OR COALESCE(candidate_shares.active_share_quantity, 0) < candidate.sharing_total_quantity
|
-- 只按候选工单取拼单份额。历史写法是先把整张 work_order_shares 聚合一遍
|
||||||
|
-- (GROUP BY work_order_id)再 join:生产实测那次聚合 22,987 行 / 35ms / 5,025 buffer,
|
||||||
|
-- 而候选只有 38 笔,聚合结果 99% 被丢掉;而且它是这条 COUNT 的主成本
|
||||||
|
-- (EXPLAIN: Aggregate 82.8ms → Index Only Scan + GroupAggregate 35ms)。
|
||||||
|
-- 关联子查询走 idx_work_order_shares_work_order,每笔候选一次索引探测即可。
|
||||||
|
OR COALESCE((
|
||||||
|
SELECT SUM(wos.quantity)
|
||||||
|
FROM work_order_shares wos
|
||||||
|
WHERE wos.work_order_id = candidate.id
|
||||||
|
AND wos.status != 'cancelled'
|
||||||
|
), 0) < candidate.sharing_total_quantity
|
||||||
)
|
)
|
||||||
) ranked
|
) ranked
|
||||||
WHERE ranked.primary_category_rank <= COALESCE(
|
WHERE ranked.primary_category_rank <= COALESCE(
|
||||||
|
|||||||
+16
-1
@@ -26,11 +26,26 @@ services:
|
|||||||
"work_mem=${POSTGRES_WORK_MEM:-16MB}",
|
"work_mem=${POSTGRES_WORK_MEM:-16MB}",
|
||||||
"-c",
|
"-c",
|
||||||
"maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-256MB}",
|
"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
|
restart: unless-stopped
|
||||||
# 让 PostgreSQL 优雅关闭有足够时间完成检查点;无限制时 OOM 可能拖垮整机。
|
# 让 PostgreSQL 优雅关闭有足够时间完成检查点;无限制时 OOM 可能拖垮整机。
|
||||||
mem_limit: ${POSTGRES_MEM_LIMIT:-1.5g}
|
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
|
shm_size: 512mb
|
||||||
stop_grace_period: 60s
|
stop_grace_period: 60s
|
||||||
logging: *json-log-rotation
|
logging: *json-log-rotation
|
||||||
|
|||||||
Reference in New Issue
Block a user