GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

17. chunk 数の上限を、バイトを捨てずに守る

背景と課題

ADR-0012 は、索引する すべてのソースファイルについて 1 つの約束をしている: parse の成否によらず、持っている バイトを全部差し出す。定義の外にあることを理由に落としてはならず、grammar が読めなかった 領域だからという理由でも落としてはならない。 ADR-0014 は定義が入れ子で 居てよい深さの上限を足したが、その約束は意図的に守った — 上限を超えたファイルは拒否ではなく 行単位で chunk 化し、その判断が見えるように parse:too-deep を付ける。

3 つ目の上限は両方より前からあり、この約束を守っていなかった。MAX_CHUNKS_PER_FILE は 「1 ファイルが出せる chunk は 512 個まで」と言うが、その適用は先頭 512 個を残して残りを 捨てる形だった。piece は位置順にソートされているので、捨てられるのは常にファイルの 末尾になる。定義が 600 個あるファイルは、先頭からどこか途中までが索引され、残りは documents にはあって (コードパーサはソースを逐語で保持するので get_document は全文を 返す) どの chunk にも無い — つまりどの検索も届かない状態になっていた。索引時の tracing::warn! 1 行がそう言うだけで、他には何も無い — document にタグは付かず、 groove doctor が読み戻せるものも無く、ドキュメントにも書かれていなかった。

しかもこの切り捨ては fallback を選ぶ分岐の外にあったので、スコープ側の fallback の 出力も切っていた。ファイルを丸ごと保つためにわざと degrade したファイルが、その直後に 別の理由で末尾を切られていたことになる。

本決定が答える問い: ファイルが生き残らなければならないとき、chunk 数の上限とは何を意味 するのか。

判断の軸

検討した選択肢

  1. 切り捨てたまま、それを言う。 監査票が求めたタグと doctor の検査を足し、末尾は 落としたままにする
  2. 行単位へ degrade する。 ただし budget が狭い設定では chunk 数が上限を超え得ることを 受け入れる
  3. 行単位へ degrade し、上限に収まるよう行の budget を広げる。
  4. 上限を撤廃する。 1 MiB のバイト上限が既にファイルを縛っている、という理由で

決定

選択肢 3。 定義が上限より多い chunk を要求するファイルは行単位で chunk 化し (スコープ上限が既に使っていたのと同じ fallback)、parse:too-many-chunks を付ける。 戻ってくるものが上限に収まるよう、行の budget は必要なら広げる。

選択肢 1 を採らない理由。 get_document が返している末尾に検索が届かない状態は、 書き留めるべき制限ではなく ADR-0012 がまさに防ぐために存在する失敗であり、書き留めた ところで誰にも逃げ道を与えない。MAX_CHUNKS_PER_FILE は設定ではなく定数なので、検査が 名指しできる直し方は「ファイルを小さくしろ」しかない。

選択肢 2 を採らない理由。 行 fallback を縛るのは budget であり、その budget は下限の 無い設定キーである。max_chunk_chars を小さくした知識ベースでは、1 MiB のファイル 1 本が 何十万もの chunk を生み、そのすべてが rebuild_index が embedder と DB を保持したまま embed される — ADR-0014 が防ぐために存在する可用性の形に、反対側から到達する。上限は 助言に成り下がる。

選択肢 4 を採らない理由。 バイト上限が縛るのはバイトであって chunk 数ではなく、2 つは 別の問いである。split_by_lines は次の行が budget を超えるときにだけ piece を切るので、 隣り合う 2 つの piece は合計で必ず budget を超える。したがって非空白 W 文字のテキスト から出る piece は 2W / budget + 1 未満になる。既定 budget 3500 と 1 MiB 上限を入れると、 空白を持たないファイルでおよそ 600 — 既に上限超過。この 600 は計測値ではなく上の式から 出した上界で、式の根拠は grooveseek/src/parser/code/mod.rs の split_by_lines の切り方に あり、the_widened_budget_keeps_the_split_under_the_bound テストが式を言い直すのではなく その関数自身に突き合わせている。上限を撤廃すると「1 ファイルが出せる chunk はいくつまでか」に 答えが 1 つも無くなる。

選択肢 3 が使うのは同じ算術である。piece を limit 未満にしたければ budget は 2W / (limit - 1) でよく、fallback はそれと設定値の広い方を採る。広げ幅はファイルから 導かれるので、同じファイルはどのマシンでも同じ chunk になり、広げる必要が無かったファイルに 対しては何もしない。

上限の値そのものは 512 のまま。本リポジトリ自身のソース (.rs 62 本) を scratch の知識ベースへ 索引して数えたところ、最も広いファイルで 297 chunk だった 。 通常のコードは上限に届くまで 4 分の 3 ほどの余地がある。変えたのは「届いたときに何が 起きるか」の方である。

結果と代償

参考