TL;DR
bigram_index
可用于多种用途,本文重点聚焦于短语搜索性能:在下方的 1M 文档基准测试中,bigram_index='all' 将 QPS 提高了约 2.9x,并将平均短语查询延迟降低了约 3.2x。
如果你的主要问题是将 xt850 与 xt 850 匹配,而不是加速短语搜索,请参阅
如何让 xt850 匹配 xt 850
。
短语搜索可能非常耗时。即使查询很短,引擎仍需验证顺序和邻近性,而当以下情况发生时,这种工作量会更加明显:
- 单个词是常见词
- 数据集很大
- 短语查询在你的工作负载中很频繁
这正是 bigram_index 的用途。
bigram 索引实际上做了什么
通常,像 "noise cancelling headphones" 这样的短语会被处理为需要按正确顺序和相邻出现的独立标记。bigram 索引允许 Manticore 预先存储相邻标记对,例如:
noise cancellingcancelling headphones
这为引擎在短语匹配期间更快地缩小候选文档提供了途径。
本文专门聚焦于短语加速。
重要注意事项:bigram 在分词级别工作
这是在你只关注加速路径时容易忽略的部分。
bigram_index 仅在分词级别工作。它不考虑后续的转换,如词形变化、词形或停用词,而这些可能会显著改变短语匹配的预期。
实际结论很简单:bigram 对短语速度非常有效,但如果你的索引严重依赖词形变化、词形或停用词,在广泛启用此设置之前,请测试你实际关心的短语行为。
模式 1:默认行为
这是基准线。未启用显式的 bigram 索引,因此不会存储 bigram 倒排列表。
使用场景:
- 短语搜索很少
- 文档很短
- 你希望使用最轻量的索引路径
示例
DROP TABLE IF EXISTS bi_none_demo;
CREATE TABLE bi_none_demo(title text);
INSERT INTO bi_none_demo VALUES
(1,'wireless noise cancelling headphones'),
(2,'noise cancelling microphone'),
(3,'wireless gaming headset');
SELECT id, title FROM bi_none_demo WHERE MATCH('"noise cancelling"');
这是基准行为。查询匹配预期的行,但 Manticore 没有预计算的 bigram 倒排列表来更高效地解决短语。
模式 2:all
bigram_index = all
这是最激进的短语加速模式。每个相邻的标记对都会被索引为 bigram。
使用场景:
- 精确短语搜索是核心功能
- 短语查询经常包含常见词并产生大量候选
- 你希望获得最强的短语加速
- 你不想调整频繁词列表
示例
DROP TABLE IF EXISTS bi_all_demo;
CREATE TABLE bi_all_demo(title text)
bigram_index='all';
INSERT INTO bi_all_demo VALUES
(1,'lord of the rings trilogy'),
(2,'house of the dragon season 2'),
(3,'made for iphone charger');
SELECT id, title FROM bi_all_demo WHERE MATCH('"house of the dragon"');
SELECT id, title FROM bi_all_demo WHERE MATCH('"made for iphone"');
这里的重要点不是不同的匹配,而是不同的索引策略:all 存储每个相邻对,因此短语查询在搜索时可以获得最大数量的 bigram 帮助。
选择 all 的原因是在短语搜索变得昂贵时,因为许多文档匹配单个词,Manticore 然后需要进行更多位置验证以确认精确短语。all 通过更早地缩小候选来帮助。
模式 3:first_freq
bigram_index = first_freq
bigram_freq_words = for, of, the, with
此模式仅在第一个标记在你的频繁词列表中时存储对。
使用场景:
- 短语搜索很重要
- 你想要比
all更轻量的替代方案 - 你的数据中的许多短语包含在你自己的语料库中真正频繁的词
使用上述列表:
for iphone有资格of the有资格the dragon有资格made for没有资格lord of没有资格
在生产环境中,不要从记忆中选择 bigram_freq_words。从你自己的数据中推导它。一种实用方法是使用
indextool
通过 --dumpdict ... --stats 导出字典统计信息,查看最频繁的标记,然后从这些结果中构建一个小型的 bigram_freq_words 列表。
示例
DROP TABLE IF EXISTS bi_first_freq_demo;
CREATE TABLE bi_first_freq_demo(title text)
bigram_index='first_freq'
bigram_freq_words='for,of,the,with';
INSERT INTO bi_first_freq_demo VALUES
(1,'made for iphone charger'),
(2,'lord of the rings trilogy'),
(3,'house of the dragon season 2');
SELECT id, title FROM bi_first_freq_demo WHERE MATCH('"made for iphone"');
SELECT id, title FROM bi_first_freq_demo WHERE MATCH('"lord of the"');
查询仍然返回预期的行。变化的是哪些对被索引:
"made for iphone"从for iphone获益"lord of the"从of the获益
这使得 first_freq 在许多有用短语包含常见连接词时成为 all 的更轻量替代方案。
模式 4:both_freq
bigram_index = both_freq
bigram_freq_words = for, of, the, with
这是最狭窄的频率模式。仅当两个标记都在频繁词列表中时,才存储对。
使用场景:
- 你希望最保守的 bigram 足迹
- 你主要关心由在你语料库中高度频繁的词构建的对
- 你在调整大型语料库,不想索引每个相邻对
使用相同的列表:
of the有资格for iphone没有资格the dragon没有资格
示例
DROP TABLE IF EXISTS bi_both_freq_demo;
CREATE TABLE bi_both_freq_demo(title text)
bigram_index='both_freq'
bigram_freq_words='for,of,the,with';
INSERT INTO bi_both_freq_demo VALUES
(1,'lord of the rings trilogy'),
(2,'house of the dragon season 2'),
(3,'made for iphone charger');
SELECT id, title FROM bi_both_freq_demo WHERE MATCH('"lord of the"');
SELECT id, title FROM bi_both_freq_demo WHERE MATCH('"made for iphone"');
查询仍然匹配,但内部选择性不同:
"lord of the"包含of the,这是both_freq愿意存储的"made for iphone"包含for iphone,这是first_freq会覆盖但both_freq不会覆盖的
你应该选择哪种性能模式?
本文中的基准测试显示 all 可以提供显著的速度提升,但它仍然是单一工作负载上的一个基准测试。
Manticore 自己的文档指出,对于大多数用例,both_freq 是最佳模式。这是一个合理的默认值,因为它旨在在短语加速和索引成本之间实现更平衡的权衡。
使用这些模式时:
- 选择
both_freq作为一般短语搜索工作负载的默认起点 - 选择
all当短语搜索特别重要且您希望获得最强加速,接受更高的索引成本 - 选择
first_freq当您的数据中许多有用短语涉及常见连接词,且您希望比both_freq更广泛的结果 - 选择默认行为当短语加速不重要时
基准测试:bigram索引真的能加速短语搜索吗?
是的。在一个简单的本地基准测试中,差异很容易测量。
我使用 manticore-load 在同一 Manticore 实例上构建了两个包含 100 万文档的表:
- 一个没有显式的
bigram_index设置 - 一个设置为
bigram_index='all'
文档是随机的 60-80 字文本,基准测试重复运行随机的 2 字短语查询。
为清晰起见,索引和搜索均使用 --threads=1 运行。多线程的数值当然会更高,但单线程运行更容易观察单个 CPU 核心上的功能变化。
SELECT COUNT(*) FROM bench_bigram_* WHERE MATCH('"<text/2/2>"')
基准测试设置
无 bigram 的数据加载:
manticore-load \
--drop \
--wait \
--threads=1 \
--batch-size=1000 \
--total=1000000 \
--init="CREATE TABLE bench_bigram_none_rand(title text)" \
--load="INSERT INTO bench_bigram_none_rand(id,title) VALUES(<increment>,'<text/60/80>')"
包含所有 bigram 的数据加载:
manticore-load \
--drop \
--wait \
--threads=1 \
--batch-size=1000 \
--total=1000000 \
--init="CREATE TABLE bench_bigram_all_rand(title text) bigram_index='all'" \
--load="INSERT INTO bench_bigram_all_rand(id,title) VALUES(<increment>,'<text/60/80>')"
无 bigram 的搜索基准测试:
manticore-load \
--threads=1 \
--total=5000 \
--load="SELECT COUNT(*) FROM bench_bigram_none_rand WHERE MATCH('\\\"<text/2/2>\\\"')"
包含所有 bigram 的搜索基准测试:
manticore-load \
--threads=1 \
--total=5000 \
--load="SELECT COUNT(*) FROM bench_bigram_all_rand WHERE MATCH('\\\"<text/2/2>\\\"')"
我的观察
在此次本地运行中:
| 表 | QPS | 平均延迟 |
|---|---|---|
bench_bigram_none_rand | 755 | 1.3 ms |
bench_bigram_all_rand | 2175 | 0.4 ms |
这大约是 QPS 的 2.9x 提升,且在相同 100 万文档工作负载下平均延迟提升了约 3.2x。
使用 bigram_index='all' 时索引速度更慢,这是预期的:
- 无 bigram:约
45k docs/sec - 使用
all:约17k docs/sec
这种权衡正是为何存在多种模式的原因。
最终结论
如果您的主要问题是短语搜索性能,请首先将 bigram_index 视为加速功能。
对于大多数实际工作负载,从 both_freq 开始并进行测量。如果需要更强的效果且能承担额外的索引成本,请切换到 all。当您的短语工作负载高度依赖常见连接词时,请考虑 first_freq。
