Skip to content

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

Description

@liplus-lin-lay

目的

/admin/backfill-issue-index の per-call 上限を実測に合わせる。あわせて、予算切れで見送った候補を cursor が追い越す設計を直し、上限値の正確さに結果が依存しない形にする。

確認済みの事実(2026-08-03 本番実行、#210 の backfill 掃引)

limit を変えて全 repo を掃引した結果:

limit 結果
50 1 回目の呼び出しで Too many subrequests by single Worker invocation(呼び出し全体が 500 で失敗)。4 repo すべてで同じ(dipper_ai のみ候補が 50 未満で完走)
25(DEFAULT_INDEX_BACKFILL_LIMIT 全 48 呼び出しが例外なく indexed=24 absent=0 failed=1。ランダムではなく毎回きっちり 1 件、repo をまたいで同一
15 全 8 呼び出しで failed=0

25 で毎回 1 件だけ落ち、15 でゼロになることから、失敗は item の内容ではなく 1 呼び出しあたりの予算で決まっている。

実消費の見積もり(観測境界からの算術)

limit=25 で 24 件成功・25 件目で予算切れ、という境界が repo をまたいで一定だった。1 invocation の予算を 1000、呼び出しごとの固定費(fetchHighestItemNumber 1 + scan chunk の D1 query 数)を O とすると:

24c + O <= 1000 < 25c + O

O を 0〜20 の範囲に置いても c ≈ 40(39.2〜41.7)に収まる。1 候補あたり約 40 subrequest。

コード上の見積もりはこれと合っていない:

  • DEFAULT_INDEX_BACKFILL_LIMIT = 25 のコメント: 1 候補あたり「GitHub fetch + embed + Vectorize + D1 + store fan-out、1000 subrequest のうち ~5
  • MAX_INDEX_BACKFILL_LIMIT = 100 のコメント: 「100 候補 × ~5 ≈ 500、予算の半分」

実装上の論理的な呼び出し数は確かに 6 前後(GitHub fetch 1 / store DO 2 / Workers AI 1 / Vectorize 1 / D1 1)。論理呼び出し 1 回が subrequest 1 個に対応していないことになるが、どの binding が見積もりを超えて消費しているかは未特定。この issue で機構を断定しないこと。既存の MAX_COMMENT_FETCHES_PER_REPO_PER_RUNsrc/poller.ts)のコメントも同種の過小見積もり(~3-5)から実測で下方修正された履歴を持つ。

修正方針

1. 予算切れで見送った候補を cursor が追い越さない(構造側、こちらが本体)

現状は processAndUpsertIssue が内部で embed 失敗を catch して failed を返し、cursor はその候補を追い越す。結果として 1 回の掃引では拾い切れず、実運用では 25 で掃引 → 15 で再掃引の 2 パスが必要だった。

#215 で issue / PR poller の watermark に入れたのと同じ不変条件を持ち込む——取り込みそこねた最初の候補の手前で cursor を止める。 そうすれば次の呼び出しがちょうどその候補から再開し、上限値が多少ずれていても取りこぼしはゼロになる。定数の正確さに結果が依存しない形が要点。

トレードオフは #215 と同じで、恒久的に失敗する候補があると掃引が止まる。ただしこれは人間 / AI が駆動する admin 掃引なので、nextCursor が進まないこととして応答に現れる(cron の暗黙の停止とは違う)。停止時に手動で cursor を進められる余地は残すこと。

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

  • DEFAULT_INDEX_BACKFILL_LIMIT: 15(本番で failed=0 を実測済みの値)
  • MAX_INDEX_BACKFILL_LIMIT: c ≈ 40 から余裕を取った値。100 は到達不能なので必ず下げる。 通らない上限を残すのは、次に使う人(AI 含む)を同じ穴に落とす
  • 根拠コメントは「~5」を別の推定値に差し替えるのではなく、上の観測境界と算術を書く。推定値を推定値で置き換えると同じ誤りになる

制約

  • 本番での再測定はもう取れない。 Index covers only about 45 percent of issue and PR history #210 の掃引で 6 repo すべて索引 100% に到達したため、候補が存在しない。行を消して候補を作るのは論外。よって定数の検証は上記の観測データ(既に取得済み)と unit テストに限られる。この事実を根拠コメントに残すこと
  • 上限 20 は算術上通るが未実測。実測済みは 15 のみ。どちらがどちらかをコメントで区別すること

対象ファイル

  • src/backfill-issue-index.ts(cursor の不変条件、DEFAULT_INDEX_BACKFILL_LIMIT / MAX_INDEX_BACKFILL_LIMIT と根拠コメント)
  • docs/installation.md / docs/installation.ja.md(§15 の limit 記述と掃引手順)
  • docs/0-requirements.md / docs/0-requirements.ja.md(cursor の不変条件を書いている箇所があれば)
  • 回帰テスト

背景

#210 の backfill を本番実行して判明した。索引は 6 repo すべて 100% に到達済みで、修復自体は完了している。本 issue は器具側の精度の問題。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug動いていない、壊れているdone役目完了、orchestration (review / merge / close) フェーズ待ちready本文が実装開始できる形まで収束している状態。ただし更新は継続可能

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions