mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 10:28:06 +00:00
perf(webui)+feat(config): 聊天记录写盘节流 + 配置库空闲页回收
两条都是我上一封里点出、你说继续的问题。
① 聊天记录:每条消息都整段重写 → 节流合并写
原来 persistChatLocked 每次变更就整段重写记录文件,而一轮对话会触发多次
(用户消息、每个工具事件、收尾消息)。200 条上限下文件可达数 MB,单轮就能
放大出几十 MB 写。文件里还留着一个 chatSaveThrottle=3s 常量——声明了但从未
被使用(疑似上次 revert 的遗留),等于节流从来没生效。
现在:persistChatLocked 只置脏 + 唤醒写盘协程;chatPersistLoop 去抖
chatSaveThrottle(3s)、并以 chatSaveMaxDelay(10s) 兜底(持续输出也不会无限拖延);
写盘前把快照拷出来,**不持 chatMu 做文件 IO**;写失败重新标脏下轮重试。
插件 Stop 里调 Handler.Close():停协程 + 强制落最后一次(幂等),否则丢最后一轮。
实测(临时实例,连发 3 条消息):3s 窗口内记录文件**尚未创建**(节流生效);
SIGTERM 后文件出现且 6 条(3 用户 + 3 助手,无 LLM key 故为错误回复)全在
——关停落盘没丢。
② config.db:SQLite 的 DELETE 不缩文件 → 空闲页够多时 VACUUM
新增 ConfigRegistry.MaybeCompact(minFreeBytes, minRatio):空闲页 >= 1MB 且
占页数 >= 25% 才做一次 VACUUM,避免每次启动都重写整库。库里是 WAL 模式,
VACUUM 之后必须再 wal_checkpoint(TRUNCATE),否则主库文件看着没变小。
调用点放在插件加载**之后**(大值的搬走/删除发生在插件 Start 里,之前调没意义)。
实测(一个刚被搬走 5MB 聊天记录的实例):
freelist 1288 页 × 4096B;启动日志「配置库已压缩: 5394432 -> 118784 字节」
config.db 5,394,432 → 118,784 字节;记录文件 5,279,491 字节完好未动。
测试:TestChatPersistenceIsThrottled(节流窗口内不写盘 + Close 必落盘 + Close 幂等)、
TestMaybeCompactReclaimsFreePages(删大值后文件确实变小 + 数据完好 + 阈值不达标时不白做功)。
This commit is contained in:
@ -1246,3 +1246,49 @@ func TestHistoryStoreSaveIsAtomicAndRoundTrips(t *testing.T) {
|
||||
t.Fatalf("回读不一致:%+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatPersistenceIsThrottled 钉住聊天记录写盘节流:
|
||||
// 连续变更不得每条都整段重写文件,但 Close 前必须把最后一次落盘(否则丢对话)。
|
||||
func TestChatPersistenceIsThrottled(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "chat.json")
|
||||
|
||||
cfgReg := internalConfig.NewConfigRegistry("")
|
||||
webuiCfg := cfgReg.PluginConfig("webui")
|
||||
webuiCfg.RegisterDef(internalConfig.ConfigDef{Key: "history_file", Default: ""})
|
||||
webuiCfg.Set("history_file", file)
|
||||
|
||||
s := testSDK(sdk.SDKConfig{
|
||||
Settings: sdk.NewSettings("webui", cfgReg),
|
||||
Config: sdk.NewConfig(&types.Config{}),
|
||||
EventBus: events.NewBus(),
|
||||
})
|
||||
h := NewHandler(s)
|
||||
if h.history.Path() != file {
|
||||
t.Fatalf("history 路径应为 %s,实际 %s", file, h.history.Path())
|
||||
}
|
||||
|
||||
// 模拟一轮对话里的连续变更(用户消息 + 多个工具事件 + 收尾)
|
||||
base := time.Now()
|
||||
for i := 0; i < 20; i++ {
|
||||
h.chatMu.Lock()
|
||||
h.chatHistory = append(h.chatHistory, ChatMsg{
|
||||
Role: "assistant", Content: "消息", Time: base.Add(time.Duration(i) * time.Second).Format(time.RFC3339),
|
||||
})
|
||||
h.persistChatLocked()
|
||||
h.chatMu.Unlock()
|
||||
}
|
||||
// 节流窗口内不应落盘
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Fatal("节流窗口(3s)内不应已经写盘")
|
||||
}
|
||||
|
||||
// Close 必须把最后一次变更落下去
|
||||
h.Close()
|
||||
msgs := newHistoryStore(file).Load()
|
||||
if len(msgs) != 20 {
|
||||
t.Fatalf("Close 后应有 20 条记录,实际 %d", len(msgs))
|
||||
}
|
||||
// Close 幂等
|
||||
h.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user