Skip to content

bug(index): pre-D1 doc vectors linger in Vectorize and the reap cannot reach them #204

Description

@liplus-lin-lay

目的

現行 vector-id 体系より前の世代の doc ベクトルが Vectorize に残っており、削除経路から到達できない。同一ファイルが二重に索引された状態が常態化している。一回性の掃除で解消する。

前提(2026-08-02 実測)

事実 1: 生きているファイルが doc 行を 2 本持つ

repo: Liplus-Project/github-webhook-mcp / type: doc:

doc_path 行 A (updated_at) 行 B (updated_at)
README.md 2026-06-26 2026-04-10
docs/installation.md 2026-04-28 2026-04-10
docs/0-requirements.md 2026-06-26 2026-04-10

同一 path の upsert が既存行を上書きせず 2 行になっている以上、両者の vector id は異なる。

事実 2: 2026-04-10 の層は現行 D1 に存在し得ない

現行 D1(github-rag-fts)の作成日は 2026-04-19。D1 実測(search_docs / type='doc' / 当該 repo)は 2026-04-19 が 3 行、04-21 が 2 行、06-26 が 4 行の計 9 行で、2026-04-10 の行は 1 本も無い。にもかかわらず検索は 2026-04-10 の doc を返す。その層は Vectorize にのみ存在する

事実 3: 原因は vector-id 体系の移行

移行コミット = 215e2e22026-04-19fix(pipeline): hash vector IDs to stay under Vectorize 64 byte cap / PR #84)。

移行前(215e2e2~1:src/pipeline.ts):

export function vectorId(repo: string, number: number): string {
  return `${repo}#${number}`;
}
export function releaseVectorId(repo: string, tagName: string): string {
  return `${repo}#release-${tagName}`;
}
export function docVectorId(repo: string, path: string): string {
  return `${repo}#doc-${path}`;
}

現行は {prefix}:{base64url(sha256(parts.join(NUL)))}src/pipeline/vector-id.ts)。両者に衝突は無い。

削除経路(src/poller.ts の doc 削除、src/webhook.ts の removed 処理)はいずれも現行 docVectorId で id を計算するため、旧世代には永久に到達できない

日付が三点で整合する: vector-id 移行 = 2026-04-19 / 現行 D1 作成 = 2026-04-19 / 孤児層の updated_at = 2026-04-10(移行前)。孤児層は移行前に索引され、移行後に一度も再索引されていないベクトル。

実害

  • 候補プールの浪費: 同一ファイルが 2 枠を取る。spec(retrieval): top-k is crowded by multiple rows of the same underlying entity #189 の集約が表示段では畳むので top_k の意味は守られるが、プールの実効幅は減る
  • 削除済みファイルが残り続ける: github-webhook-mcp #243 / PR #244 で .claude/ 65 ファイルを削除した直後、D1 FTS 側の .claude/ doc 行は 0 件になった(現行世代は正しく reap された)が、検索は依然として .claude/CLAUDE.md(updated_at 2026-04-10、dense のみ)を返す
  • 内容が移行前で固定: 旧世代の行は再索引されないので本文が古いまま返る

範囲の限定

移行は全 surface の id を変えているので issue / release にも及んでいる可能性があるが、実測で確認できているのは doc のみ

updated_at の意味は type ごとに違う。release v0.3.2updated_at: 2026-04-06(移行前)だが sparse からも引けており現行 FTS に存在する = release 自身の日付であって索引時刻ではない。この差があるため日付だけで他 surface の孤児を判定できない。

doc 以外の孤児の有無は未確認。実装前に surface ごとの確認を行うこと。 確認できない surface は本 issue の対象に含めない。

方針(採用)

一回性の掃除を admin endpoint として置く。再 embed は伴わない。

POST /admin/purge-legacy-vectors?repo=owner/repo[&dry_run=true][&surface=doc]

  1. 対象 repo の現行ツリーから .md を列挙し、各 path について旧式 {repo}#doc-{path} を計算
  2. VECTORIZE.deleteByIds にバッチで渡す(存在しない id は無害)
  3. dry_run=true では件数だけ返し、削除しない
  4. per-run 上限を持たせる(bug(docs): the doc reap has no per-run cap and its teardown is not surface-independent #203 と同じ理由 = subrequest 予算)
  5. ツリーから既に消えた path のために、明示 paths を body で渡せる口を用意する

残差: ツリーから消えた path の旧 id は列挙できない。判明している分(今回の .claude/ 65 本)は明示 paths で一回流す。旧式で索引された時期は閉じているため、この残差集合は今後増えない。

採らなかった案:

  • index ごと作り直す — 最も単純だが全件再 embed のコストが乗る。孤児は有限で増えないため釣り合わない
  • Vectorize を列挙して現行 D1 に対応の無いものを消す — 列挙 API に依存し対応表の作成コストも高い。旧 id が決定的に再構成できる以上、列挙は不要
  • 放置し spec(retrieval): top-k is crowded by multiple rows of the same underlying entity #189 の集約で表示段だけ守る — 削除済みファイルが検索に出続ける問題が残る

制約

  • 再 embed を伴わないこと(掃除であって再構築ではない)
  • 現行世代のベクトルを消さないこと。旧式 id と現行 id は形式が異なるので、計算した旧式 id 以外を触らない
  • dry_run を既定にしない場合でも、削除件数をレスポンスに含めること

受け入れ条件

  • dry_run=true で削除件数が返り、Vectorize に変更が無いことがテストで固定されている
  • 実行後、README.md / docs/installation.md / docs/0-requirements.md の doc 行が 1 本ずつになる(実測で確認)
  • 実行後、.claude/CLAUDE.md が検索から消える(実測で確認)
  • per-run 上限があり、上限を超えた分が呼び出し側に分かる形で返る

想定変更箇所

  • src/index.ts(admin endpoint 追加)
  • src/pipeline/vector-id.ts または新規モジュール(旧式 id の再構成関数。現行 id 生成とは分離すること)
  • 対応するテスト
  • docs/installation.md / .ja.md(admin endpoint の説明)

参考

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