Skip to content

fix(backfill): hold the index cursor at the first uningested candidate [backfill, docs, tests] - #217

Merged
liplus-lin-lay merged 1 commit into
mainfrom
216-backfill-issue-index-per-call-limits-exceed-the-subrequest-budget-at-their-documented-values
Aug 3, 2026
Merged

fix(backfill): hold the index cursor at the first uningested candidate [backfill, docs, tests]#217
liplus-lin-lay merged 1 commit into
mainfrom
216-backfill-issue-index-per-call-limits-exceed-the-subrequest-budget-at-their-documented-values

Conversation

@liplus-lin-lay

Copy link
Copy Markdown
Member

何を直したか

/admin/backfill-issue-index の 2 点。本体は cursor 側(構造)で、定数の実測合わせはその上の器具の精度。

1. 予算切れで見送った候補を cursor が追い越さない

processAndUpsertIssue が embed 失敗を返した候補について、nextCursorその候補の 1 つ手前で止める(#215 で issue / PR poller の watermark に入れたのと同じ不変条件)。候補は番号昇順に処理するので、最初の 1 件を押さえれば以降も全部覆える。

これで limit が実際の subrequest 予算より大きすぎても、代償は 1 回分の無駄な呼び出しであって取りこぼしではない。正しさが定数の正確さに依存しないのがこの変更の要点。#210 の掃引では実際に limit=25 で一周 → limit=15 で再掃引の 2 パスが必要だった。

  • 404(absent)は cursor を止めない。 GitHub 側に無い番号は誰が何回試しても取り込めないので、そこで止めると retry の境界ではなく永久停止になる
  • done は「番号空間の上端に到達」だけでは立たない。未取り込みの候補が残っていれば false
  • トレードオフは fix(poller): hold the issue watermark at the first uningested item [poller, pipeline, index, docs, tests] #215 と同じ(恒久的に失敗する候補があると掃引が止まる)。ただし応答に現れる — nextCursor が渡した cursor と同じ値で返り failed >= 1。手動で越える経路は cursor = nextCursor + 1、run log にも held before #N として出る

2. 定数を実測に合わせた

定数 変更 根拠
DEFAULT_INDEX_BACKFILL_LIMIT 25 → 15 本番で failed=0 を実測した最大値
MAX_INDEX_BACKFILL_LIMIT 100 → 20 c ≈ 40 から算術上の上限 24。20 は算術のみ・未実測

100 は到達不能な上限だった(limit=50 で呼び出し全体が 500 で失敗)。根拠コメントは「~5」という推定値を別の推定値で置き換えるのではなく、observation boundary(limit=25 で 48 呼び出しすべてが indexed=24 failed=1)とそこからの算術をそのまま書いた。論理呼び出し ~6 に対し実測 ~40 という差について、どの binding が消費しているかは未特定のままにしてある(この PR で機構は断定しない)。

本番での再測定は不可能という制約もコメントに残した: #210 の掃引で 6 repo すべて索引 100% に到達したため候補が存在しない。行を消して候補を作るのは論外。よって定数の検証は取得済みの観測データ + unit テストに限られる。

テスト

新規 7 本(cursor 不変条件 6 + 定数 envelope 1)。cursor の hold を戻すと 6 本のうち 4 本が落ちることを確認済み:

  • 失敗候補の 1 つ手前で cursor を止める
  • 未取り込みが残っている間は done を立てない
  • 404 では cursor を止めない
  • 停止が「cursor が動かないこと」として見える
  • cursor = nextCursor + 1 で詰まった番号を越えられる
  • 毎回 3 件目が予算で落ちる状況でも、複数呼び出しで 1..10 が 1 件も落ちずちょうど 1 回ずつ着地する

全体 256 件 green(vitest 198 + workers pool 58)、tsc --noEmit clean。

docs

  • docs/installation.md / .ja.md §15: limit の範囲・既定値・レスポンス例、実測コスト(1 候補 ≈ 40 subrequest)、cursor hold と手動 step-over 手順、absent が cursor を止めない理由
  • docs/0-requirements.md / .ja.md: 欠落修復の節に cursor 不変条件とそのトレードオフ

実機影響

deploy 後の挙動変化は limit の受理範囲(1..20)と既定値、および nextCursor の進み方のみ。索引自体は 6 repo すべて 100% に到達済みなので、この endpoint に現に流れる候補は無い。

Closes #216

🤖 Generated with Claude Code

…e [backfill, docs, tests]

Bring the #215 watermark invariant to the backfill-issue-index sweep and
recalibrate its per-call limits against the 2026-08-03 production measurement.

cursor invariant: the cursor is now held one below the first candidate a call
failed to ingest, so the next call reopens on that exact number instead of
overtaking it. A 404 (absent) is exempt, since nothing will ever ingest those
numbers and holding there would stall the sweep permanently. The stall the
invariant does introduce is visible in the response (nextCursor equal to the
incoming cursor, failed >= 1) and steppable by hand.

constants: DEFAULT 25 -> 15 (largest value measured with failed=0),
MAX 100 -> 20 (arithmetic only, under the c ~= 40 ceiling of 24). The 100
ceiling was unreachable. Rationale comments now carry the observation boundary
and its arithmetic instead of the old ~5 estimate, and record that production
re-measurement is no longer available.

予算切れで見送った候補を cursor が追い越していたため、#210 の掃引では
limit=25 で一周したあと limit=15 で再掃引する 2 パスが必要だった。cursor を
止めることで、上限値が多少ずれていても取りこぼしがゼロになる — 正しさが定数の
正確さに依存しない形にするのが本体で、定数の実測合わせはその上での器具の精度。
どの binding が見積もりを超えて消費しているかは未特定のまま(断定しない)。

Regression tests: 4 of the 6 new cursor tests fail when the hold is reverted.

Closes #216
@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 7b9e119 Aug 03 2026, 07:15 AM

@liplus-lin-lay liplus-lin-lay 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)

