Skip to content

fix(poller): cap diff-path files per run against the subrequest budget [poller, pipeline, docs, tests] - #240

Merged
smileygames merged 1 commit into
mainfrom
238-cap-per-commit-file-count-in-diff-path
Aug 14, 2026
Merged

fix(poller): cap diff-path files per run against the subrequest budget [poller, pipeline, docs, tests]#240
smileygames merged 1 commit into
mainfrom
238-cap-per-commit-file-count-in-diff-path

Conversation

@smileygames

Copy link
Copy Markdown
Member

目的

diff path に file 数軸の上限が無く、file 数の多い commit が単独で Worker の 1 invocation あたり subrequest 予算(1000)を使い切っていた。#178 の watermark 不変条件により、取り込みに成功していない commit を watermark は追い越さないため、失敗は決定論的に毎 cron 再現し diff surface がそこで恒久停止する。#236 が閉じた token 軸と同じ停止の形。

Closes #238

観測との対応

#237 merge 後の最初の diff 巡回で 3030: Max context reached は消滅したが、watermark は前進しなかった。neuron-graph-rag@92eb94dcb88c5e7494bc3b69377249e08601d99f(44 file)の embed batch 3 本すべてが Too many subrequests by single Worker invocation で拒否されている。

premise の訂正

issue 本文は「subrequest 予算は docs / wiki / issue / release 経路と共有されている」としていたが、これは誤り。handleScheduled は cron 式で dispatch を分けており、diffs は 30 * * * * で専用 invocation を持つ(DIFFS_CRON)。

ただし結論は変わらない。予算は POLL_REPOS の全 repo × 両 phase で共有されており、実際に効いたのはそちらの共有である。上記の観測で、loop の手前にある repo は正常に index され、末尾の neuron-graph-rag だけが枯渇している事実がその形を示している。issue の constraint「diff path が予算をどれだけ使ってよいか を明示する」は、この repo 数軸に対して満たしている。

変更点

