docs(embedding,memory): 复核 p2 的三处措辞改正(纯注释,零行为改动)
review-m15 round1 判 p2-1items(fix-then-merge),三条都属「把证据能支撑什么写准」: 1. api.go 原写「最坏也只是这一片失败」与实现语义**相反** —— Embed 的实际语义是 「任一片失败 ⇒ 整条调用失败、其余分片已算完的工作量一并丢弃」。改为与该包 budget.go 里已经写对的那一句对齐,并点明调用方据此降级(记忆一条不丢、判定规则 一字不改,变的是这一条审查的召回质量)。 2. 「尖峰与批次无关」是**过度断言**:原证据只支持「至少存在一个与批次无关的外部/ 争用分量」(4 条/片也实测到 7.139s);越界的**频率**是否随批次上升,1/40 vs 2/40 的样本不足以判定 ⇒ 改标为「归因未定」,并明说批上限取 8 的依据始终是硬证据 (16 条/片实测越界 + 真实代码路径 3 次里 2 次越界),**不依赖**任何成因推断 (原文与同文件「16 条越界故不取更大」的理由自相矛盾)。同时补上独立复核窗口的 对照(review-m15:24 次单请求 0 尖峰)说明尖峰强依赖窗口负载。 3. budget.go 的不变式不再被读成延迟保证:明确定性为「**稳态、空闲机器的建模拟合**, 用途是构造批形状」,并点名真正的兜底(90s 审查预算按整条链算 + 任一片失败即降级 的路径 + 已登记的链路韧性遗留);结论句由「8 条/片不越界」改为「**空闲实测下**不 越界、负载下仍偶发越界」;perItemBudget 与 callBudgetShare 注释里「另一半/余量」 的同口径措辞一并改正(加一句:这是建模余量,不是单请求一定不越界的保证)。 另:preinsert_config.go 里「设计稿那一行待同步」已过期 —— 塔同日已把 design/23 §15 同步为 Timeout=90s,改为「已按实测同步」。 验证:go build ./... / go vet ./... 干净;go test -count=1 ./... 全绿; go test -race -count=1 ./internal/runtime/embedding/... ./internal/runtime/memory/... 全绿;diff 中无非注释行(纯文档改动)。
This commit is contained in:
1 parent
f3fb097630
commit
bdf3f3edaf
3 files changed
+45
-26
No files matched your search
@@ -45,9 +45,11 @@ const (
|
||||
// 为什么每次调用必须有自己的上限:上层「入库前审查」的单条总预算是 90s
|
||||
// (memory.defaultReviewTimeout),而**一条召回链是几十个请求**(201 条 ÷ 8 条/片
|
||||
// = 26 个,见 budget.go 文件头的链路实测)—— 一次卡死的 HTTP 不能把整条审查的预算
|
||||
// 都赌掉:把它压在 5s,最坏也只是这一片失败,调用方按**既有语义**降级(审查降级到
|
||||
// 词项余弦并如实留痕 degraded),**绝不静默丢记忆**。本常量只砍等待时间,不改任何
|
||||
// 判定规则。
|
||||
// 都赌掉:把它压在 5s,最坏是**这一片超时**;而 `Embed` 的语义是**任一片失败 ⇒
|
||||
// 整条调用失败**(其余分片已经算完的工作量一并丢弃),调用方据此按**既有语义**降级
|
||||
// (召回降级到词项余弦并如实留痕 degraded)——**记忆一条不丢、判定规则一字不改**,
|
||||
// 变的是这一条审查的召回质量。本常量只砍等待时间,不改任何判定规则。
|
||||
// (同一口径见 budget.go 文件头 ④ 末段。)
|
||||
//
|
||||
// 5s 保持不变的依据:本次故障(2026-10-13)的成因**不是**这个数太小(链条是
|
||||
// 「N 条 → ⌈N/批次⌉ 个请求」,链长超的是**调用方**预算),因此修法是把链条收短
|
||||
@@ -55,11 +57,12 @@ const (
|
||||
// 20s → 90s),而不是把单次超时放大到迁就链条 —— 后者会让真挂死的请求也一起
|
||||
// 拖住整轮审查。
|
||||
//
|
||||
// ⚠️ 已知残余(见 budget.go 文件头 ④):空闲窗口实测单请求最大 1.782s,但**任何**
|
||||
// 批次形状都偶发越过本超时的尖峰(实测 4 条 7.139s、8 条 9.711s —— 明确与批次无关)。
|
||||
// 越过它的后果被设计成**有界的**:那一次调用失败 ⇒ 调用方按既有语义降级
|
||||
// (召回回落词项余弦 + 留痕 degraded),**不是**整轮丢失。真正要治的是链路对单点
|
||||
// 慢分片的韧性(重试/分片级容错),不在这里。
|
||||
// ⚠️ 已知残余(见 budget.go 文件头 ④):空闲窗口实测单请求最大 1.782s,但尖峰会越过
|
||||
// 本超时 —— **至少存在一个与批次无关的外部/争用分量**(实测 4 条/片也能到 7.139s);
|
||||
// 越界的**频率**是否随批次上升,本组样本(1/40 vs 2/40)不足以判定,**归因未定**。
|
||||
// 后果:那一片超时 ⇒ **整条 `Embed` 失败**、其余分片已算完的工作量丢弃 ⇒ 调用方按
|
||||
// 既有语义降级(召回回落词项余弦 + 留痕 degraded)—— 记忆不丢、判定规则不改。
|
||||
// 真正要治的是链路对单点慢分片的韧性(重试/分片级容错),不在这里。
|
||||
//
|
||||
// 冷启动另有 warmupTimeout(见 budget.go),但它**只放宽本进程的第一个请求**、
|
||||
// 且一次性花掉:失败不会让它续期,因此「服务一直挂死」这件事**不会**变成
|
||||
|
||||
@@ -12,8 +12,14 @@ import "time"
|
||||
//
|
||||
// requestOverhead + 一次请求的字符总量 × perCharBudget ≤ DefaultRequestTimeout / callBudgetShare
|
||||
//
|
||||
// 左半是**推演出来的耗时上界**,右半是该超时下允许用掉的一半(另一半是这台机器的
|
||||
// 抖动余量)。批次上限由解这个不等式得到(见 DefaultBatchLimit / DefaultBatchChars)。
|
||||
// 左半是**推演出来的耗时上界**,右半是该超时下允许用掉的一半。⚠️ 这条不等式的**地位**
|
||||
// 必须读准:它是**稳态、空闲机器上的建模拟合**,用途是**构造批形状**(批次上限由解它
|
||||
// 得到,见 DefaultBatchLimit / DefaultBatchChars),**不是**「单次请求 < 超时」的延迟
|
||||
// 保证 —— 文件头 ④ 的实测尖峰(4 条 7.139s / 8 条 9.711s)整段越过 5s,2.5s 的
|
||||
// 「另一半」装不下它。真正的兜底是三件东西、缺一不可:**调用方预算**
|
||||
// (memory.defaultReviewTimeout = 90s,按整条链而不是单片算)、**降级路径**
|
||||
// (任一片失败 ⇒ 调用方回落词项余弦 + 留痕 degraded,记忆不丢、规则不改),以及已登记
|
||||
// 的**链路韧性遗留**(一片失败丢整条 ⇒ 修法是重试/分片级容错,不在这里)。
|
||||
//
|
||||
// ⚠️ 这条不等式**只按稳态超时(DefaultRequestTimeout)推导,且只约束批次形状**;
|
||||
// 冷启动那次放宽(warmupTimeout)**不改批形状**,只放宽进程第一个请求的**截止
|
||||
@@ -53,11 +59,12 @@ import "time"
|
||||
// 4 条/片与 8 条/片的墙钟都是 ~21s。抬高批次上限买到的是**请求数减半**
|
||||
// (51 → 26)与其带来的服务端排队压力下降,**不是**链路变快。要让链路落回
|
||||
// 调用方预算,只有把预算抬到链长之上(审查侧 defaultReviewTimeout)。
|
||||
// - **8 条/片不越界**:空闲窗口里三轮最大的单请求 1.782s,远在 5s 单次超时之内。
|
||||
// - **8 条/片在空闲实测下不越界**:三轮最大单请求 1.782s,远在 5s 单次超时之内。
|
||||
// ⚠️ 但这**不是**延迟保证 —— 负载下仍偶发越界(见 ④),尖峰与批次的关系未定。
|
||||
// - **16 条/片会越界**:同一批正文、同一次会话里复现出单请求 **11.043s**
|
||||
// (那一轮整条链 49.4s)。批上限取 8 而不是 12/16,依据是**实测到的最坏单请求
|
||||
// 本身就出界**,不需要归因。(隔离测量里 16 条三次稳定在 1.35~1.39s ⇒ 这个
|
||||
// 形态方差极大,不能只看隔离值。)
|
||||
// (那一轮整条链 49.4s);③ 里真实代码路径 3 次有 2 次越过 5s。批上限取 8 而不是
|
||||
// 12/16,依据就是这两条**实测**(隔离测量里 16 条三次稳定在 1.35~1.39s ⇒ 这个形态
|
||||
// 方差极大,不能只看隔离值),**不依赖**任何关于尖峰成因的推断。
|
||||
//
|
||||
// ③ **真实代码路径**(不是直连端点:跑 APIEmbedder.Embed(201 条) 本身,用注入的
|
||||
// RoundTripper 数真实 HTTP 请求数):
|
||||
@@ -65,17 +72,23 @@ import "time"
|
||||
// 8 条/片 26 个请求 / 201 个向量,墙钟 21.489s、28.220s(两轮,均未触发单请求超时)
|
||||
// 16 条/片 直接 embedChunk:1.818s、**5.016s 超时**、**5.001s 超时**(3 次里 2 次越界)
|
||||
//
|
||||
// ④ **必须登记的残余(实测,不是推测)**:任何形状下都偶发「单请求越过 5s」,
|
||||
// 与批次**无关** —— 交错对比 40 次 4 条 + 40 次 8 条(同一批正文):
|
||||
// ④ **必须登记的残余(实测,不是推测)**:尖峰会越过 5s 单次超时 —— 交错对比
|
||||
// 40 次 4 条 + 40 次 8 条(同一批正文):
|
||||
//
|
||||
// 4 条/片 p50 0.456s p90 1.008s 最大 **7.139s**(>5s 计 1/40)
|
||||
// 8 条/片 p50 0.848s p90 1.861s 最大 **9.711s**(>5s 计 2/40)
|
||||
//
|
||||
// 该窗口机器上确有别的负载(`tasklist` 可见多个 go/link 进程 = 塔里其它 worker 在
|
||||
// 编译;同窗后端也在跑群聊回合)。归因**未定**,但两件事可以确定:① 4 条/片同样尖峰
|
||||
// (7.139s)⇒ 尖峰**不是**本次放大批次的后果;② 因为「一个分片失败 ⇒ 整条 Embed
|
||||
// 失败」,一次尖峰就丢掉其余分片已算完的工作量 —— 链路对单点慢分片的**韧性**是遗留
|
||||
// 问题,**调常量解决不了**(修法是重试/分片级容错,不在本切片内)。
|
||||
// 编译;同窗后端也在跑群聊回合);同一夜 review-m15 的独立窗口(24 次单请求:n=4
|
||||
// max 0.236s、n=8 max 1.097s)**一次尖峰都没有** ⇒ 尖峰强依赖窗口负载。因此这里只
|
||||
// 断言证据支持得住的那一层:**至少存在一个与批次无关的外部/争用分量**(4 条/片也能
|
||||
// 到 7.139s ⇒ 尖峰**不是**本次放大批次的后果);**越界的频率是否随批次上升,1/40 与
|
||||
// 2/40 的样本不足以判定,归因未定**(方向反而更像「批次越大越容易越界」,但那需要带
|
||||
// 在飞计数的观测才能定论)。
|
||||
//
|
||||
// 另一件确定的事:因为「一个分片失败 ⇒ 整条 Embed 失败」,一次尖峰就丢掉其余分片
|
||||
// 已算完的工作量 —— 链路对单点慢分片的**韧性**是遗留问题,**调常量解决不了**
|
||||
//(修法是重试/分片级容错,不在本切片内)。
|
||||
//
|
||||
// 复跑命令(读活库正文、只连本机 embedding 服务、不写任何库):
|
||||
//
|
||||
@@ -105,16 +118,19 @@ const (
|
||||
// 见文件头)。依据不是「拍一个更小的数」,而是**实测摊薄**:整条链里每条的平均
|
||||
// 成本 70~103ms(8 条/片轮次 0.824s ÷ 8 ≈ 103ms;64 条档最坏 6.872s ÷ 64 ≈
|
||||
// 107ms),300ms 仍是实测的约 **3 倍**。按不变式反解,它把条数上限放到 8:
|
||||
// (5s/2 − 100ms) / 300ms = 8 —— 恰好用满「超时的一半」,另一半=抖动余量。
|
||||
// (5s/2 − 100ms) / 300ms = 8 —— 恰好用满「超时的一半」(另一半按建模拟合留给现场
|
||||
// 抖动;它是**建模口径**、不是延迟保证,见文件头的不变式一节)。
|
||||
//
|
||||
// 为什么不再大:条数上限 12 需要 perItemBudget ≤ 200ms(只剩实测的 1.9 倍),
|
||||
// 16 需要 ≤ 150ms(1.4 倍)—— 而 16 条/片本次已复现出**单请求 11.043s**
|
||||
// (> 单次超时 5s,见文件头)。批次越靠近那条界,单请求越可能「客户端先放弃、
|
||||
// 服务端还在算」(级联堆积的起点)。取 8:**请求数减半,单请求仍留 3 倍余量**。
|
||||
// 服务端还在算」(级联堆积的起点)。取 8:**请求数减半,且建模上界对实测摊销仍留
|
||||
// 约 3 倍**(这是建模余量,不是单请求一定不越界的保证 —— 见文件头 ④)。
|
||||
perItemBudget = 300 * time.Millisecond
|
||||
|
||||
// callBudgetShare 表示一次请求最多用掉超时的 1/2:另一半是抖动余量
|
||||
// (见文件头 16 条/片那轮的数倍放大)。调大它 = 余量变小,必须连带复核实测。
|
||||
// callBudgetShare 表示一次请求最多用掉超时的 1/2。它是**建模口径**(一半给建模拟合、
|
||||
// 一半留给现场抖动),**不是**延迟保证 —— 文件头 ④ 的尖峰整段越过单次超时。调大它
|
||||
// = 建模上界更紧,必须连带复核实测。
|
||||
callBudgetShare = 2
|
||||
|
||||
// DefaultBatchLimit 是单次请求的**条数**上限,由上面三个常量推出来(唯一推演):
|
||||
|
||||
@@ -49,8 +49,8 @@ const (
|
||||
// - 审查超时后的失败形态目前**不写留痕**(preinsert.go 的 `ctx.Err()` 分支,
|
||||
// 不属本次改动范围)⇒ 预算越长,「静默停摆」的窗口越长、面板越看不出来。
|
||||
//
|
||||
// 🔴 与设计稿的差异(如实登记):design/23 §15 那一行写的是 `Timeout=20s`,
|
||||
// 是本次改动**之前**的口径;本值按上面实测抬高,设计稿那一行待同步。
|
||||
// 🔴 与设计稿的对齐(如实登记):design/23 §15 那一行原写 `Timeout=20s`(本次改动
|
||||
// **之前**的口径),已按上面实测同步为 `Timeout=90s`。
|
||||
defaultReviewTimeout = 90 * time.Second
|
||||
// reviewAuditWho / reviewAuditVerb 是审查留痕的审计口径:它是**系统任务**,
|
||||
// 不是任何角色(design §4.1 C 的同一理由)。
|
||||
|
||||
Reference in new issue
Block a user