受入基準の充足

issue #216 の修正方針 2 点を満たしている。

方針 結果
1. 取り込みそこねた最初の候補の手前で cursor を止める(本体) 充足。retryBoundary ??= number で最初の失敗を捕らえ、nextCursor = retryBoundary - 1donescannedTo >= maxNumber 単独ではなく nextCursor === null になった
2. 定数を実測に合わせ、推定値を推定値で置き換えない 充足。既定 25 → 15(実測 failed=0)、上限 100 → 20。根拠コメントは 48 呼び出しの観測境界と 24c + O <= 1000 < 25c + O の算術を書いており、~5 を別の推定値に差し替えていない

CI green(test / CI / Workers Builds)。docs は同一 PR 内(installation 両言語 §15 + 0-requirements 両言語 + index.ts の endpoint ヘッダ)。

diff 実読で確認した点

  • cursor が後退しない: 候補は from + 1 以上で from の初期値が min(cursor, maxNumber) なので、最小の候補は cursor + 1。したがって retryBoundary - 1 >= cursor が常に成立する。等号がそのまま停止シグナルになる形で、追加の応答フィールドを要していない
  • absent を hold から除外: item === nullcontinueretryBoundary を立てない。issue が唯一の設計分岐として挙げた点で、判断は妥当——404 の番号は永久に取り込めないので、そこで止めると retry の境界ではなく恒久停止になる。liplus-language は実際に 22 件の 404 番号を持つので、除外しなければ本番の掃引が確実に詰まっていた
  • result.embedded が false = 実際の embed 失敗: force: true は hash 一致分岐を飛ばすので必ず embed 経路に入り、返り値は embeddingSucceeded そのもの。hash skip が失敗として数えられる経路は無い
  • dry run の挙動が変わっていない: retryBoundary!dryRun ブロック内でしか立たないので、dry run の nextCursor は従来と同じ
  • limit の検証が定数を参照している: src/index.ts:542MAX_INDEX_BACKFILL_LIMIT を直に見ているので、上限を 20 に下げたことが 400 応答にそのまま反映される。本 session で私が投げた limit=25 / 50 は今後 400 で弾かれる。これが本 issue の狙いどおり
  • cursor 逆行の不在を cursor > maxNumber の端でも確認: scannedTomaxNumber に丸められ候補が空になるので nextCursor = nulldone = true

検証の実効性

新規テスト 7 本のうち 6 本が cursor 不変条件、1 本が定数の envelope。担当が cursor hold を巻き戻すと 6 本中 4 本が落ちることを実走で確認済み。テスト名を読み、停止の可視性makes a stall visible as a cursor that did not move)と手動での踏み越えsteps over a permanently failing number when the caller advances the cursor)の両方が見張られていることを確認した。受容したトレードオフが仕様として固定されている形。

受容したトレードオフ

  • 恒久的に失敗する候補があると掃引が止まる。 #215 の watermark と同じ性質だが、こちらは cron ではなく人間 / AI が駆動する endpoint なので、nextCursor が入力と同値で返ることとして応答に現れる。cursor = nextCursor + 1 で踏み越えられることが docs と実行ログ(held before #N)の両方に書かれている
  • 上限 20 は算術のみで未実測。 実測済みは 15 だけ。この区別がコメント上で明示されている。本番での再測定は不可能(6 repo すべて 100% 到達済みで候補ゼロ、行を消して候補を作るのは論外)で、その事実も根拠コメントに残っている

scope の逸脱

無し。

次のステップ

auto mode のため human gate 無し。self-review pass → merge。deploy 後の運用作業は無い——現在この endpoint を通る候補は存在しない(全 repo 100%)。変更の効果は次に欠落が生じた時に現れる。

@liplus-lin-lay
liplus-lin-lay merged commit 5726608 into main Aug 3, 2026
3 checks passed
@liplus-lin-lay
liplus-lin-lay deleted the 216-backfill-issue-index-per-call-limits-exceed-the-subrequest-budget-at-their-documented-values branch August 3, 2026 07:18
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.

backfill-issue-index per-call limits exceed the subrequest budget at their documented values

1 participant