mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-25 16:04:02 +08:00
- GC 策略:主进程与 driver-agent 启动时收紧 SetGCPercent 至 50 - 周期回收:scan_rows 与 callStreamQuery 每 5 万行触发 runtime.GC - 自适应限流:driver-agent 引入 GOMEMLIMIT 自适应策略,2GB 起步按 1GB 步长抬升至 8GB 上限 - 批次调优:流式批次由 256 行缩减至 64 行,降低 JSON 编解码瞬时峰值
118 lines
4.4 KiB
Go
118 lines
4.4 KiB
Go
package db
|
||
|
||
import (
|
||
"runtime"
|
||
"runtime/debug"
|
||
"sync/atomic"
|
||
|
||
"GoNavi-Wails/internal/logger"
|
||
)
|
||
|
||
// 本文件实现 driver-agent 进程的 GOMEMLIMIT 自适应策略。
|
||
//
|
||
// 背景:driver-agent 是独立子进程,主进程无法控制其内存。
|
||
// 静态 limit(如固定 2GB)在大结果集场景下会触发 GC 硬模式,导出速度降 15-25%;
|
||
// 而 limit 设太大又失去约束意义。
|
||
//
|
||
// 策略:起步保守(2GB),运行时监控 HeapAlloc,逼近当前 limit 时按 1GB 步长抬升,
|
||
// 上限 8GB 防止无限制增长。配合 SetGCPercent(50) + 周期 GC,正常场景下稳态堆
|
||
// 仅几百 MB,limit 不会被触发;只有 GC 真的跟不上时才抬升。
|
||
|
||
const (
|
||
// MemorySoftLimitInitialBytes 是进程启动时的默认 soft limit。
|
||
// 2GB 覆盖绝大多数导出场景的稳态堆需求。
|
||
MemorySoftLimitInitialBytes int64 = 2 * 1024 * 1024 * 1024
|
||
|
||
// MemorySoftLimitMaxBytes 是自适应抬升的绝对上限。
|
||
// 8GB 防止失控;用户机器内存 < 16GB 时也留有余量给主进程和系统。
|
||
MemorySoftLimitMaxBytes int64 = 8 * 1024 * 1024 * 1024
|
||
|
||
// MemorySoftLimitStepBytes 是每次抬升的步长。
|
||
// 1GB 粒度足够平滑(不会一次跳太多),又不会频繁触发(HeapAlloc 1GB 量级才需要再抬)。
|
||
MemorySoftLimitStepBytes int64 = 1 * 1024 * 1024 * 1024
|
||
|
||
// MemoryAutoscaleTriggerPercent 控制 HeapAlloc 达到当前 limit 的多少百分比时触发抬升。
|
||
// 80% 留出 20% 缓冲,避免 GC 噪声导致频繁抖动抬升。
|
||
MemoryAutoscaleTriggerPercent = 80
|
||
)
|
||
|
||
// currentMemorySoftLimit 记录当前已应用的 soft limit。
|
||
// atomic 以便 MaybeGrowMemoryLimit 在并发流式查询中安全调用。
|
||
var currentMemorySoftLimit atomic.Int64
|
||
|
||
// InitMemorySoftLimit 在进程启动时调用,应用初始 soft limit。
|
||
// 重复调用安全:以最后一次为准。
|
||
func InitMemorySoftLimit(initial int64) {
|
||
if initial <= 0 {
|
||
initial = MemorySoftLimitInitialBytes
|
||
}
|
||
if initial > MemorySoftLimitMaxBytes {
|
||
initial = MemorySoftLimitMaxBytes
|
||
}
|
||
debug.SetMemoryLimit(initial)
|
||
currentMemorySoftLimit.Store(initial)
|
||
}
|
||
|
||
// CurrentMemorySoftLimit 返回当前已应用的 soft limit,主要供测试和监控使用。
|
||
func CurrentMemorySoftLimit() int64 {
|
||
return currentMemorySoftLimit.Load()
|
||
}
|
||
|
||
// MaybeGrowMemoryLimit 在大结果集流式处理时周期性调用(建议与周期 GC 同节奏),
|
||
// 当堆用量达到当前 limit 的 MemoryAutoscaleTriggerPercent 时按步长抬升。
|
||
//
|
||
// 设计要点:
|
||
// - 仅对调用过 InitMemorySoftLimit 的进程生效(driver-agent);主进程未初始化时 currentMemorySoftLimit=0,
|
||
// 本函数直接返回,不影响主进程的 GC 行为
|
||
// - 读 HeapAlloc 用 runtime.ReadMemStats(短暂 STW,每 5W 行一次可忽略)
|
||
// - 抬升通过 debug.SetMemoryLimit 应用,原子记录新值
|
||
// - 达到 MemorySoftLimitMaxBytes 后不再抬升,让 GC 硬模式接管
|
||
// - 不做"降级":进程 long-running,下次任务可能同样需要;soft limit 大不浪费内存
|
||
//
|
||
// 返回 true 表示触发了抬升(用于日志观测)。
|
||
func MaybeGrowMemoryLimit() bool {
|
||
current := currentMemorySoftLimit.Load()
|
||
if current <= 0 {
|
||
// 进程未启用 soft limit(如主进程),跳过自适应
|
||
return false
|
||
}
|
||
|
||
grown, next := shouldGrowMemoryLimit(current, readHeapAlloc())
|
||
if !grown {
|
||
return false
|
||
}
|
||
|
||
currentHeap := readHeapAlloc()
|
||
debug.SetMemoryLimit(next)
|
||
currentMemorySoftLimit.Store(next)
|
||
logger.Infof("内存 soft limit 自适应抬升:%dMB → %dMB(HeapAlloc=%dMB)",
|
||
current/1024/1024, next/1024/1024, currentHeap/1024/1024)
|
||
return true
|
||
}
|
||
|
||
// shouldGrowMemoryLimit 是 MaybeGrowMemoryLimit 的纯逻辑核心,便于单元测试。
|
||
// 输入:当前 limit、当前 HeapAlloc;输出:是否抬升、抬升后的新 limit。
|
||
func shouldGrowMemoryLimit(currentLimit, heapAlloc int64) (bool, int64) {
|
||
if currentLimit >= MemorySoftLimitMaxBytes {
|
||
return false, currentLimit
|
||
}
|
||
if heapAlloc < currentLimit*MemoryAutoscaleTriggerPercent/100 {
|
||
return false, currentLimit
|
||
}
|
||
next := currentLimit + MemorySoftLimitStepBytes
|
||
if next > MemorySoftLimitMaxBytes {
|
||
next = MemorySoftLimitMaxBytes
|
||
}
|
||
if next == currentLimit {
|
||
return false, currentLimit
|
||
}
|
||
return true, next
|
||
}
|
||
|
||
// readHeapAlloc 封装 runtime.ReadMemStats,便于测试 mock。
|
||
func readHeapAlloc() int64 {
|
||
var m runtime.MemStats
|
||
runtime.ReadMemStats(&m)
|
||
return int64(m.HeapAlloc)
|
||
}
|