记忆 accessed_at 归一化
wesgine 记忆系统中 accessed_at 字段的归一化设计,确保零值不会导致 GC 和排序异常。
零值问题
accessed_at 列定义为 NOT NULL DEFAULT 0,Go 零值映射到 1970-01-01T00:00:00Z。然而这个字段决定了:
| 消费者 | 零值影响 |
|---|---|
GC retentionScore | 时间衰减接近 0,优先被淘汰 |
| Hybrid Ranker | 排序权重压到最低 |
Digest stale 判定 | 被读作"30+ 天未被召回"→ 标记 stale |
两处 ORDER BY accessed_at DESC | 排到查询末尾 |
零值不是中性状态,是该字段最坏的取值,且恰好落在最新写入的行上。
历史补偿
此前有 6 个读者各自带一份补偿逻辑:
if entry.AccessedAt.IsZero() {
use entry.CreatedAt
}
而第 7 个消费者(context_index.go 的 ORDER BY)没有补偿——同一行在 5 处读作创建时间,在 1 处读作 epoch,不报错,只是在某一个面上永久排最后。
当前设计:归一一处,读者不补偿
saveCore 在落盘前执行归一:
if entry.AccessedAt.IsZero() {
entry.AccessedAt = entry.CreatedAt
}
同时 ddlMemoriesV10 幂等修存量:
UPDATE wes_memories
SET accessed_at = created_at
WHERE accessed_at = 0;
缺任何一条,删掉那六份补偿就不安全。
为什么不用 DEFAULT CURRENT_TIMESTAMP
这会让所有未被召回的行的 accessed_at = 写入时间,混淆"刚写入"与"刚被召回"两种语义。created_at 是更精确的下界。
第二排序键
accessed_at 相同时用 created_at 作为 tiebreaker:
ORDER BY accessed_at DESC, created_at DESC, id
并列时不确定的顺序移动的是页边界——同一查询的返回行集随谁先入列而变。
不变量
INV-MEM-51:accessed_at 的下界是 created_at,归一只在 saveCore 一处。
相关文档
- 记忆写入管线 →
memory-save-pipeline.md - 记忆 GC →
memory-gc-details.md - 记忆 Lazy Recall →
memory-lazy-recall.md