Files
MyGoNavi/internal/db/memory_limit_autoscale_test.go
Syngnat 98965a56e1 🐛 fix(memory): 修复大数据量导出导致进程内存飙升至 16G 的问题
- GC 策略:主进程与 driver-agent 启动时收紧 SetGCPercent 至 50
- 周期回收:scan_rows 与 callStreamQuery 每 5 万行触发 runtime.GC
- 自适应限流:driver-agent 引入 GOMEMLIMIT 自适应策略,2GB 起步按 1GB 步长抬升至 8GB 上限
- 批次调优:流式批次由 256 行缩减至 64 行,降低 JSON 编解码瞬时峰值
2026-06-19 12:05:02 +08:00

130 lines
4.5 KiB
Go
Raw Permalink 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.
package db
import (
"testing"
)
func TestShouldGrowMemoryLimit_NoActionWhenBelowThreshold(t *testing.T) {
current := int64(2 * 1024 * 1024 * 1024) // 2GB
// HeapAlloc 仅占 50%,远低于 80% 阈值
heapAlloc := current * 50 / 100
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if grown {
t.Fatalf("HeapAlloc=%dB 低于 80%% 阈值,不应抬升", heapAlloc)
}
if next != current {
t.Fatalf("未抬升时 next 应等于 currentgot=%d want=%d", next, current)
}
}
func TestShouldGrowMemoryLimit_NoActionAtExactThreshold(t *testing.T) {
current := int64(2 * 1024 * 1024 * 1024)
// HeapAlloc 正好等于 80% 阈值heapAlloc < current*80/100 为假时才抬升
// current*80/100 = 1.6GBheapAlloc = 1.6GB 时 heapAlloc < 1.6GB 为假 → 抬升
heapAlloc := current * MemoryAutoscaleTriggerPercent / 100
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if !grown {
t.Fatalf("HeapAlloc=%dB 已达 80%% 阈值,应抬升", heapAlloc)
}
wantNext := current + MemorySoftLimitStepBytes
if next != wantNext {
t.Fatalf("抬升步长错误got=%d want=%d", next, wantNext)
}
}
func TestShouldGrowMemoryLimit_StepByGB(t *testing.T) {
current := int64(2 * 1024 * 1024 * 1024) // 2GB
heapAlloc := int64(3 * 1024 * 1024 * 1024) // 3GB > 2GB * 80% = 1.6GB
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if !grown {
t.Fatalf("HeapAlloc=%dB 超过 80%% 阈值,应抬升", heapAlloc)
}
wantNext := int64(3 * 1024 * 1024 * 1024) // 2GB + 1GB step = 3GB
if next != wantNext {
t.Fatalf("抬升后 limit 应为 3GBgot=%d want=%d", next, wantNext)
}
}
func TestShouldGrowMemoryLimit_CapAtMax(t *testing.T) {
// 当前 limit 已等于上限
current := MemorySoftLimitMaxBytes
heapAlloc := current * 2 // 即使 HeapAlloc 远超 limit 也不再抬升
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if grown {
t.Fatalf("已达上限 %dB不应再抬升", MemorySoftLimitMaxBytes)
}
if next != current {
t.Fatalf("已达上限时 next 应等于 currentgot=%d want=%d", next, current)
}
}
func TestShouldGrowMemoryLimit_CapWhenStepExceedsMax(t *testing.T) {
// 当前 limit 距上限不足 1GB 步长7.5GB
current := MemorySoftLimitMaxBytes - 512*1024*1024 // 7.5GB
heapAlloc := current + 1 // 超过 80% 阈值
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if !grown {
t.Fatalf("HeapAlloc 已逼近 limit应触发抬升即便步长会触及上限")
}
if next != MemorySoftLimitMaxBytes {
t.Fatalf("抬升后应 cap 在 maxgot=%d want=%d", next, MemorySoftLimitMaxBytes)
}
}
func TestShouldGrowMemoryLimit_NoActionWhenCurrentExceedsMax(t *testing.T) {
// 异常情况current > max理论不会发生但应防御性处理
current := MemorySoftLimitMaxBytes + 1
heapAlloc := current * 2
grown, next := shouldGrowMemoryLimit(current, heapAlloc)
if grown {
t.Fatalf("current 已超过 max不应再抬升")
}
if next != current {
t.Fatalf("next 应等于 currentgot=%d want=%d", next, current)
}
}
func TestInitMemorySoftLimit_ClampToMax(t *testing.T) {
// 初始化值超过 max 时应被截断
overMax := MemorySoftLimitMaxBytes * 2
InitMemorySoftLimit(overMax)
if got := CurrentMemorySoftLimit(); got != MemorySoftLimitMaxBytes {
t.Fatalf("初始化超过 max 应被截断got=%d want=%d", got, MemorySoftLimitMaxBytes)
}
// 恢复默认值,避免污染其他测试
InitMemorySoftLimit(MemorySoftLimitInitialBytes)
}
func TestInitMemorySoftLimit_DefaultWhenZeroOrNegative(t *testing.T) {
InitMemorySoftLimit(0)
if got := CurrentMemorySoftLimit(); got != MemorySoftLimitInitialBytes {
t.Fatalf("initial=0 应使用默认值got=%d want=%d", got, MemorySoftLimitInitialBytes)
}
InitMemorySoftLimit(-1)
if got := CurrentMemorySoftLimit(); got != MemorySoftLimitInitialBytes {
t.Fatalf("initial<0 应使用默认值got=%d want=%d", got, MemorySoftLimitInitialBytes)
}
}
func TestMaybeGrowMemoryLimit_NoOpWhenUninitialized(t *testing.T) {
// 模拟主进程未初始化的场景:
// 通过将 currentMemorySoftLimit 直接置零(绕过 InitMemorySoftLimit来测试
// 注意:这是一个破坏性测试,需在测试末尾恢复状态
saved := currentMemorySoftLimit.Load()
defer currentMemorySoftLimit.Store(saved)
currentMemorySoftLimit.Store(0)
if MaybeGrowMemoryLimit() {
t.Fatalf("currentMemorySoftLimit=0 时应直接返回 false不主动初始化")
}
if got := CurrentMemorySoftLimit(); got != 0 {
t.Fatalf("未初始化时不应被 MaybeGrowMemoryLimit 改写got=%d want=0", got)
}
}