性能优化

wesgine 的核心存储基于 SQLite,运行时涉及 LLM API 调用、工具执行、记忆检索等多种 I/O 操作。本文档介绍关键性能调优点、并发控制策略和基准测试方法。


SQLite 调优

三层数据库架构

每个 Cell 维护三个 SQLite 数据库,物理隔离不同类型的数据:

cells/{cellID}/
├── meta.db       # Cell 元数据(小,低频写入)
├── sessions.db   # 会话/消息/记忆(大,高频读写)
└── state.db      # 运行时追踪/观测(大,高频追加)

这种分离确保:

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

关键不变量:

温度调度器

温度调度器控制 Cell 的活跃/休眠状态,避免资源浪费:

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 管理:

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 调用优化

超时配置

组件超时说明
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

最佳实践

  1. 不要关闭 WAL 模式——它是并发性能的基础
  2. 不要启用 mmap——不可靠,已被 INV-RESIL-08 禁止
  3. 不要手动 VACUUM——使用 resilience.SQLiteBackup 做原子快照
  4. 合理设置 MaxConcurrentRuns——过高会导致 Provider 排队
  5. 监控温度分布——过多 Warm Cell 占用 goroutine
  6. 定期检查 WAL 大小——异常增长说明 checkpoint 失败
  7. 使用 --timeout 限制测试时间——防止死锁导致 CI 卡住