Files
order_site/apps/backend/src/startup/memory-reporter.ts
T
yml2213 1ca15e9025 perf(db): 统计金额落到列上、会话刷新节流、补齐保留与内存治理
背景:生产 32 天内 >500ms 慢查询从 2.4k/天涨到 1.3 万/天(09-26 尖峰 14.3 万),
pg_stat_statements 显示后台工单列表族占 57%、打手端 visible_orders 占 27%;
同时 backend 容器十天从 194MB 涨到 754MB(768MB 限额的 98%),贴顶后整体变慢。

1. 新增迁移 083:work_orders 增加派生列 source_order_amount_fen,并从
   material_json.source.orderAmountFen 回填(只认 15 位以内整数,与 JS/SQL 口径一致)。
   统计查询原来每行要解析 1.2KB JSON(全表约 132MB)且同一表达式求值两次,
   生产实测 3.5~4.4 秒;改走派生列后 COALESCE 短路,未命中列的行才回退 JSON/orders.total_amount,
   口径与改造前完全一致。写入点:createWorkOrder / updateWorkOrder 各一处,并加静态守卫。
2. worker_sessions.last_seen_at 从"每个请求都写"改为"5 分钟才写一次":
   32 天被写了 3300 万次(占 DB 总时间 2.8%,并堆出 14% 死元组)。仍用一条语句返回鉴权有效性
   (EXISTS target AS valid),不能因为不写就让上层 401。
3. 保留策略补上 work_order_events(71.7 万行 / 269MB,此前完全没纳入清理,只增不减),
   新增配置 retention.workOrderEventDays(默认 180 天)与 WORK_ORDER_EVENT_RETENTION_DAYS 出口。
4. 内存治理:compose 后端限额 768m→1g 并显式设置 V8 老生代上限(--max-old-space-size=768),
   避免贴 cgroup 上限后 GC 抖动;新增每 10 分钟的进程内存快照日志(rss/heapUsed/heapLimit),
   用于区分"正常 GC 曲线"与"真泄漏"。
5. 限流桶加 5 万条硬上限:key 里含请求体字段(collect 的 orderNo)时可被构造成高基数,
   只按过期清理挡不住内存增长。

验证:后端 530 项单测全绿(新增 11 项,含派生列/会话/保留的静态守卫与解析函数单测)。
2026-10-01 12:55:09 +08:00

41 lines
1.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import v8 from 'node:v8'
import { logInfo } from '../utils/logger.js'
/**
* 周期性记录进程内存快照,用于判断 backend 容器内存是"正常 GC 曲线"还是"持续增长"。
*
* 为什么需要它:生产上 backend 限额 768MB,重启后 194MB,十天后贴到 754MB(98%),
* 之后整个服务明显变慢——但仅凭容器数字分不清是 V8 贴近 cgroup 上限导致 GC 抖动,
* 还是真有增长点。有了这份日志就能对比:
* - rssMb 长期单调上升、重启后回落明显 → 存在增长点(缓存/事件缓冲/连接堆积);
* - rssMb 稳定、heapUsedMb 上下波动 → 正常 GC 曲线,调限额即可。
*
* 每 10 分钟一条,logger 内部已有按天滚动与 30 天保留,不会堆积。
*/
const MEMORY_REPORT_INTERVAL_MS = 10 * 60 * 1000
function toMb(bytes: number): number {
return Math.round((Number(bytes) || 0) / 1024 / 1024)
}
export function startMemoryReporter(): NodeJS.Timeout {
const report = () => {
const usage = process.memoryUsage()
logInfo('[memory]', '进程内存快照', {
rssMb: toMb(usage.rss),
heapUsedMb: toMb(usage.heapUsed),
heapTotalMb: toMb(usage.heapTotal),
externalMb: toMb(usage.external),
arrayBuffersMb: toMb(usage.arrayBuffers || 0),
heapLimitMb: toMb(v8.getHeapStatistics().heap_size_limit),
})
}
report()
const timer = setInterval(report, MEMORY_REPORT_INTERVAL_MS)
// 不因为这个定时器把进程钉在事件循环里(关闭流程/测试环境都受益)
timer.unref()
return timer
}