Skip to content

fix(poller): hold the issue watermark at the first uningested item [poller, pipeline, index, docs, tests] - #215

Merged
liplus-lin-lay merged 1 commit into
mainfrom
210-index-covers-only-about-45-percent-of-issue-and-pr-history
Aug 3, 2026
Merged

fix(poller): hold the issue watermark at the first uningested item [poller, pipeline, index, docs, tests]#215
liplus-lin-lay merged 1 commit into
mainfrom
210-index-covers-only-about-45-percent-of-issue-and-pr-history

Conversation

@liplus-lin-lay

Copy link
Copy Markdown
Member

何を直したか

pollRepo は 1 run で最大 200 件(MAX_PAGES_PER_RUN × 100)を fetch する一方、embed するのは最大 50 件(MAX_EMBEDDINGS_PER_RUN)。残りは空の bodyHash を retry の印として保存していたが、watermark はその印を追い越して進んでいた — 完走時は poll 開始時刻、pagination 打ち切り時は最後に fetch した項目の updated_at。batch は updated 昇順なので、どちらの分岐も見送った項目より必ず新しい位置に着地する。結果、印は付くが印の付いた項目が二度と fetch されない。

2026-08-03 の実測で、索引対象 repository の issue / PR 履歴の約 55%(1239 件)が search_docs に無かった。欠落が連続した番号帯でなく散発に見えるのは、脱落が番号順ではなく updated_at 順に起きるため。

commit diff surface で #178 / #179 が既に潰した構造と同型で、issue / PR surface には適用されていなかった。

変更

恒久修正 — src/poller.ts

  • processIssues が「retrieval surface に載せられなかった最初の項目」(embedding 予算で見送り / embed 失敗)の updated_at を retry boundary として返す。batch は昇順なので、最初の 1 件を押さえれば以降も全部覆える。
  • nextIssueWatermark がその 1 秒手前に watermark を留める。margin と never-regress の形は nextForwardDiffWatermark と同型 — /issuessince は "updated after" と規定されているので、境界の項目自身が次 window から落ちうるため。
  • ETag も同じ条件で書き戻さない。 残作業がある状態で ETag を保存すると、次 run の条件付き request が 304 を返して残りを見る前に return する(docs poller が tree ETag を保持するのと同じ hold)。

backfill — src/backfill-issue-index.ts + src/index.ts

POST /admin/backfill-issue-index?repo=owner/repo[&dry_run=true][&limit=N][&cursor=N]

  • repo の issue 番号空間を走査し、search_docs に issue / PR 行が無い番号を取り込む。番号空間は密かつ有界なので、数値 cursor が「どこまで走査したか」を厳密に表せる。時刻 cursor は使わない — 欠陥が突いた順序をそのまま持ち込むことになるため。
  • 1 call の予算は limit(既定 25 / 上限 100)で、これは subrequest 予算であると同時に Workers AI の予算。dry_run=true は予算を使わず欠落量だけを測る(1 call 最大 5000 番)。
  • GitHub 側に無い番号(削除 / transfer)は 404 → absent に計上して retry しない。

src/pipeline/embed-issue.ts

force option を追加(backfill だけが指定)。候補はいずれも retrieval surface が欠けていると分かっている項目で、hash 一致(embed 成功後に FTS5 mirror が失敗した行がこの状態)だと恒久的に skip されてしまうため、body-hash 判定を飛ばす。

テスト

回帰テスト 4 本は、修正を戻すと 4 本とも落ちることを確認済み:

  • 予算で見送った項目が次 run の window に残る(60 件 vs 予算 50)
  • 残作業がある間 ETag を書き戻さない
  • embed 失敗した項目の手前で watermark が止まる
  • 予算より大きい backlog(130 件)が 3 run で 1 件も落とさず drain する

加えて nextIssueWatermark の単体 5 本と、backfill module の 17 本。全体 249 件 green、tsc --noEmit clean。

実行順序

恒久修正の deploy → backfill の実行、の順。逆にすると backfill 後に入る新規項目が同じ経路でまた落ちる。backfill の実行は merge + deploy 後に parent が行う(Master の go-sign は 2026-08-03 取得済み)。

release surface の別種の穴は #211 に分離済み。#209(索引済み行の state 取り残し)とは独立。

Closes #210

