
2026/03/24 15:31
高速正規表現検索:エージェントツール用テキストインデックス化
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
この記事は、現代のAI支援型コーディングツールがまだgrepスタイルの正規表現検索に依存していると主張しています。しかし、クラシックユーティリティであるgrepや、より高速なripgrepさえも、大規模なモノレポでは実用的ではありません。なぜなら、毎回すべてのファイルをスキャンしなければならないからです。そのため、効率的な検索にはローカルに構築された専用インデックスが必要です。初期研究では、正規表現検索にはインデックスが不可欠であることが示され、トリグラムやサフィックス配列といった代表的なアプローチがあり、それぞれにトレードオフがあります(トリグラムインデックスは大きなポスティングリストを生成し、サフィックス配列は高価な再構築を必要とします)。新しい確率的トリグラムや疎n-gram手法は、クエリ速度を保ちつつインデックスサイズを削減します。実験では、ローカルクライアント側の正規表現インデックスがComposer 2などのモデルに対して数秒からほぼリアルタイムへと遅延を短縮することが示されました。本提案のアーキテクチャは、ポスティングリストをハッシュ–オフセットテーブル付きでファイルに保存し、バイナリ検索を高速化するためにメモリマップしてローカルにインデックスを構築します。これによりネットワーク往復が不要になり、ローカル編集後もインデックスは新鮮な状態を保ちます。実証結果は、大規模企業レポジトリでの遅延削減が顕著であり、デバッグやリファクタリング作業を高速化することを示しています。このシフトは、ツールエコシステム内でローカルインデックスベースの検索エンジンへの広範な移行を示しており、開発者と企業に生産性向上という形で恩恵をもたらします。
本文
時間は平坦な円です。
1973年に最初の grep がリリースされたとき、それはファイルシステム上のテキストファイルに対して正規表現を照合するだけの基本的なユーティリティでした。年月が経つにつれ、開発ツールが高度化するとともに、次第により専門的なツールへと置き換えられていきました。まずは ctags のような概略的構文インデックス、その後多くの開発者がコードベースを効率よくナビゲートできる IDE に移行し、解析や構文インデックス作成に加えて型情報を補完するものへと進化しました。
最終的には Language Server Protocol (LSP) が標準化され、古いものも新しいテキストエディタすべてがこれらのインデックスを利用できるようになりました。そして LSP が標準となりつつあるその頃に Agentic コーディングが登場し、何と聞いてください――エージェントは grep を使うことが大好きなのです。
問題点
コンテキストを収集するための最先端技術はいくつかあります。過去に、セマンティックインデックスを多くのタスクで利用すればエージェント性能は大幅に向上すると話し合ったことがありますが、モデル自身が正規表現検索でしか解決できない特定のクエリも存在します。つまり 1973 年に戻る必要があります――それでも分野は少し進歩しています。
ほとんどのエージェントハーネス(私たちものを含め)は、検索ツールとして ripgrep をデフォルトで使用します。Andrew Gallant が開発したスタンドアロン実行ファイルで、古典的な grep の代替としてより合理的なデフォルト(例えば無視するファイルの扱い)と圧倒的に高速なパフォーマンスを提供します。ripgrep は正規表現マッチング時に速度を重視して設計されたため、非常に速いという評判があります。
しかし、どれだけ高速であっても ripgrep にはひとつの重大な制限があります ― すべてのファイルの内容を対象にマッチする 必要がある点です。小規模プロジェクトなら問題ありませんが、Cursor のユーザー(特に大規模エンタープライズ顧客)の多くは非常に大きなモノレポで作業しています。そのため rg コマンドの実行時間が 15 秒を超えるケースが日常茶飯事であり、コードを書いている最中にエージェントと対話しながら進めるワークフローを著しく遅延させます。
正規表現検索は Agentic 開発の重要な要素であり、その対象を明示的に扱うことが不可欠です。従来の IDE が Go To Definition のような操作のためにローカル構文インデックスを作成するのと同様に、我々はエージェントがテキスト検索を行う際に使用するコアオペレーション用のインデックスを構築しています。
典型的なアルゴリズム
テキストデータをインデックス化して正規表現マッチングを高速化するアイデアは古くから存在します。1993 年に Zobel、Moffat、Sacks‑Davis が発表した論文 Searching Large Lexicons for Partially Specified Terms using Compressed Inverted Files で、n-gram(文字列の幅が n のセグメント)を使った逆インデックスと正規表現を n‑gram の木構造に分解して検索する手法が紹介されました。
この概念は、その論文よりも 2012 年に Russ Cox が Google Code Search 停止直後に書いたブログ投稿で広く知られるようになりました。ここでは、これらのインデックスに共通する基本構成要素を簡潔に復習します。
逆インデックス
検索エンジンの根幹となるデータ構造です。対象文書集合から各文書を トークン に分割(tokenization)し、各トークンをキーとして辞書型構造に格納します。値はそのトークンが出現するすべての文書リスト(posting list)です。検索時にはトークンごとに posting list をロードし、交差させることですべてのトークンを含む文書を取得します。
正規表現で機能しない理由
ソースコードは自然言語ではなく、単語単位で分割すると識別子が誤って切断され、文字レベルでの精度が必要になるケースが多いです。プログラミング言語ごとの構文を尊重したトークナイズ手法が求められます。
3-gram 分解(Trigram)
ソースコードに対する単純なトークン化は正規表現検索には不適切です。そこで典型的なアルゴリズムでは trigram(重複しながら 3 文字ずつ切り出す)を採用します。
- バイグラムだとキー数が少なく、posting list が膨大になり効率が落ちます。
- クワッドグラムだと posting list は小さくなるものの、インデックス全体で数十億ものキーが必要になり管理が難しくなります。
3 文字はトークン化を簡潔に保ちながら、インデックスサイズを実用的に抑えるバランス点です。正規表現自体の解析も必要ですが、リテラル文字列や選択肢、文字クラスから trigrams を抽出し、適宜 union(OR)または intersection(AND)で posting list を結合します。
全体像
- インデックス構築 – 各文書のすべての重複 3-gram を抽出し、そのオフセットを posting list に追加。
- クエリ時 – 正規表現を trigrams の集合に分解、対応する posting list をロードして交差/結合し候補文書集合を取得。
- 検証 – 候補文書ごとに本物の正規表現を実行。これは全コードベースを走査するよりはるかに低コストです。
google/codesearch や sourcegraph/zoekt などがこの trigrams ベースの逆インデックスで大規模な検索性能を実現していますが、以下の課題があります。
- インデックスサイズが大きい場合もある。
- クエリ時のヒューリスティック(trigram 数の選択)が重要:少ないと候補が多すぎ、逆に多いと posting list の読み込みコストが増える。
Suffix Array – 逸話
テキスト検索アルゴリズム史を語る際には、Nelson Elhage が 2015 年に開発した livegrep の実装も触れておく価値があります。大規模プロジェクトと比べて livegrep は極めて小さく、Linux カーネルの最新バージョンのみをインデックス化していますが、そのスコープの狭さゆえに実装は独特です。
Suffix Array は文字列のすべての接尾辞をソートした配列です。大規模なコーパスではスペースコストが高いですが、元の文字列へのアクセスが可能ならば各接尾辞オフセットだけを格納して圧縮できます。
- 正規表現をリテラルに分解。
- 各リテラルで suffix array を二分探索し、潜在的マッチ位置を取得。
のような文字範囲は、開始と終了の境界を二分探索して求めます。[a‑z]
欠点としては、すべての文書を 1 つの文字列に結合しなければならないため更新が難しいこと、マッチ位置を元ファイルへ戻す処理が煩雑になる点があります。
Probabilistic Trigram Masks(Bloom フィルタ)
GitHub の Project Blackbird は、trigram インデックスに「次の文字」情報を追加する手法を検証しました。直接四文字目を格納すると quadgram インデックスになってしまうため、Bloom フィルタ(8 ビット)で可能なすべての続き文字を保持します。
- locMask – 位置を 8 で割った余りを示す 8‑bit マスク。
- nextMask – 次に来る文字の Bloom フィルタ(8 ビット)。
この方式は quadgram をクエリできつつインデックスサイズを抑え、隣接 trigrams が連続していることも確認できます。しかし Bloom フィルタが飽和すると選択性が低下し、誤検知が増えるという欠点があります。
Sparse N‑grams
ClickHouse の正規表現演算子や GitHub の新しい Code Search で採用されているのが Sparse N‑grams です。すべての連続 n-gram を抽出する代わりに、文字ペアごとに決定的な重み(例:CRC32 ハッシュ)を付与し、境界の重みが内部よりも高い部分だけを抜き取ります。
- 文字対ごとに重み計算。
- 内部のすべてのペアより大きい境界のみを抽出。
- 抽出された n-gram は可変長で、従来の trigram よりはずっと少数です。
重み付けに頻度情報(稀な文字対に高い値)を加えると、さらに n‑gram の数を減らせます。この手法はクエリ時に最小カバー集合を再構築できるため、高速で特異性の高い検索が可能です。
クライアント側インデックス
上記の設計は従来サーバーサイドで実装されてきましたが、Agentic 開発では ローカル で構築・照合する方針が適しています。
- インデックスは候補セットのみを提供し、各ファイルはやはりローカルで走査します。
- クライアントに保持すればネットワーク遅延がなくなり、レイテンシが向上します。
- Git コミットと連動させることで、インデックスを差分レイヤーとして高速更新可能です。
実装のポイント:
- Posting File – すべての posting list を連続したバイナリファイルに格納。
- Lookup Table – n‑gram のハッシュと Posting File 内オフセットをソートして保持し、
で高速検索。mmap - ハッシュのみで管理するため、衝突は極めて稀で偽陰性が生じません。
このレイアウトによりメモリ使用量を最小限に抑えつつ、クエリ性能を最大化できます。
結論
高速モデル(例:Composer 2)へテキスト検索インデックスを提供することで、Agentic ワークフローの質が飛躍的に向上します。特に大規模 Enterprise リポジトリでは grep の遅延がコードサイズと比例して増大するため、全コードベースを走査する時間を削減できれば、バグ調査時や再帰的な修正作業で顕著な時間短縮につながります。
今後は既存のアプローチ(セマンティックインデックス、確率的トリグラムマスク、Sparse N‑gram)を最適化しつつ、世界最大規模のリポジトリでさらにパフォーマンス向上を実現する新しい手法を探求していきます。