fix(memory): 冷启动放宽改为一次性 + 试算轮区分「没候选」与「向量不可用」
review round1 的 p1/p2 收口(commit 5967272 之后)。
p1:旧实现 `warmed` 只在 2xx 之后才翻 ⇒ 注释里写的「只在第一次成功响应之前」
实际是「服务坏着的全部时间」。reviewer 实测「接受连接但永不回包」时**每次**调用
都等满 30s(fail-fast 变 fail-slow):审查 20s 预算被一次吃光、索引写入拖成
8×30s、开机探针阻塞 30s。
- `warmupLeft` 改为**一次性许可**(CompareAndSwap 取走即失效,成败都算花掉)
⇒ 挂死时只有第一个请求慢一次,此后一律 DefaultRequestTimeout。
- `warmupTimeout` 30s → 8s:与常态 5s 同一量级(≤2×),且严格小于审查总预算
20s(一次冷启动不许吃光整轮);两条界都由 TestDefaultRequestTimeout 守住。
- 放宽被花掉且没成功时留一条 WARN(事后可复核「第一个请求」这个判据)。
- 更正两处自述:api.go 的「不会被 30s 拖住」与 budget.go 的不变式说明(批次形状
永远按稳态超时推导;冷启动只放宽第一个请求的截止时刻)。
- p2:删掉零调用者的 `WithRequestTimeout` —— 单次超时只允许改常量,批次上限与它
同源并由编译期/常量断言复核。
p2:试算轮在向量源不可用时 `RecallMode` 与「没有候选可比」同为空串。
- `recallLedger` 增 `unavailable` 计数 + `noteUnavailable()`(strictRecall 失败
那一笔记一笔);试算汇总行新增 `recall_unavailable_partitions`,**成败都记**。
- 不扩 `recallMode` 取值集合:失败那一路不序列化 result(响应是
500 tidy_recall_unavailable),新取值只会出现在日志里却要动一次契约,不划算。
测试:三种失败形态 × 两次调用的耗时断言(连接被拒 / 500 / 永不回包),
试算轮的「不可用」与「没候选」两态日志断言;两处变异自证(warmupTimeout 调回
30s ⇒ TestDefaultRequestTimeout 红;去掉 noteUnavailable ⇒ 试算汇总行退化成
recall_mode="" + recall_unavailable_partitions=0)。
This commit is contained in:
1 parent
59672720bd
commit
6c30ca540a
7 files changed
+273
-72
No files matched your search
@@ -76,22 +76,30 @@ func (t *Tidier) DryRun(ctx context.Context) (TidyDryRun, error) {
|
||||
|
||||
out.Stats = stats
|
||||
out.Candidates = collected
|
||||
// 如实上报**实际发生**的来源:空串 = 没有任何分区真正算过相似度(「没候选」,
|
||||
// 与「向量不可用」不是一回事)。
|
||||
// 如实上报**实际发生**的来源:空串 = 没有任何分区真正算过相似度(「没候选」)。
|
||||
// 「尝试过但向量源不可用」是另一件事,由 recall_unavailable_partitions 单独给出
|
||||
// (见 logDryRun 的说明与 recallLedger.unavailable)。
|
||||
out.RecallMode = ledger.label()
|
||||
out.DurationMS = t.now().Sub(started).Milliseconds()
|
||||
// 汇总行**成败都记**(与 RunOnce 同一口径):失败那一路的
|
||||
// `recall_unavailable_partitions` 正是「向量不可用」与「没候选」的唯一区分点,
|
||||
// 只在成功时记就等于把这条口径丢掉。
|
||||
t.logDryRun(out, ledger)
|
||||
if len(failures) > 0 {
|
||||
return out, fmt.Errorf("memory/tidy: dry-run finished with errors: %w", errors.Join(failures...))
|
||||
}
|
||||
t.logDryRun(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// strictRecall 是**试算轮**的召回:语义召回不可用时**不**降级,如实上报。
|
||||
// 日志只记分区与错误,绝不带记忆正文(候选预览里已含正文)。
|
||||
//
|
||||
// 失败时要往 ledger 记一笔 unavailable:否则「尝试过但向量源不可用」与「没有候选
|
||||
// 可比」在观测面上同值(reviewer round1 的 p2)。
|
||||
func (t *Tidier) strictRecall(ctx context.Context, p tidyPartition, items []MemoryItem, stats *TidyStats, budget tidyBudget, ledger *recallLedger) ([]tidyPair, error) {
|
||||
pairs, err := t.recallPairs(ctx, p, items, stats, budget, ledger)
|
||||
if err != nil {
|
||||
ledger.noteUnavailable()
|
||||
t.log.Warn("memory tidy dry-run recall unavailable",
|
||||
"partition", p.label(), "items", len(items), "error", err)
|
||||
return nil, fmt.Errorf("%w: %w", ErrTidyRecallUnavailable, err)
|
||||
@@ -101,7 +109,17 @@ func (t *Tidier) strictRecall(ctx context.Context, p tidyPartition, items []Memo
|
||||
|
||||
// logDryRun 输出试算的观测口径。它带 source(memory/tidy),**不带任何记忆
|
||||
// 正文** —— 只记计数、分区数与截断标签。
|
||||
func (t *Tidier) logDryRun(out TidyDryRun) {
|
||||
//
|
||||
// 🔴 `recall_mode` + `recall_unavailable_partitions` 一起才把三件事分开
|
||||
// (reviewer round1 的 p2:它们曾同值):
|
||||
// - mode=""、unavailable=0:本轮**没有候选可比**(压根没算过相似度);
|
||||
// - mode=""、unavailable>0:**尝试过召回但向量源不可用**(严格口径不降级,
|
||||
// 调用方拿到的是 `ErrTidyRecallUnavailable` ⇒ HTTP 500 `tidy_recall_unavailable`);
|
||||
// - mode=embedding/lexical:真的用该来源算过。
|
||||
//
|
||||
// 没有为此扩 `recallMode` 的取值集合:试算失败时**不序列化** result(响应是错误
|
||||
// 体),一个新取值只会出现在这行日志里,却要动一次 API 契约与文档 —— 不划算。
|
||||
func (t *Tidier) logDryRun(out TidyDryRun, ledger *recallLedger) {
|
||||
t.log.Info("memory tidy dry-run finished",
|
||||
"rule_version", TidyRuleVersion,
|
||||
"partitions", out.Stats.Partitions,
|
||||
@@ -113,6 +131,7 @@ func (t *Tidier) logDryRun(out TidyDryRun) {
|
||||
"veto_reasons", tidyReasonsLabel(out.Stats.VetoReasons),
|
||||
"errors", out.Stats.Errors,
|
||||
"recall_mode", out.RecallMode,
|
||||
"recall_unavailable_partitions", ledger.unavailablePartitions(),
|
||||
"duration_ms", out.DurationMS)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user