fix(docs): tear down doc reap surfaces independently and cap it per run [poller, docs, tests] - #205
Conversation
…cs, tests] The repository-docs reap tore down three surfaces (Vectorize, D1 FTS5, store row) while the wiki reap tore down four, and it walked the whole deleted set with no per-run budget. Both gaps are closed by adopting the wiki reap's shape. - deleteEdgesForVector is now called for every reaped doc, and the four surfaces are torn down independently so a Vectorize failure no longer strands the D1 rows users retrieve. - MAX_DOC_DELETIONS_PER_REPO_PER_RUN (5) caps the reap. At ~4 subrequests per deletion an unbounded loop over a mass deletion could exhaust the light cron's invocation budget alone; a single PR removing 66 .md files puts all of them on one run. - The tree ETag is held back while deletions are outstanding. Without this the next run answers 304 and returns before it looks at the leftover, so the drain would stall until the tree changed again. Same hold the fetch cap already used; the two branches are now one write. pollDocs is exported for the new test file. edge 削除は溜まったものを流すためではなく、wiki 側との対称性のために入れて いる。doc_edges の端点は現状どちらも wiki vector ID なので doc vector ID は 1 行も一致せず、この DELETE は no-op になる。issue 本文の「edge table に溜ま り続ける」という記述はこの点で実測と食い違っており、PR 本文に訂正を書いた。 効果は将来 doc 側の edge writer が入ったときに「vector を削除する」の意味が 2 経路で同じであり続けること。 Refs #203
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
github-rag-mcp | 7533ae8 | Aug 02 2026, 12:21 PM |
…cs, tests] deleteEdgesForVector on a doc vector ID matches zero rows: indexWikiEdges is the only doc_edges writer, its src id is the page's wvid and the dst id it computes is a wikiDocVectorId too, and the backfill in index.ts filters WHERE type = 'wiki_doc'. A doc vector id cannot be an endpoint. The call is removed. Justifying it as symmetry against a future doc-edge writer was a push-surplus argument for a statement with no behavior behind it. With the premise gone the asymmetry is not a defect either: wiki pages have edges and repository docs do not, so the two reaps differing is the data differing, not the handling. The knowledge is kept as a comment on the delete loop, naming the invariant and what to do if it changes. Per-deletion fan-out is 3 subrequests, not 4; the budget arithmetic in the constant's doc comment is corrected to match. Unchanged from the prior commit, all three load-bearing on real defects: per-surface independent teardown, MAX_DOC_DELETIONS_PER_REPO_PER_RUN, and the ETag hold that keeps the drain moving once the cap exists. Tests drop the edge assertions and pin those three instead, plus a new case fixing that an FTS5 failure still reaches the store DELETE. 親側の裁定による。subtractive-structural-beauty の (A)(B) 適用で、0 行にしか マッチしない DELETE は behavior に load-bearing でないため削除。知識だけ コメントとして残す形に寄せた。 Refs #203
liplus-lin-lay
left a comment
There was a problem hiding this comment.
Self-review (auto mode, parent agent)
前提の訂正を経た PR であることの記録
初回実装は issue #203 の当初本文どおり deleteEdgesForVector を doc 削除ループへ足していた。実装者がその過程で「該当する edge は存在しない」と報告し、親側で独立に確認して裁定した。
確認内容: doc_edges の writer は indexWikiEdges の 1 経路のみ。src_vector_id は wiki ページの wvid、dst_vector_id は wikiDocVectorId(repo, slug)。src/index.ts の backfill も WHERE type = 'wiki_doc' で絞る。したがって d: prefix の doc vector ID は端点になり得ない。
issue #203 の当初の「欠け 1」は私が書いた前提の誤りだった。 wiki と doc の扱いの差は defect ではなく、データの違いに扱いが対応していただけ。issue 本文とタイトルは訂正済み。
no-op の DELETE を落とした根拠は rules/model/subtractive-structural-beauty.md の (A) と (B) — 0 行にしかマッチしない文は behavior に load-bearing でなく、「将来 doc が端点になったら」という正当化は Detection signs が名指しする push surplus の形。不変条件はコメントと docs に残し、情報だけ保存して dead な文を落とす形にした。
受け入れ条件
- per-run 上限があり、超過分が次 run に持ち越される — 満たす。
MAX_DOC_DELETIONS_PER_REPO_PER_RUN = 5。11 件を 5-5-1 の 3 run で重複も取りこぼしも無く drain することがテストで固定 - backlog 中は tree ETag が前進しない — 満たす。fetch 枠が既に持っていた据え置きを削除枠へ広げ、分岐 2 本を 1 本に畳んでいる。cap を入れた以上これが無いと 304 短絡で drain が止まるので、持ち越しの成立そのものを担保する条件
- Vectorize 失敗でも FTS5 / store の削除が実行される — 満たす。surface ごとの独立 teardown に変更。従来は外側 try が Vectorize を含み、落ちると D1 側が両方残る構造だった
- wiki 側の既存テストが無変更で pass — 満たす
数値の整合
fan-out が 4 → 3 subrequest になったのに伴い、定数 doc comment の予算計算(5 × 3 × 5 = 75、LIGHT_CRON worst case 約 825)と docs / テストのコメントが実数に揃えられている。撤回した edge 呼び出しの分を計算に残していない点を確認。
テストの寄せ直し
baseline(export だけ足した修正前コード)に対する測り直しで、挙動 3 本 fail / control 4 本 pass。維持した 3 点それぞれに fail するテストが 1 本ずつ対応しているので、回帰を捕まえる側と不変条件を固定する側の分離が保たれている。control が 1 本増えている(FTS5 失敗時に store DELETE へ到達)のは独立 teardown の裏側の固定で、妥当。
scope
PR タイトルは squash commit タイトルになるため、実態(edge を含まない)に合わせて実装者が変更済み。本文にも撤回理由が残っている。派生 issue #206 の「edge も入れるか判断」という記述も、撤回した論拠が次の実装へ伝播しないよう「対象外」へ書き換えられている。撤回の波及先まで潰されている点は評価する。
brake
user repo の PR で Li+ source を触っていないため brake 1 / brake 2 の対象外。
次のステップ
auto mode につき human gate なし。self-review pass → merge。
Closes #203
何をしたか
doc 削除ループの実在の欠陥 2 つを直した。
1. surface ごとの独立 teardown
従来は 1 件を 1 本の外側 try で包んでいたため、Vectorize が落ちると store 行が残る。FTS5 だけは内側 try で守られていたので、**Vectorize 失敗時に「FTS5 だけ消えて store 行が残る」**という中途半端な状態になる。wiki reap と同じく surface ごとに独立して teardown する形に変更した(Vectorize / D1 FTS5 / store row の 3 面)。
2. per-run cap
MAX_DOC_DELETIONS_PER_REPO_PER_RUN(既定 5)を追加。1 件あたり 3 subrequest なので、5削除 × 3 × 5repo = 75。docs surface の既存 fetch 枠(約 250)に上乗せしても LIGHT_CRON worst case は 825 程度で 1000 の内側。cap 追加に伴って ETag の扱いも直した。 cap で残した削除は、次 run で ETag が一致して 304 になると
deletedDocsを見る前に return してしまい、tree がたまたま変わるまで drain が止まる。fetch 枠が既に持っていた「枠切れなら旧 ETag を据え置く」保持を削除枠にも広げた。分岐が 2 本あった watermark 書き込みは 1 本に畳んだ。cap を入れた以上これが無いと成立しない、対の変更。issue 本文「欠け 1」の訂正 — graph edge の teardown は入れていない
issue は「削除された doc の graph edge が edge table に残り続ける」としていたが、そのような行は存在しない。
doc_edgesへの writer はindexWikiEdgesの 1 経路のみ(src/pipeline/embed-doc.ts:199、wiki 分岐の中)wvid、dst 側もextractMentionEdgesがwikiDocVectorIdで算出(src/graph.ts:70)knownWikiSlugsがWHERE type = 'wiki_doc'で絞る(src/graph.ts:85)src/index.tsの backfill も同じ条件で絞るdocs/0-requirements.mdの Graph Index 節も "src/dst are deterministic wiki vector IDs" と明記d:prefix の doc vector ID は端点になりえないので、deleteEdgesForVectorの呼び出しは 0 行にしかマッチしない。入れていない。 初版では「将来 doc 側の edge writer が入ったときのための対称性」として入れていたが、これはrules/model/subtractive-structural-beauty.mdの Detection signs が名指しする push surplus の形そのもので、裁定により撤回した。前提が崩れた以上「非対称」も defect ではない — wiki ページには edge があり repo doc には無い、というデータの違いに対して扱いが違うのは正しい。知識は残した: 削除ループのコメントに「
doc_edgesの端点は両側とも wiki vector ID であり doc vector ID は該当しない(indexWikiEdgesが唯一の writer)。この不変条件が変わったらここに teardown を足すこと」を置いた。docs の Graph Index 節にも同じ 1 文を追記。テスト
src/poller-docs.test.tsを新規追加(src/poller-wiki.test.tsと同じ mock 構成)。テストのためpollDocsを export した。挙動 3 本(修正前コードに対して fail することを実測確認済み):
If-None-Match無しで tree を取り直すことcontrol 4 本(修正前コードでも pass、境界の固定):
docVectorIdにより teardown され、残る doc は無傷であること全 178 件 green、
tsc --noEmitclean。変更していないもの
currentPaths差分)poller-wiki.test.ts無変更で pass)別軸として残したもの
webhook 経路の doc 削除(
src/webhook.ts:490-515)に、今回直した「外側 try 1 本」の構造がそのまま残っている。webhook は push の一次経路で cron はその fallback なので実際に多く通るのはこちら。issue の想定変更箇所がsrc/poller.ts限定だったため #206 に分離した(edge 追加は不要である旨も明記済み)。