索引文本搜索
索引文本搜索是 文本搜索的预热缓存版本。首次调用会在磁盘上构建三元组和标识符倒排表存储;后续调用使用该存储跳过显然不可能包含模式的文件。这样一来,在大型代码库中重复查询时,延迟会大幅降低,而且无需引入守护进程或监视进程。
预热文本功能现在包括:
- 单索引搜索
- 通过重复指定
--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/。
构建成功后会输出一行摘要:
Built index for 2547 files in .greph-index (193428 trigrams, +2547 ~0 -0 =0)
四项计数分别表示新增、更新、删除和未更改的文件数量。首次构建时,新增计数等于文件数量,其余计数均为零。
刷新索引
./vendor/bin/greph-index refresh .
refresh会重新遍历索引根目录,对每个文件进行哈希计算,并且只重写元数据发生变化的条目。输出使用相同的四项计数,让你准确了解哪些内容发生了变化:
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 标志:
{
"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。
编程方式使用
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(...)。