性能优化
wesgine 的核心存储基于 SQLite,运行时涉及 LLM API 调用、工具执行、记忆检索等多种 I/O 操作。本文档介绍关键性能调优点、并发控制策略和基准测试方法。
SQLite 调优
三层数据库架构
每个 Cell 维护三个 SQLite 数据库,物理隔离不同类型的数据:
cells/{cellID}/
├── meta.db # Cell 元数据(小,低频写入)
├── sessions.db # 会话/消息/记忆(大,高频读写)
└── state.db # 运行时追踪/观测(大,高频追加)
这种分离确保:
- sessions.db 损坏不影响 state.db(INV-RESIL-03)
- 各层独立的 WAL 日志,减少写冲突
- 可分别优化不同层的 PRAGMA
PRAGMA 配置
wesgine 对每个数据库应用以下 PRAGMA:
-- 必须项
PRAGMA journal_mode = WAL; -- WAL 模式(并发读不阻塞)
PRAGMA mmap_size = 0; -- INV-RESIL-08:禁用 mmap
PRAGMA busy_timeout = 5000; -- 写冲突等待 5 秒
PRAGMA foreign_keys = ON;
-- 推荐项
PRAGMA synchronous = NORMAL; -- WAL 模式下 NORMAL 已足够安全
PRAGMA cache_size = -8000; -- 8MB 页面缓存
PRAGMA temp_store = MEMORY; -- 临时表在内存
禁用 mmap 的原因
PRAGMA mmap_size=0 是强制不变量(INV-RESIL-08)——消除整类 SQLITE_IOERR_MMAP 故障。mmap 在 NFS/CIFS 和某些 Linux 内核版本下不可靠。
VACUUM 策略
VACUUM 禁止自动执行(INV-RESIL-09)——VACUUM 中断 = 全损。只在以下场景使用:
// 先快照再 VACUUM
cell.TriggerSnapshot(ctx)
// 然后通过 VACUUM INTO 做原子副本
resilience.SQLiteBackup(ctx, dbPath, backupPath)
FTS5 索引
消息全文搜索使用 FTS5:
CREATE VIRTUAL TABLE wes_messages_fts USING fts5(
content, content='wes_messages', content_rowid='rowid'
);
FTS5 trigger 必须覆盖 INSERT / UPDATE / DELETE 三种操作(INV-PERSIST-04)。
WAL Checkpoint
WAL 会增长直到被 checkpoint。wesgine 在 Cell Stop 和快照前执行 checkpoint:
PRAGMA wal_checkpoint(TRUNCATE);
注意:强制退出(SIGKILL)会留下未 checkpoint 的 WAL,下次启动会恢复。
并发控制
进程级排他锁
同一 Cell 同一时刻只有一个写入者(INV-RESIL-04):
{CellDataDir}/.cell.lock → flock 排他锁
这使得进程内互斥量是充分的——不需要 SQL 级锁。
Run 并发
每个 Cell 的并发 Run 数受 MaxConcurrentRuns 配额限制:
Quotas: wesgine.CellQuotas{
MaxConcurrentRuns: 3, // 超过返回 429
}
记忆操作并发
记忆 Store 的 touchWriter goroutine 使用 channel + batch flush:
写入 → touchCh channel → touchWriter goroutine → 每 5s batch flush 到 DB
关键不变量:
Store.Close()同步等待 flush 完成- batch 内同一 entry 的多次命中会累加
RecallCount
温度调度器
温度调度器控制 Cell 的活跃/休眠状态,避免资源浪费:
- Hot → Warm:Run 结束时自动转换(秒级)
- Warm → Cool:idle 15 分钟后(释放 goroutine,保留磁盘)
- Cool → Cold:idle N 天后(Stopped 状态)
- Cool → Warm:首请求自动 WarmUp(100-500ms)
Gateway 中间件链
path parse → cell exist → auth → temperature → quota → dispatch
Cool Cell 首请求同步 WarmUp;Cold Cell 返回 503 + Retry-After: 5 并后台唤醒。
内存管理
记忆 GC
记忆子系统有自动 GC 机制:
GCConfig{
MaxEntries: 5000,
TTL: map[ScopeKind]time.Duration{
ScopeAgent: 30 * 24 * time.Hour,
ScopeSession: 7 * 24 * time.Hour,
},
DigestInterval: 6 * time.Hour,
}
GC 使用 recall-frequency weighted 淘汰(RetentionScore),而非纯 LRU(INV-MEM-26)。
上下文窗口管理
长对话的上下文压缩通过 CognitiveSettlement 管理:
- MidRunCompression:Run 中途上下文超过经济窗口时触发
- EndOfRunSettlement:每次 Run 结束时强制执行
- 压缩后旧消息被 SessionState 替代
Workspace 文件 GC
Scratch 目录在 Run 结束后自动清理(INV-IO-06):
os.RemoveAll(scratchDir)
Workspace 支持手动 GC:
POST /cells/{id}/workspace/gc
Embedding 缓存
记忆 Store 维护 LRU embedding 缓存,避免重复计算:
type embedCache struct {
mu sync.Mutex // 注意:不是 RWMutex
cache map[string][]float32
lru *list.List
cap int
}
网络优化
SSE 流式传输
Run 通过 SSE 推送事件,连接断开不取消 Run(INV-RUN-DETACH):
POST /cells/{id}/run → SSE 事件流
GET /cells/{id}/runs/{runID}/events?from_seq=N → 重连续播
SSE 行长上限
行长上限是产品决策:client.MaxEventBytes = 32MB(INV-LINE-01)。
Provider 调用优化
- Sequential Fallback:优先私有 Provider,失败 fallback 到共享池
- Per-Cell Health Tracking:每个 Cell 独立跟踪 Provider 健康状态
- Rate Limiting:per-Cell 速率限制,不影响其他 Cell
超时配置
| 组件 | 超时 | 说明 |
|---|---|---|
| Provider 调用 | 按 Provider 配置 | 通常 30-120 秒 |
| HITL 等待 | CellSpec.HITL.Timeout | 默认 60 秒 |
| Exec 前台 | ctx 超时 | KillProcessGroup 清理 |
| Exec stdout 排空 | 5 秒 | 超时强制 CloseOutput |
基准测试
冷启动时间
| 场景 | 预期时间 | 说明 |
|---|---|---|
| Hypervisor Start | < 100ms | 零 I/O、零 goroutine |
| Cell Create(空) | < 500ms | 创建 DB + schema |
| Cell WarmUp(Cool → Warm) | 100-500ms | 打开 DB + 恢复状态 |
| Cell Activate(Cold → Hot) | 1-3s | 完整启动 |
运行时性能
# 使用 wescode bench 评测
bin/wescode bench --dataset ./tests/bench/dataset --runs 1 --verbose
# 单题调试
bin/wescode bench --case swe-001 --runs 1 --verbose
SQLite 性能基线
| 操作 | 预期 QPS | 条件 |
|---|---|---|
| 记忆读取(by ID) | 10,000+ | 单行查询 |
| 记忆列表(带过滤) | 1,000+ | 分页查询 |
| 记忆写入 | 5,000+ | WAL 模式 |
| FTS5 搜索 | 500+ | 全文检索 |
| 消息插入 | 5,000+ | 含 FTS trigger |
压测方法
func BenchmarkMemoryList(b *testing.B) {
store := setupTestStore(b)
// 预填充 10000 条记忆
for i := 0; i < 10000; i++ {
store.Save(ctx, testEntry(i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
store.List(ctx, ListOptions{
Layer: "about_me",
Limit: 50,
})
}
}
运行:
go test -bench=BenchmarkMemoryList -benchmem ./internal/memory/...
诊断工具
Cell 配置快照
GET /cells/{id}/config/snapshot
返回当前 Cell 的完整运行时配置,包含所有 PRAGMA、缓存大小、配额等。
Token 用量分析
GET /cells/{id}/observe/token-usage?group_by=model&days=7
GET /cells/{id}/observe/token-summary
GET /cells/{id}/observe/token-usage/aggregate?agent=legal-agent&group_by=day
性能剖析
# Go pprof
go tool pprof http://localhost:9091/debug/pprof/profile?seconds=30
go tool pprof http://localhost:9091/debug/pprof/heap
go tool pprof http://localhost:9091/debug/pprof/goroutine
最佳实践
- 不要关闭 WAL 模式——它是并发性能的基础
- 不要启用 mmap——不可靠,已被 INV-RESIL-08 禁止
- 不要手动 VACUUM——使用
resilience.SQLiteBackup做原子快照 - 合理设置 MaxConcurrentRuns——过高会导致 Provider 排队
- 监控温度分布——过多 Warm Cell 占用 goroutine
- 定期检查 WAL 大小——异常增长说明 checkpoint 失败
- 使用
--timeout限制测试时间——防止死锁导致 CI 卡住