记忆 GC 详解
wesgine 记忆垃圾回收的淘汰策略、豁免规则与节流调度。
淘汰策略
GC 使用 retentionScore 加权淘汰(INV-MEM-26),不是纯 LRU:
score = timeDecay(accessed_at)
× recallWeight(recall_count)
× trustWeight(trust)
× kindWeight(kind)
分数最低的条目优先被回收。
四个权重因子
timeDecay(时间衰减)
基于 accessed_at(归一后下界为 created_at,INV-MEM-51)。
越久未被召回 → 衰减越大 → 分数越低。
recallWeight(召回频率)
基于 recall_count。
被频繁召回的条目保留分数更高。
trustWeight(信任等级)
| Trust | 权重 |
|---|---|
| TrustSystem | 最高 |
| TrustExplicit | 高 |
| TrustMulti | 中 |
| TrustInferred | 低 |
| TrustUnknown | 最低 |
kindWeight(类型权重)
不同 Kind 有不同的保留倾向。
GC 豁免
以下 Kind 的条目免疫 GC:
| Kind | 原因 |
|---|---|
KindConstraint | 用户显式约束,不可自动删除 |
KindInstruction | 用户显式指令 |
KindEnvironment | 系统环境信息 |
豁免条目仍然计入配额(Quotas.MemoryMaxEntries)。
TTL 批处理
GC 按 TTL 分批回收:
func gcTTLBatch(scope, namespace string, ttl time.Duration, limit int) {
// 单次只回收一批
// 不遍历全量
// 下次调度继续
}
判据:剩下的会不会被再访问一次。
Digest 触发
GC 同时触发 Digest 压缩:
| 触发 | 说明 |
|---|---|
DigestInterval | 定时触发 |
| GC 运行后 | GC 完成后检查是否需要 Digest |
| HTTP 按需 | POST /memory/digest |
Digest 执行:BM25 语义去重、冲突检测、stale 标记、supersession、晋升。
配额联动
QuotaSink 计数规则
| 操作 | 计数 |
|---|---|
| Save 成功 | RecordMemoryDelta(+1) |
| Delete 全部 backend 成功 | RecordMemoryDelta(-1) |
| Delete 任一 backend 失败 | 不计数(INV-MEM-33) |
配额校正
QuotaGC 每 5 分钟校正:
- 读取真实条目数
- 与内存计数对比
- 修正漂移
storeBackoffPersister
写入后重读(幂等 save),防止虚增计数:
- 同值跳过、变更才写
- 空态清理 0 行
"页到空"策略
ClearNamespace / RefreshEnvironment 使用循环至清空:
for {
batch := list(scope, namespace, limit=500)
if len(batch) == 0 {
break
}
for _, entry := range batch {
delete(entry.ID)
}
}
不用 Limit: 0——读"全部"不会真的只读一次(INV-MEM-34)。
相关不变量
- INV-MEM-26:GC 使用 recall-frequency weighted 淘汰
- INV-MEM-33:Delete 失败不计数
- INV-MEM-34:循环至清空
相关文档
- 记忆 GC 配额联动 →
memory-gc-quota.md - 记忆信任等级 →
memory-trust-levels.md - 记忆 Digest 压缩 →
memory-digest.md - 记忆 accessed_at 归一 →
memory-accessed-at-details.md