予算の明示と導出

  • DIFF_SUBREQUEST_BUDGET_PER_RUN = 900 — diff surface が 1 invocation で使ってよい取り分の宣言(Cloudflare の 1000 のうち)。
  • diffFileBudgetPerPhase(repoCount) — 宣言した取り分を repoCount × 2 で割り、file 数に換算する。1 file の worst case コストは 3 subrequest(D1 FTS mirror、store row、embed batch の按分)。
  • literal を固定せず repo 数から導出したのは、POLL_REPOS が通常の config commit で増えるため(chore(poller): add Liplus-Project/neuron-graph-rag to POLL_REPOS [config] #233 が 6 番目を追加)。その日の list に合わせた literal は次の追加で天井を超え、しかもその超過は原因となった変更ではなく loop が最後に到達した repo の失敗として現れる。今回の観測がまさにその形だった。
  • 下限 5 file を置いた。予算 0 は全 diff watermark を恒久的に止める——いま取り除こうとしている失敗そのものなので、回復可能な超過側に倒す。現行 6 repo で 18 file/phase、14 repo まで 1000 subrequest 以内。

forward / backward の予算分離

forward phase が先に走るため予算を共有すると、forward window が忙しい repo では歴史遡行が永久に進まなくなる。各 phase が独立の予算を持つ。

分割(間引きではない)

  • processAndUpsertCommitDiffmaxFiles / indexedFilePaths を追加、戻り値に deferred / alreadyIndexed を追加。予算を超える file は必ず deferred として報告され、落とさない。
  • 新 status partial。watermark 不変条件の判定は status !== "ok" なので、partialfailed と同じ扱いになり、最後の file が index されるまで watermark は前進しない。bug(diff-sync): failed commits advance watermark and leave permanent gaps #178 の不変条件そのものは変更していない。
  • 再開位置は structured store の GET /diffs から読む。これが無いと上限付きの run が毎 cron 同じ先頭側を再 index して収束しない。file 数が予算を超える commit だけが 1 subrequest を払うので、通常の commit はこの判定に何も払わない。store 側エラーは「まだ何も index されていない」ではなく commit の失敗として扱う(前者は分割を黙って先頭からやり直させる)。
  • log 行に partial を追加し、deferred を backward 側にも出すようにした。partial は分割途中、deferred はその run が到達しなかった commit で、いずれもエラーではない。

範囲外としたもの

webhook 経路(src/webhook.ts)は上限を入れていない。per-push 1 invocation で圧力が異なり、かつ watermark を持たないため、上限を入れると file を落とすことになる。webhook が取りこぼした commit は forward phase が redundancy として拾い、そこでは本 PR の分割が効く。

test

  • src/pipeline/embed-diff.test.tsmaxFiles による分割、再開(indexedFilePaths)、繰り返し呼び出しの収束(deferred=0 に到達すること)、上限なし呼び出し(webhook 経路)の不変性、index 済み commit の再 embed 抑止。
  • src/poller.test.ts — 予算導出(14 repo まで 1000 subrequest 以内 / repo 追加で縮む / 44 file commit に対して使える大きさ / 下限 5 を割らない)、44 file 相当 commit の複数 run 完走と単調前進、分割中の watermark 保持、予算消費後の後続 commit の deferred、予算内 commit が resume query を払わないこと、phase ごとの独立予算、resume query 失敗時の扱い。

npm test(unit 259 / workers 60)、npx tsc --noEmitnode scripts/check-schema-drift.mjsnpx wrangler deploy --dry-run いずれも local で通過。

要件記述

docs/0-requirements.md / docs/0-requirements.ja.md の Diff Pipeline 節と、docs/installation.md / docs/installation.ja.md の追いつき速度の記述を同一 PR で更新。

…t [poller, pipeline, docs, tests]

diff path に file 数軸の上限が無く、file 数の多い commit 単独で Worker の
1 invocation あたり subrequest 予算を使い切っていた。#237 の merge 後も
watermark が前進せず、token 軸のエラーが `Too many subrequests by single
Worker invocation` に置き換わっただけだった観測(neuron-graph-rag@92eb94d、
44 file、embed batch 3 本すべて拒否)に対応する。

変更点:

- `diffFileBudgetPerPhase(repoCount)` を追加。diff surface の予算取り分
  (`DIFF_SUBREQUEST_BUDGET_PER_RUN` = 1000 のうち 900) を `repoCount × 2`
  で割り、file 数に換算する。literal を固定しないのは POLL_REPOS が通常の
  config commit で増えるため(#233 が 6 番目を追加)。下限 5 file を置き、
  予算 0 による恒久停止ではなく回復可能な超過側に倒す。
- forward / backward 両 phase が独立の file 予算を持つ。forward が先に走る
  ため予算を共有すると歴史遡行が永久に進まなくなる。
- `processAndUpsertCommitDiff` に `maxFiles` / `indexedFilePaths` を追加し、
  戻り値に `deferred` / `alreadyIndexed` を追加。間引きではなく分割で、
  未処理 file は必ず報告される。
- 分割中の commit は新 status `partial`。watermark 不変条件の判定は
  `status !== "ok"` なので、最後の file が index されるまで watermark は
  前進しない。#178 の不変条件は変更していない。
- 分割の再開位置は structured store の `GET /diffs` から読む。これが無いと
  上限付きの run が毎 cron 同じ先頭側を再 index して収束しない。予算を超える
  commit だけが 1 subrequest を払う。store 側エラーは「未 index」ではなく
  commit の失敗として扱う。
- 要件記述と installation doc を同一 PR で更新。

webhook 経路は per-push 1 invocation で圧力が異なり、watermark を持たない
ため上限を入れると file を落とすことになる。本 PR の範囲外とし、webhook が
取りこぼした commit は forward phase が redundancy として拾い、そこでは
分割が効く。

test: embed-diff の分割 / 再開 / 収束、poller の予算導出(14 repo まで
1000 subrequest 以内、下限 5 を割らない)、44 file commit の複数 run 完走、
分割中の watermark 保持、phase ごとの独立予算、resume query 失敗時の扱い。

Closes #238
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
github-rag-mcp ef46e86 Aug 14 2026, 07:15 AM

@smileygames smileygames left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI self-review (auto mode, parent)

検証したもの

コードを読んで確認した点。

予算導出の算術 — 6 repo で phases = 12floor(900/12) - 20 = 55floor(55/3) = 18 files/phase。報告値と一致する。上限側も確認した。repo 13 以降は導出値が floor を下回り 5 に張り付く。14 repo で総コスト 28 phase × (5×3 + 20) = 980、Cloudflare の 1000 に収まる。15 repo で 1050 となり超える。「14 repo まで持つ」は正しい。

floor の向き — 0 に落として全 watermark を恒久停止させるより、予算超過(次 cron で再試行される一過性失敗)を選ぶ。この issue が消そうとしている失敗形そのものを floor 不在が再生産するので、向きは正しい。

watermark 不変条件 — 判定が status !== "ok" であり、partialfailed / deferred と同じ側に立つ。#178 の不変条件そのものは書き換わっていない。分割途中の commit が watermark を越えないことをテストが押さえている。

resume の scope(最も危険だった箇所)GET /diffs?repo=...&commit_sha=... が commit 単位で絞られていることを store 側で確認した(src/store.ts L1332-1339、listDiffsByCommit(repo, commitSha))。両パラメータ必須で、欠けたら 400。もし commit_sha が無視される実装だったら、別 commit で同名 path を index 済みの file が「済み」と誤判定され、file が静かに落ちていた。issue の「file を落とさない」制約に直結する箇所だが、正しく絞られている。

収束 — 予算は未 index file に対して効き、resume が済みを除外するので進捗は単調。44 file / 18 = 3 run で完了する。

webhook 経路を無制限のまま残した判断 — 妥当と判断する。webhook は watermark を持たないため cap は deferral ではなく file 欠落になる。取りこぼした commit は forward phase が拾い、そこには分割が効く。データ欠落は生じない。

受け入れ条件

criterion 結果
file 数の多い commit が単独で invocation 予算を使い切らない pass(コード上)。実測は post-merge
部分処理された commit の watermark が前進しない passstatus !== "ok" 判定 + テスト
既存テストが通り、回帰テストが追加されている pass — 259 unit + 60 workers green、新規 26 assertion

指摘なし

前回(#237)は定数の根拠に指摘を返したが、今回は返す点がない。予算を literal ではなく POLL_REPOS 数から導出した判断、phase ごとに予算を分けた判断(forward が先に走るため共有すると backward が恒久的に飢える)、floor の向き、いずれも根拠が明示されていて追える。

premise の誤り(起票側の責)

issue #238 本文に「cron 1 tick は 1 invocation で全 repo・全 surface を処理するため、subrequest 予算は docs / wiki / issue / release 経路と共有されている」と書いたが、これは誤り

wrangler.tomlcrons は 4 系統(0 / 15 comments / 30 diffs / 45 wiki)で、controller.cron で dispatch が分かれる(src/poller.ts L3202-3223)。diff は自前の invocation を持つ。

実際の共有は POLL_REPOS の全 repo × 両 phase の間で起きており、観測とも整合する(loop の先の repo は正常に index され、最後の neuron-graph-rag だけが飢えた)。結論は変わらないが、前提の事実誤認である。

起票時(06:17 UTC)は毎時単一 invocation と誤認していた。その後 06:53 UTC に cron が複数系統であることを発見して Master に報告したが、その事実を open issue の premise に反映しなかった。実装側が読み直して訂正し、PR body に記録している。

next step

auto mode のため human check なし。self-review pass をもって AI が直接 merge する。

merge 後、neuron-graph-rag@92eb94dcb88c5e7494bc3b69377249e08601d99f(44 file)を対象に観測する。予想は forward 3 run で完了し、最後の file が landed した run で watermark が前進。次の diff cron は毎時 30 分。これは #236 の最後の受け入れ条件でもある。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(poller): cap per-commit file count in the diff path against the subrequest budget

1 participant