记忆 accessed_at 归一化
wesgine 记忆系统中 accessed_at 时间戳的零值陷阱与归一化设计。
零值问题
accessed_at 列定义为 NOT NULL DEFAULT 0。
零值(1970-01-01)不是中性状态——它是该字段最坏的取值:
| 影响 | 说明 |
|---|---|
GC retentionScore | 衰减到 ~0,最先被淘汰 |
| Hybrid ranker | 压到底部 |
| Digest | 读作 stale |
ORDER BY accessed_at DESC | 排到末尾 |
最坏的取值落在最新写入的行上。
曾经的缺陷
六个读者各自带一份补偿:
if AccessedAt.IsZero() { use CreatedAt }
第七个(context_index.go 的 ORDER BY)没带。
同一行在五处读作创建时间、在一处读作 epoch。
归一化设计(INV-MEM-51)
归一在 saveCore 一处,读者不补偿:
saveCore 落盘前:
if entry.AccessedAt.IsZero() {
entry.AccessedAt = entry.CreatedAt
}
存量修复:ddlMemoriesV10 幂等更新。
为什么不用 DEFAULT CURRENT_TIMESTAMP
SQLite 的 DEFAULT 在 INSERT 时生效,Go 的 ORM 通常显式赋值。
即使改了默认值,Go 代码传 time.Time{} 仍会写入零值。
所以归一点必须在应用层(saveCore),不在 DDL。
第二排序键
accessed_at 并列时用 created_at 打破平局。
这不是整洁性:List 排序后切 Offset/Limit,并列时不确定的顺序移动的是页边界。
相关不变量
- INV-MEM-51:
accessed_at下界是created_at - 归一只在
saveCore一处 - 读者不补偿
相关文档
- 记忆写入管线 →
memory-save-pipeline.md - 记忆 GC 详情 →
memory-gc-details.md