Skip to content

[PR] fix: defer large-db integrity quick_check past listen so big stores fit the 60 s spawn budget (#4609) - #4619

Closed
ranxianglei wants to merge 12 commits into
masterfrom
2026-10-25_quick-check-off-critical-path
Closed

ranxianglei wants to merge 12 commits into
masterfrom
2026-10-25_quick-check-off-critical-path

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #4609

查重: 不重复 — 与 #4515 同属「启动 I/O 超过启动器硬性 60 s spawn 预算 → 子进程被杀 → 宿主静默直连」后果家族,但机制不同:#4515 是 persist.boot() 解码 legacy JSON 语料(可缓存预热);本 issue 是 0.1.205 引入(#4175)的整库 PRAGMA quick_check,与缓存无关(14 GB > RAM,每次开库全量重扫;0.1.204 同一库 0.75 s 即绑定)。与 #4125(端口占用)、#4516(直连回退缺少 oversized 拒绝,下游面)均不同。

What was wrong

UnifiedStore.open ran PRAGMA quick_check synchronously BEFORE listen, on every open. Time ∝ index.db size: a 14 GB store took 260 s against the launcher's hard 60 s spawn budget (SPAWN_WAIT_MS=20000 + SLOW_START_GRACE_MS=40_000, src/launcher.ts) → child SIGKILLed mid-scan → bootstrap failed → the whole host process lifetime went direct (uncompressed), retrying every ~70 s with identical failure; restarts could not help because the verdict was never cached.

Fix — three-lane decision at open

  1. Skip — main file unchanged since a passing check (size+mtime fingerprint in sidecar index.db.quickcheck): no scan at all. Malformed fingerprint → re-check (fail toward checking).
  2. ≤ 256 MiB — inline check exactly as before (typed StorageIntegrityError throw → shadow quarantine + retry-once; behavior byte-identical); now stamps the fingerprint on pass.
  3. > 256 MiB — the port binds FIRST; the scan runs in a background worker thread with its own readonly second connection (both engines are sync-only APIs — an inline run would just move the 260 s freeze onto live traffic; WAL reader+writer coexistence is supported). Outcomes:

BILI_STORAGE_QUICK_CHECK=0 still skips everything. No new config surface (the config-key question stays open in-thread per Configuration Surface Discipline — not built here).

Split point rationale: field-measured scan rate ≈ 54 MB/s (14 GB / 260 s) → worst-case inline freeze ≈ 5 s at 256 MiB, comfortably inside the budget with margin; below it the pre-#4609 synchronous guarantee (never serve from a corrupt index) is preserved exactly.

Behavior changes (old → new)

Surface Old New
Large-db (>256 MiB) integrity detection timing sync inside open(), before listen — O(db) blocks the bind after listen, background worker; port available immediately
Large-db corruption consequence typed throw → shadow quarantines + retries once within the same boot unified reads off for the process lifetime (envelope fallback) + next-boot quarantine via marker
Repeated opens of an unchanged store full quick_check every time skipped via fingerprint
Files in the storage dir — + index.db.quickcheck (fingerprint), + index.db.quarantine-pending (marker, transient; deliberately extensionless — session-dir *.json walks treat JSON files as session state)
/__bili/status integrity field only checked when a check ran inline also records deferred outcomes (pass/fail)
≤ 256 MiB path inline check + typed throw / quarantine-retry unchanged (plus fingerprint stamp + pass log line)

Config surface

No change: BILI_STORAGE_QUICK_CHECK keeps its meaning (opt out of all checks); no new fields/env vars/CLI flags added.

Tests

tests/issue4609-quick-check-defer.test.ts (9 tests, ~1.2 s) — decision matrix driven through the existing *ForTests seam (production threshold untouched): skip / sync / env-opt-out lanes; deferred pass (open returns before the scan, verdict arrives later); deferred-failure protocol end-to-end (callback fires once → marker → next open quarantines .corrupt-* → fresh db); shadow sink flip (stats.disabled + lastError naming the failure); negative controls both directions — NOTADB file = genuine ok:false verdict vs dead worker = inconclusive null (no marker). Pre-existing #4175 pins stay green: tests/storage-durability.test.ts, tests/storage-full-flip.test.ts (one initial regression of mine there — the fingerprint used to be .json, which session-state dir walks count — fixed by making the sidecar extensionless). Full-suite verdict: CI on the pushed head.

Related

ci-check added 3 commits October 25, 2026 04:32
…ath (#4609)

A 14 GB index.db took 260 s for PRAGMA quick_check inside UnifiedStore.open,
always exceeding the launcher's hard 60 s spawn budget: the child was killed
while still scanning and the host stayed on direct (uncompressed) for its
whole lifetime, with every retry failing identically.

Three-lane decision at open:
- unchanged since a passing check (size+mtime fingerprint in
  index.db.quickcheck) -> skip;
- <= 256 MiB -> inline check exactly as before (typed throw, shadow
  quarantine + retry-once); now stamps the fingerprint on pass;
- > 256 MiB -> bind first, scan in a background worker thread with a
  readonly second connection (both engines are sync-only APIs; running it
  inline would move the freeze onto live traffic). A deferred failure
  disables unified reads for the process lifetime (JSON envelopes remain
  the recovery source), writes an index.db.quarantine-pending marker, and
  the NEXT open quarantines the file set before opening fresh (no live
  handle -> no Windows EBUSY). An inconclusive worker death records
  nothing and retries next open.

BILI_STORAGE_QUICK_CHECK=0 still skips everything. No new config surface.
…rotocol)

- sync lane passes and stamps the fingerprint; unchanged reopen skips
  (no new integrity record); malformed fingerprint re-checks;
  BILI_STORAGE_QUICK_CHECK=0 skips everything
- deferred lane: open returns before the scan, verdict arrives via
  store.deferredQuickCheck; pass stamps cache + status wire
- deferred failure: callback fires once, marker written, next open
  quarantines the file set (.corrupt-*) and opens fresh
- shadow sink: maybeOpen flips stats.disabled with a lastError naming
  the failure, unified reads off, envelopes remain the recovery source
- NOTADB file yields a genuine ok:false verdict (worker statement error),
  while a dead worker stays inconclusive (null, no marker, retry next open)

The suite lowers the 256 MiB split point through the existing *ForTests
seam so fixtures stay small; production threshold is untouched.
…n+zh

The BILI_STORAGE_QUICK_CHECK row now states the actual behavior after
#4609: fingerprint skip, <=256 MiB inline (unchanged failure protocol),
>256 MiB deferred behind listen with the marker + sink quarantine path.
@ranxianglei ranxianglei changed the title fix: defer large-db integrity quick_check past listen so big stores fit the 60 s spawn budget (#4609) [PR] fix: defer large-db integrity quick_check past listen so big stores fit the 60 s spawn budget (#4609) Oct 24, 2026
ci-check added 2 commits October 25, 2026 05:31
…te (#4609)

The WORKER_SOURCE string is bundled VERBATIM into dist/index.js, where
scripts/check-dist-external.mjs text-scans for quoted-require forms. The
literal require("node:sqlite") inside it tripped the gate (node:sqlite is
not in module.builtinModules on Node 22, so it is not skipped as a builtin).
Route the specifier through a named const — the checker documents opaque
specifiers as invisible by design. Same gate class as #3511/#3516/#4008.
)

CONFIGURATION.md / CONFIGURATION.zh-CN.md are generated artifacts of
tools/gen-config-docs.mjs — update the BILI_STORAGE_QUICK_CHECK seed entry
(website/config-reference/env-only.yaml) to the three-lane behavior (skip by
fingerprint / inline <=256 MiB / deferred-after-listen >256 MiB) and
regenerate, keeping the docs-sync gate green.
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-25_quick-check-off-critical-path (d564699)

Latest build — install this exact version

npm install -g billion-context@0.0.6170-pr.4619.df5a24d

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
d564699 billion-context@0.0.6170-pr.4619.df5a24d build
a248a96 billion-context@0.0.6084-pr.4619.14814b3 build
e03244d billion-context@0.0.5987-pr.4619.38ba6e5 build
70367a3 billion-context@0.0.5873-pr.4619.363f5d4 build
ff3a0a5 billion-context@0.0.5806-pr.4619.bc80cc4 build
97f1528 billion-context@0.0.5648-pr.4619.fef4a16 build
dc43e97 billion-context@0.0.5599-pr.4619.47c0718 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr4619.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b)

查重: 不重复(与 PR 正文自查一致,我独立核过):后果家族同 #4515(启动 I/O 超启动器 60 s spawn 预算 → 子进程被杀 → 宿主静默直连),机制不同——本 PR 修的是 #4175 引入的整库 PRAGMA quick_check 在 listen 前同步执行,与 #4515 的 legacy JSON 解码、#4125 端口占用、#4516 直连回退缺 oversized 拒绝均不同根。

标签: bug scope/storage p1(大库用户升级 ≥0.1.205 后 bili 子进程 spawn 即被杀、整个宿主生命周期静默直连 = 核心能力整体停摆,按 owner 2026-10-19 规则属 p1 可用性类)。

CI 快照(head dc43e97,36 个 check-run):除 unused-exports 外全绿。该红是继承性失败,非本 PR 引入:我在 origin/master 树(146b34f27)上本地跑 node scripts/check-unused-exports.mjs,同样报 INTERNAL_ONLY src/agent/client-build-note.ts: ClientBuildCheck(该符号来自已合入 master 的 #4545,本 PR diff 完全不碰 src/agent/)。ci-worker-r1 已被平台转发处理中;按 CI 失败协调政策我在 #4599 留一行记录,不另开 issue。

正在做完整 review(方向/回归/代码/测试/diff)+ 定向测试(issue4609 + storage-durability + storage-full-flip),结构化八问结论随后补上。

@ranxianglei ranxianglei added bug Something isn't working p1 scope/storage labels Oct 24, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

CI 红判定:继承性失败,本分支不动代码,等 #4606 合入后重触发。

本轮收到的是旧 head 6de0545f28 的三红(build-artifact / docs-sync / unused-exports)——前两项已被后续提交 8b70e9133、dc43e973d 修掉,当前 head dc43e97 上 build-artifact、docs-sync 均绿,唯一剩余红是 unused-exports。

结构化八问:

  1. 是什么问题:unused-exports 门禁报 INTERNAL_ONLY src/agent/client-build-note.ts: ClientBuildCheck —— 这个 type 导出了,但全仓零外部引用。
  2. 什么原因导致的:不是本 PR 引入的。该文件由 [PR] dsh lane: note in-session when running an unverified @deepseek-ai/dsh client build (soft, exact-match) #4545 train 带入(dc9db16/0fe7589ef),master 侧 4d34abfaf 的去导出清扫漏了它,master 自身 born-red。证据:master head 146b34f27 的 unused-exports(job 120993296611)与本 PR merge ref 逐字节同一报错行;本分支与 master 对该文件的 git 历史完全一致。
  3. 影响范围与严重程度:每个 rebase 到 tip 的 PR 都继承这条红(CI Control Center: Centralized CI Failure Handling & Status Sync (Comment all CI issues here; do not open new issues) | CI 总控: CI 失败集中处理与状态同步(所有 CI 问题在此评论, 勿开新 issue) #4599 已确认 [PR] fix: persist each committed preflight fold so a dead marathon leaves durable progress (#3978) #4046/[PR] fix(overflow): recognize the DashScope input-length-range 400 so the probe path can arm #4263/[PR] fix: stamp bili.log lines with the system local time | 日志时间改用系统本地时间 #4537 等同症状);叠加另一条 master 常红(LIVE_FILES 漏登记 fix(ci): every test shard is red — tests/issue3890-parent-gone-confirm-drain.test.ts spawns processes but is missing from LIVE_FILES #4604,test 矩阵 11/11 红),当前所有 PR 都拿不到全绿。对本 PR 改动本身无影响。
  4. 怎么修复的:修复已在途——[PR] fix(ci): clear the two master-red gates in one branch — unused-exports (#4602) + the #3890 LIVE_FILES tripwire (#4604) #4606(分支 2026-10-25_unbreak-master-ci-gates,含 fix(ci): master unused-exports is red — ClientBuildCheck is exported but only used inside its own file #4602 去导出 + fix(ci): every test shard is red — tests/issue3890-parent-gone-confirm-drain.test.ts spawns processes but is missing from LIVE_FILES #4604 live-files 登记,[PR] fix(ci): drop the unused export of ClientBuildCheck #4603 已并入它)。[PR] fix(ci): clear the two master-red gates in one branch — unused-exports (#4602) + the #3890 LIVE_FILES tripwire (#4604) #4606 合入后,CI 检查的是 merge ref,会自动带上修复,重触发(或轻 rebase)即绿。本分支无需任何代码变更。
  5. 是否有回归问题:无——未做任何代码改动。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:不涉及。
  8. 是否引入了新的配置:不涉及。

为什么不直接把单行去导出推上本分支:ci.yml 是 fail-fast 分层(if: !failure()),guard 层一红整个 test/static-gates 层直接 skipped——当前 head 的 test 矩阵根本没跑。就算推掉这一行让 unused-exports 转绿,test 层一跑就会撞上 LIVE_FILES 常红(#4604),PR 照样红,只白烧一整轮 CI。按 CI 协调规则(master 红 = 继承性失败,不自行排查修复,记录到 #4599),我已在 #4599 留了一行继承说明。

收工判定:本 PR 代码面健康(其余门禁全绿),红全部来自 master born-red;等 #4606 合入 → 重触发 CI → 预期全绿,无需改动本分支。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

CI 自动修复通道收尾(attempt 2/2):状态未变,维持「继承性失败」判定,本分支不推代码。

复核结果(与上条判定一致):

为什么不推单行去导出:ci.yml fail-fast 分层下,unused-exports 一红整个 test 层被 skip;若单独推掉这一行,test 矩阵随即撞上另一条 master 常红(LIVE_FILES 漏登记,#4604),PR 仍红,只白烧一整轮 CI。两条红都在 #4606 里,只有它合入才能一次转绿。

解除条件:#4606 合入 master → 本 PR 重触发 CI(CI 检查 merge ref,自动带上修复)→ 预期全绿。无需改动本分支,也无需 rebase(base 未动)。#4599 已留继承记录,不再重复。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

PR review 结论(代码已逐段读完,本地定向验证已过)

八问

  1. 是什么问题:Storage Reliability Trio: open-time self-check (quick_check) + online backup (VACUUM INTO) + second-process probe on the | 存储可靠性三件套:开库自检 quick_check + 在线备份 VACUUM INTO + 同库第二进程探测 #4175 引入的开库前整库 PRAGMA quick_check 对大 index.db 是 O(库大小)——14 GB 库内联扫 260 s,超出启动器硬性 60 s spawn 预算,子进程被 SIGKILL 后宿主整个生命周期静默直连、无压缩。
  2. 什么原因导致的:完整性自检必要(Storage Reliability Trio: open-time self-check (quick_check) + online backup (VACUUM INTO) + second-process probe on the | 存储可靠性三件套:开库自检 quick_check + 在线备份 VACUUM INTO + 同库第二进程探测 #4175 耐久三件套),但它坐在 listen 之前的关键路径上,且两个引擎 API 都是同步的,主线程内无法异步化——修的是「检查跑在哪」,不是「要不要检查」。
  3. 影响范围与严重程度:仅 index.db >256 MiB 且走 spawn 路径的用户;症状是整个宿主生命周期直连无压缩(核心能力停摆)→ p1。≤256 MiB 用户字节级不受影响。
  4. 怎么修复的:按「指纹 × 尺寸」三车道分流——自上次通过未变(size+mtime 指纹 sidecar index.db.quickcheck)直接跳过;≤256 MiB 保持原内联行为(含失败时隔离+重试一次协议,逐字一致,仅通过时多写指纹);>256 MiB 端口先绑、独立 worker 线程只读第二连接扫描:通过写指纹;失败则本进程生命周期关闭统一读(回退 JSON 信封)+ 写标记 index.db.quarantine-pending,下次开库在拿句柄之前隔离文件组(规避 Windows EBUSY windows-22-1 lane randomly red: during test cleanup, unlink index.db hit an EBUSY external lock, couldn't survive tmp-rm | windows-22-1 车道随机红:测试清理期 unlink index.db 遇 EBUSY 外部锁,熬不过 tmp-rm 的 9.6 秒重试梯子(#2799/#2908 族复发) #4347);worker 死亡=不确定,下次重试,绝不当损坏处理。
  5. 是否有回归问题:逐项审过既有行为——① ≤256 MiB 同步车道行为保持(失败路径的 typed throw→shadow 隔离→重开协议不变,隔离函数从 shadow.ts 抽到 store.ts 共享,逻辑相同);② BILI_STORAGE_QUICK_CHECK=0 仍跳过一切(有测试钉住);③ stats.disabled 门控在 shadow.ts 13 处核实(读回退信封、ingest 停);④ 新增落盘文件均无扩展名,不落入会话目录 *.json 状态扫描(作者自己修过这个回归,现钉在 storage-full-flip);⑤ /__bili/status integrity 字段记录延迟结果,纯增量。⚠️ 发现一个缺口:src/storage/backend.ts:152 SqliteBackend.open(export/replica/checkpoint 辅助车道,调用方 src/export.ts:394/:410)开库不传 onQuickCheckFailure——大库在该车道遇到延迟失败时只记日志+写标记,不会关掉 live handle 的统一读。PR 前该车道是同步 throw(fail-fast),现在是 fail-late:严格好于旧的 260 s 阻塞,但密封弱于 shadow 车道。与 [Bug] 0.1.205+: open-time quick_check is O(index.db) — 14 GB takes 260 s, so the 60 s spawn budget always kills the child and the host stays on direct | 0.1.205+ 开库 quick_check 使启动变成 O(库大小):14GB 库 260 秒 → 必超 60 秒 spawn 预算 → 整生命周期直连 #4609 同机制族,不单开 issue;建议二选一:PR 内给该车道补 sink,或在 PR 描述里显式记为已知残余。
  6. 是否破坏缓存:不涉及(存储层不参与 wire 前缀缓存/重试/游标/去重键;指纹 sidecar 是新文件,不改任何既有键)。
  7. 是否改了设计方向:无转向。「绝不从损坏索引服务」对小程序集严格保留;大库改为「先服务、后判决、失败即关读+下次隔离」——这是 PR 正文已披露的可用性权衡([Bug/Enhancement] Codex 超过 100 MiB 的请求在 ACP 处理前断开:希望返回明确 413 并支持配置字节预算(#2606 后续) #3128 可用性大于性能)。
  8. 是否引入新配置:无。仅更新既有 BILI_STORAGE_QUICK_CHECK 的描述(en+zh+yaml seed 三处同步,docs-sync 绿);config-key 问题按 PR 正文留在 in-thread 开放,未擅自建面。

测试有效性(④):测试有牙——真实字节翻转损坏(文件中段三区域)+ 自验证前置(assertQuickCheckDetects 先证明损坏确实可检出,防空洞通过);NOTADB 真失败 vs worker 死亡不确定两类各钉一边;shadow sink 走真实 UnifiedShadow.maybeOpen 端到端。本地定向验证:tests/issue4609-quick-check-defer.test.ts 9/9、tests/storage-durability.test.ts+tests/storage-full-flip.test.ts 15/15、typecheck 干净。

CI 状态:head dc43e97 唯一红 = unused-exports,已在干净 master 树复现同样报错(src/agent/client-build-note.ts 的 ClientBuildCheck,#4545 合入带入)——继承性红,非本 PR 代码,#4599 floor 37 已记录,修复载体 #4606(等 owner 合并)或 master 修复后 rebase。注意:ci.yml fail-fast 分层下 guard 红导致本 head 的全部 test/static-gates leg 被跳过,全量套件判定尚未真正跑过,继承红清除后需重跑。

判定:代码质量达标、方向正确、本地定向验证全绿;当前不可合的唯一阻塞是继承性红。建议:先定第 5 问缺口处置(补 sink 或记残余),继承红清除后 CI 转绿即可合。我不关单、不合 PR,等 owner 验收。

ci-check added 3 commits October 26, 2026 14:09
master window commits 293ae65/de5e03659 bumped kernel/package.json to
0.0.165 without regenerating the lockfiles; lock-sync re-resolution then
rewrites the stale recorded versions (root 0.0.163, kernel 0.0.164) and
fails. Pure recorded-version sync via npm install --package-lock-only;
no dependency or semantic change.
@ranxianglei

Copy link
Copy Markdown
Owner Author

方案被后续实现取代:#5002/#4948 + master 既有有界启动工作覆盖了「大库检查拖过 listen」的目标,本 PR 不再适用,冗余关闭。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b) 收到,已知悉本 PR 被 #5002/#4948 + master 既有有界启动工作取代并关闭。CI 侧无遗留事项:此前的 unused-exports 红为 master born-red 继承(修复在 #4606),随 PR 关闭不再需要处理;本分支未推过任何代码,无残留动作。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p1 scope/storage

Projects

None yet

1 participant