跳转到正文

索引文本搜索

索引文本搜索是 文本搜索的预热缓存版本。首次调用会在磁盘上构建三元组和标识符倒排表存储;后续调用使用该存储跳过显然不可能包含模式的文件。这样一来,在大型代码库中重复查询时,延迟会大幅降低,而且无需引入守护进程或监视进程。

预热文本功能现在包括:

  • 单索引搜索
  • 通过重复指定 --index-dir 进行多索引搜索
  • 基于清单的集合搜索
  • 感知生命周期的过期处理
  • 通过 --trace-plan 提供规划器诊断信息

索引通过 greph-index CLI 和 Greph::buildTextIndex() / Greph::searchTextIndexed() 门面方法提供。

构建索引

命令行
# Full build at the current directory
./vendor/bin/greph-index build .

# Full build at an explicit root
./vendor/bin/greph-index build path/to/repo

# Use a non-default index directory
./vendor/bin/greph-index build . --index-dir /tmp/greph-index

# Build with an explicit lifecycle policy
./vendor/bin/greph-index build . \
  --lifecycle opportunistic-refresh \
  --auto-refresh-max-files 32 \
  --auto-refresh-max-bytes 1048576

默认情况下,索引存储在 <root>/.greph-index/。

构建成功后会输出一行摘要:

Text
Built index for 2547 files in .greph-index (193428 trigrams, +2547 ~0 -0 =0)

四项计数分别表示新增、更新、删除和未更改的文件数量。首次构建时,新增计数等于文件数量,其余计数均为零。

刷新索引

命令行
./vendor/bin/greph-index refresh .

refresh会重新遍历索引根目录,对每个文件进行哈希计算,并且只重写元数据发生变化的条目。输出使用相同的四项计数,让你准确了解哪些内容发生了变化:

Text
Refreshed index for 2547 files in .greph-index (193428 trigrams, +3 ~7 -1 =2536)

使用 manual-refresh 时,编辑受跟踪的文件后运行 refresh。对于 opportunistic-refresh 索引,如果变更集仍足够小,Greph 可以在搜索期间自动刷新索引。

生命周期配置

Greph不使用守护进程。生命周期配置用于控制源代码树发生变化时预热索引的行为:

  • static:从不自动检查新鲜度或修改数据
  • manual-refresh:在 stats 中显示过期信息,但搜索时从不刷新
  • opportunistic-refresh:仅当变更集低于配置的阈值时,才在搜索期间刷新
  • strict-stale-check:拒绝过期的预热搜索,而不执行刷新

使用 greph-index stats --dry-refresh 检查预热搜索将执行的操作:

命令行
./vendor/bin/greph-index stats . --dry-refresh

查询索引

命令行
# Fixed-string search
./vendor/bin/greph-index search -F "function" .

# Case-insensitive whole-word
./vendor/bin/greph-index search -F -i -w "function" .

# Counts
./vendor/bin/greph-index search -F -c "function" .

# Files only
./vendor/bin/greph-index search -F -l "function" .

# Multi-index search
./vendor/bin/greph-index search -F "apply_filters" . \
  --index-dir wordpress/.greph-index \
  --index-dir wp-content/plugins/my-plugin/.greph-index \
  --show-index-origin

# Regex search
./vendor/bin/greph-index search "function\s+\w+" .

# JSON output
./vendor/bin/greph-index search --json -F "function" .

# Planner trace to stderr
./vendor/bin/greph-index search --trace-plan -F "function" .

标志集与原生文本搜索相同。完整列表请参阅 CLI参考。

如果请求位置不存在索引,greph-index search 会报错。此命令不会自动回退到非索引扫描(AST 命令提供可选的 --fallback scan,但文本命令不提供)。请先构建索引。

命名索引集合

如果你反复搜索相同的预热布局,可以使用清单,而无需重复指定 --index-dir 标志:

JSON
{
  "name": "wordpress-local",
  "indexes": [
    {
      "name": "core-text",
      "root": "wordpress",
      "mode": "text",
      "lifecycle": "static",
      "priority": 100
    },
    {
      "name": "plugin-text",
      "root": "wp-content/plugins/my-plugin",
      "mode": "text",
      "lifecycle": "opportunistic-refresh",
      "max_changed_files": 16,
      "max_changed_bytes": 262144,
      "priority": 200
    }
  ]
}

将其保存为 .greph-index-set.json,然后:

命令行
./vendor/bin/greph-index set build
./vendor/bin/greph-index set stats --dry-refresh
./vendor/bin/greph-index set search --show-index-origin -F "apply_filters" .

索引存储内容

文本索引是三元组和标识符倒排表存储。对于每个已索引文件,Greph会提取:

  • 三元组集合:文件内容中出现的三字节序列集合。用作字面查询和正则查询的粗略预筛选。
  • 标识符倒排表:文件中符合标识符形态的词元([A-Za-z_][A-Za-z0-9_]*)集合。用于加速整词查询。
  • 生命周期元数据:存储的阈值和过期检查行为。
  • 内容元数据:用于刷新决策和过期检查的大小和 mtime。

收到查询后,Greph会从模式中提取相同形态的内容(从字面子串中提取三元组,从整词模式中提取标识符词元),求倒排表的交集,并且只重新读取匹配的文件。通过交集筛选的文件会使用与原生文本模式相同的引擎进行搜索。

对于某些已预热的摘要查询,Greph可以直接从倒排表中作答:

  • 整词 -l
  • 整词 -L
  • 整词 -q

这样一来,如果倒排表已经能确定结果,就完全无需重新打开候选文件。

这与ripgrep的可选预索引和 codesearch 的三元组索引思路相同,但专门针对磁盘上的形态构建,让已预热的 PHP能够以最快速度命中。

规划器诊断信息

--trace-plan会将已预热的文本规划器决策输出到 stderr,同时保持正常结果流不变。

跟踪信息包括:

  • 生命周期配置
  • 选中的文件数
  • 候选来源(full-scan、trigram-postings、word-postings或 invert-scan)
  • 倒排表词项数
  • 候选文件数和已验证文件数
  • 是否符合直接生成摘要的条件
  • 是否符合查询缓存条件,以及缓存填充决策

适用场景

以下情况适合使用索引模式:

  • 你会连续多次查询同一代码仓库(CI 作业、代理循环、IDE 集成)。
  • 代码仓库足够大,原生扫描已成为瓶颈。
  • 模式包含索引可以预筛选的字面子串或标识符词元。

以下情况仍适合使用原生扫描:

  • 查询的是小型代码仓库,构建索引的成本占主导。
  • 只搜索一次之后不会再查询的路径。
  • 需要在一次临时调用中获取所有匹配项,而且不想维护索引。

README中公布的基准测试数据显示,在针对 WordPress 语料库的正则查询,以及计数、含匹配项的文件和不含匹配项的文件查询中,预热后的索引模式领先于 rg 和 grep。在纯字面查询中,它优于 grep,但仍略逊于 rg。

编程方式使用

PHP
use Greph\Greph;
use Greph\Text\TextSearchOptions;
use Greph\Index\IndexLifecycleProfile;

// Build once
Greph::buildTextIndex('.', lifecycle: IndexLifecycleProfile::OpportunisticRefresh);

// Refresh after edits
Greph::refreshTextIndex('.');

// Query
$results = Greph::searchTextIndexed(
    'function',
    'src',
    new TextSearchOptions(fixedString: true),
);

searchTextIndexed接受与原生文本搜索相同的 TextSearchOptions,并返回相同的 list<TextFileResult>结构。对于多索引或基于清单的流程,请使用 searchTextIndexedMany(...) 和 searchTextIndexedSet(...)。