perf(embedding,memory): 批上限 4→8 削掉请求放大,审查预算 20s→90s 覆盖实测链长
实测(2026-10-13,本机空闲,活库 cyrene/user 分区 201 条、均长 34.7 汉字): - 一条入库审查召回链 = 51 个「4 条」请求 = 23.070 / 21.860 s;旧审查预算 20s 比链长还短 ⇒ 每一条候选满额的审查都超时,且超时后 **一行留痕都不写** (7 天里 92.1% 的审查 recall_mode 为空、2274 条超时降级的根因) - 同一条链改成「8 条」= 26 个请求 = 21.435 / 21.597 s:请求数减半,墙钟几乎不变 —— 链路耗时由「条数 × 每条成本」主导,批次只改请求数 - 「16 条」在**真实代码路径**上 3 次里 2 次让单请求越过 5s 单次超时(1.818 / 5.016 超时 / 5.001 超时)⇒ 批上限不越过 8;隔离测量里 12 条 0.996~1.017 s 仍在界内 改动: - embedding: perItemBudget 500ms→300ms(实测摊销 70~107ms/条的约 3 倍) ⇒ DefaultBatchLimit 由 (5s/2−100ms)/300ms = **8**,同一套推演、两维都成立 (条数 100ms+8×300ms=2.5s;字符 100ms+480×5ms=2.5s,均在超时的一半内) - embedding: 新增 maxSafeBatchLimit=12(实测外沿)+ 编译期断言(越界即 uint 溢出、 编译失败),与 tidy_config.go 那条同手法 - memory: defaultReviewTimeout 20s→**90s**(> 实测最坏链:空闲 21.6s⇒4.2 倍余量、 与整理链并发 56.7s + T1 8s ⇒ 1.4 倍;不再大的理由=单 worker 有界队列 256 的 最坏积压 256×90s≈6.4h、服务真挂死时第一片 5s 就降级用不到这条预算) 测试(不留过时断言):budget_test.go 加「钉值 8/480 + 两维不等式 + ≤12 外沿 + 16 条必须仍判越界」四层;api_url_test.go 的 reviewBudgetRef 20s→90s; preinsert_recall_test.go 新增跨包断言「预算 > 实测最坏链 + defaultT1Timeout」。 变异自证:perItemBudget 回 500ms ⇒ 钉值断言红;reviewTimeout 回 20s ⇒ 跨包断言红; perItemBudget 150ms(⇒16)⇒ 编译失败(constant -4 overflows uint)。三者均已逐字还原。 遗留(如实登记,未修):① 整理轮每轮重嵌整个分区(user 一轮仍 250 个请求), 解法方向是复用已入库的 vec_items 或给整理轮一个 embedding 请求预算;② 链路对单点 慢分片没有韧性 —— 一个分片失败即整条 Embed 失败、丢掉其余分片的工作量,且实测 任何批次形状都会偶发 >5s 尖峰(4 条 7.139s / 8 条 9.711s,与批次无关),方向是 重试/分片级容错,调常量解决不了。
This commit is contained in:
1 parent
20051f0168
commit
f3fb097630
8 files changed
+280
-53
No files matched your search
@@ -19,7 +19,39 @@ const (
|
||||
// defaultReviewScanLimit 是每次审查在同一分区内最多比对的既有条目数。
|
||||
defaultReviewScanLimit = 200
|
||||
// defaultReviewTimeout 是单条审查的墙钟上限。
|
||||
defaultReviewTimeout = 20 * time.Second
|
||||
//
|
||||
// 🔴 2026-10-13 由 20s 抬到 90s。依据是**实测的链长**,不是感觉:
|
||||
// 单条审查的召回链是「新条 1 + ScanLimit 200 = 201 条文本」,而 embedding 侧
|
||||
// 一次请求只吃 DefaultBatchLimit 条(改前 4、改后 8,见 embedding/budget.go 的
|
||||
// 文件头实测)⇒ 链长 = ⌈201/批次⌉ 个请求:
|
||||
//
|
||||
// 改前(4 条/片)51 个请求:整条链 **23.070 / 21.860 s**(空闲机器,两轮实测)
|
||||
// 改后(8 条/片)26 个请求:整条链 **21.435 / 21.597 s**(同上两轮)
|
||||
// 另有一次**并发整理链**时测到的同一条链 = **56.7 s**(M14 只读勘测报告)
|
||||
//
|
||||
// ⇒ 旧值 20s **比链长还短**:即使机器空闲,一条候选数满额(200)的审查也跑不完
|
||||
// 它的 embedding,于是父 ctx 到期 ⇒ 整条审查失败、**一行留痕都不写**(7 天内
|
||||
// 92.1% 的审查 `recall_mode` 为空,其中超时降级 2274 条,正是这条链的后果)。
|
||||
//
|
||||
// 取 90s 的推导:预算必须 > 链长 + 本层后面的 T1 灰区判定(defaultT1Timeout 8s)。
|
||||
// 空闲实测 21.6s ⇒ 4.2 倍余量;并发实测 56.7s + 8s = 64.7s ⇒ 仍有 1.4 倍余量。
|
||||
//
|
||||
// 为什么**不再更大**(例如 180s / 300s):
|
||||
// - 审查是**异步、单 worker、有界队列**(QueueSize 256,preinsert.go 的
|
||||
// `go r.worker`)。worker 的最坏占用 = 一条审查的预算 ⇒ 队列 256 条的最坏
|
||||
// 积压 = 256 × 90s ≈ **6.4 小时**(180s 就是 12.8 小时)。预算再大,等于把
|
||||
// 「worker 被一条慢审查占住」的时间线性放大。
|
||||
// - 90s 已经是 embedding 单次超时(5s)的 **18 倍**、T1 自身超时(8s)的 11 倍。
|
||||
// embedding 服务**真挂死**时这条预算根本用不到 —— 第一片请求在 5s 就失败,
|
||||
// 召回立刻降级到词项余弦、审查照常跑完(这一点是结构性事实,见
|
||||
// preinsert_recall.go 的降级路径)⇒ 预算只对「慢但在推进」的链有意义。
|
||||
// 那种形态说明 embedding 服务本身退化了,该修的是服务,不是继续等。
|
||||
// - 审查超时后的失败形态目前**不写留痕**(preinsert.go 的 `ctx.Err()` 分支,
|
||||
// 不属本次改动范围)⇒ 预算越长,「静默停摆」的窗口越长、面板越看不出来。
|
||||
//
|
||||
// 🔴 与设计稿的差异(如实登记):design/23 §15 那一行写的是 `Timeout=20s`,
|
||||
// 是本次改动**之前**的口径;本值按上面实测抬高,设计稿那一行待同步。
|
||||
defaultReviewTimeout = 90 * time.Second
|
||||
// reviewAuditWho / reviewAuditVerb 是审查留痕的审计口径:它是**系统任务**,
|
||||
// 不是任何角色(design §4.1 C 的同一理由)。
|
||||
reviewAuditWho = "system/memoryreview"
|
||||
|
||||
Reference in new issue
Block a user