…oller, pipeline, index, docs, tests] (#210)

pollRepo fetches up to 200 items per run (MAX_PAGES_PER_RUN x 100) and embeds at
most 50 (MAX_EMBEDDINGS_PER_RUN). The rest were stored with an empty bodyHash as
a retry marker — and then the watermark advanced past them anyway, to the poll
start time, or to the last fetched item's updated_at when pagination capped. The
batch is sorted `updated` ascending, so both bounds sit above every deferred
item: the marker was set and the marked item was never fetched again. Measured
2026-08-03, about 55% of the issue and PR history of the indexed repositories was
absent from search_docs, scattered rather than in contiguous ranges because the
loss follows updated_at order, not number order.

Same defect the commit-diff surface had (#178 / #179), on a surface the pin was
never applied to.

- src/poller.ts: processIssues reports the first item it left off the retrieval
  surfaces (budget-deferred or embed-failed) as a retry boundary, and
  nextIssueWatermark pins the watermark one second before it — the same margin
  and never-regress shape as nextForwardDiffWatermark, since /issues documents
  `since` as "updated after" and would otherwise drop the boundary item. The
  response ETag is withheld on the same condition: storing it would make the next
  conditional request answer 304 and return before it saw the leftover.
- src/backfill-issue-index.ts + src/index.ts: POST /admin/backfill-issue-index,
  a resumable per-repo sweep over the issue-number space that ingests the numbers
  with no search_docs issue/PR row. Numeric cursor, because a timestamp cursor
  over the same set reintroduces the ordering the defect exploited. dry_run
  measures the gap without spending the embed budget.
- src/pipeline/embed-issue.ts: `force` option, set only by the backfill. Every
  candidate there is known to be missing a retrieval surface, and a matching hash
  — which an embed whose FTS5 mirror failed leaves behind — would otherwise skip
  it permanently.

恒久修正と backfill を同じ PR に置いたのは、watermark を直しても既存の欠落は
自力で埋まらないため。取り残された項目が再 fetch されるのは updated_at が動いた
ときだけで、閉じた履歴はもう動かない。実行順序は逆にできない: backfill を先に
走らせると、その後に入る新規項目が同じ経路でまた落ちる。release surface の別種の
穴は #211 に分離してある。

Closes #210

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@liplus-lin-lay liplus-lin-lay linked an issue Aug 3, 2026 that may be closed by this pull request
@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 17542df Aug 03 2026, 03:22 AM

@liplus-lin-lay liplus-lin-lay self-assigned this Aug 3, 2026

@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 #210 の修正方針 2 点を満たしている。

方針 結果
恒久: 見送った最初の item の手前で watermark を止める(#179 の pin と同型、since の inclusive 境界に対する backoff margin 込み) 充足。processIssuesretryBoundary(embedding budget 見送り / embed 失敗の最初の item の updated_at)を返し、nextIssueWatermarkISSUE_RETRY_BOUNDARY_BACKOFF_MS = 1000 手前へ pin。never-regress と unparseable-date-holds も nextForwardDiffWatermark と同じ形
backfill: repo 指定・cursor・件数上限を取る admin endpoint 充足。POST /admin/backfill-issue-index。番号空間を歩く(timestamp cursor だと欠陥が突いた順序を持ち込むため)。limit 1..100 既定 25

CI green(3 checks)。docs は同一 PR 内(0-requirements 両言語 + installation 両言語 §15)。

diff 実読で確認した点

  • ETag の穴(issue 本文に無い、担当が見つけた 2 つ目の欠陥): watermark を止めても ETag を保存すると次 run が 304 で早期 return し、止めた意味が消える。holdEtag = capped || retryBoundary !== undefined で塞いである。この修正が実効を持つことを store 側から裏取りした——setWatermarketag ?? "" で書き、getWatermarketag || undefined で返すので、withhold は「前の値を残す」ではなくクリアになる。次 run は If-None-Match を送らず full fetch する。この確認が取れなければ修正は空振りだった
  • cursor の正確性: stopAt = n は n を candidates に入れたに立ち、nextCursor = scannedTo = n、次回は number > n。取りこぼしも二重処理も無い
  • force の適用範囲: hash 一致でも embed する経路は backfill だけが立てる。poll / webhook 経路は hash check を保つので全件再 embed にはならない。embed 成功 + FTS mirror 失敗の行は hash が残り通常経路では永久にスキップされるため、この option は必要
  • nextIssueWatermark の never-regress: pin が現在の watermark 以下なら現在値を返す。バッチ先頭が失敗した場合に前進も後退もしない

検証の裏取り

担当が「watermark logic を巻き戻すと新規回帰テスト 4 本が落ちる」ことを実走で確認している。テスト構成を読み、drains a backlog larger than the budget across runs without skipping(130 件 3 run)が欠陥の再現そのものを見張っていることを確認した。

受容したトレードオフ

  • 永続的に embed 失敗する item が 1 件あると、その repo の issue 索引全体が止まる。 修正前は追い越して黙って落としていた。これは #178 が明示的に選んだ「silent gap より visible stall」の判断と同じで、commit-diff 面が既に同じ性質を持っている。専用のアラームは無く、毎 run の watermark=... (pinned before ...) ログが同一値を繰り返すことが停止のシグネチャになる。commit-diff 面と観測性の水準は同じなので、本 PR が新たに作った差分ではない

scope の逸脱

無し。ETag の穴は issue 本文に無いが、恒久修正が実効を持つための必要条件であり同一欠陥の一部。

次のステップ

auto mode のため human gate 無し。self-review pass → merge。

残件(PR 範囲外、parent が deploy 後に実行): /admin/backfill-issue-index を全 6 repo に対して cursor を回して実行する。Master の go-sign 取得済み(2026-08-03、対象 1239 件)。順序は deploy → backfill。逆順だと backfill 後に入る新規分が同じ経路で再び落ちる。

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.

Index covers only about 45 percent of issue and PR history

1 participant