Percolate query [ˈpɜ:kəleɪt] [ˈkwɪəri]
📌 简介
🔄 工作原理
传统搜索是存储文档(JSON 文档),然后发出查询以检索数据的子集.
Percolate 查询则相反:
- 存储查询 (存储查询条件) :首先在 ES 中预先注册并存储大量的查询条件。这些查询条件描述了文档特征,可以将这些查询条件想象成预设的"过滤器"或"规则"
- 渗透文档:当新文档被索引到 ES 中时,或者当有一个新的文档需要被评估时,不是针对文档索引执行搜索,而是使用渗透查询 将文档"渗透"或"流动"到这些已存储的查询中
- 匹配查询 (查询匹配): ES 会快速地检查该文档与哪些预先存储的查询条件相匹配。换句话说,系统会判断这个文档是否"穿透"了哪些预设的"过滤器"
- 返回结果 (返回匹配结果): 渗透查询最终会返回与该文档成功匹配的已存储查询的子集。这些返回的查询就是描述该文档特征的"规则"
更形象的比喻:
可以将 Percolate Query(渗透查询)想象成一个筛子系统。
- 筛子孔洞 (存储的查询): 预先制作了很多不同孔径的筛子(存储的查询条件),每种孔径代表一种筛选规则。
- 沙子 (渗透的文档): 当新的沙子(文档)倒入筛子系统时,会穿过一些孔洞,而被另一些孔洞阻挡。
- 穿过的孔洞 (匹配的查询): 最终哪些筛子的孔洞允许沙子穿过,就代表这个沙子符合哪些筛选规则(匹配的查询)。
🛠️ 具体实现步骤
1️⃣ 创建索引并定义 percolator
字段
首先,创建一个索引,并在 mappings
中定义一个 percolator
字段:
json
PUT /percolator_index
{
"mappings": {
"properties": {
"query": {
"type": "percolator"
},
"category": {
"type": "keyword"
}
}
}
}
query
字段:类型为percolator
,用于存储查询语句。category
字段:存储查询的类别,方便后续分类筛选。
2️⃣ 存储查询(注册规则)
将查询语句存入 percolator_index
索引
json
POST /percolator_index/_doc/1
{
"query": {
"match": {
"content": "Elasticsearch"
}
},
"category": "search_engine"
}
json
POST /percolator_index/_doc/2
{
"query": {
"match": {
"content": "machine learning"
}
},
"category": "AI"
}
🔹 这里,存储了两个查询:
- 查询 1 :匹配包含
"Elasticsearch"
的文档。 - 查询 2:匹配包含
"machine learning"
的文档。
3️⃣ 运行 Percolate 查询
当有新数据到来时,使用 percolate
查询来检查该数据匹配哪些已存储的查询:
json
GET /percolator_index/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"content": "Elasticsearch is a powerful search engine"
}
}
}
}
🔹 查询原理:
- Elasticsearch 会遍历
percolator_index
中所有的query
字段,检查哪个查询能匹配content: "Elasticsearch is a powerful search engine"
。
🔹 查询结果:
json
{
"hits": {
"total": 1,
"hits": [
{
"_id": "1",
"_source": {
"query": { "match": { "content": "Elasticsearch" } },
"category": "search_engine"
}
}
]
}
}
✅ 匹配到了 _id: 1
,因为 "Elasticsearch"
存在于新文档中
📊 Percolate 查询的内部机制
- 索引阶段(存储查询):
- Elasticsearch 会将存储的查询转换为
Lucene
查询,并将其存入percolator
索引。 - 查询不会像普通文档一样存储,而是以结构化形式索引,以便后续匹配。
- Elasticsearch 会将存储的查询转换为
- 查询阶段(匹配文档):
- 当新文档到达时,Elasticsearch 会倒转查询流程:
- 传统查询: 查询 → 过滤文档。
- Percolate 查询: 文档 → 过滤查询。
- 通过反向搜索,系统会返回与该文档匹配的所有查询。
- 当新文档到达时,Elasticsearch 会倒转查询流程:
🚀 适用场景
✅ 适用于
- 实时警报系统 (Real-time Alerting System): 例如,监控网络日志,当出现符合特定安全规则(预定义的 percolate 查询)的日志条目时,立即发送警报。
- 个性化内容推荐 (Personalized Content Recommendation): 用户可以预设自己的兴趣偏好(存储为 percolate 查询),当有新的文章、商品等内容(文档)发布时,系统可以快速判断哪些内容符合用户的兴趣,并进行推荐。
- 事件订阅 (Event Subscription):用户订阅特定类型的事件,当系统中出现符合订阅条件的事件文档时,用户会收到通知。例如,订阅"服务器宕机"事件,当系统日志中出现宕机相关的文档时,订阅用户会收到通知。
❌ 不适用于
- 存储百万级查询:如果查询量太大,性能会下降。
- 复杂正则匹配:
Percolate Query
适合结构化查询,而不是复杂的regexp
。
✅ 优点(Advantages)
1️⃣ 适用于实时触发系统
- 事件驱动匹配:适合实时通知 、日志监控 、内容分类等场景。
- 自动触发:当新数据到达时,能够自动匹配存储的查询,无需手动执行搜索。
2️⃣ 反向查询,提高匹配效率
- 查询是"索引"而非文档: 查询被存储在索引中,数据到来时仅需执行一次查询,避免遍历大量数据。
- 减少查询次数:比传统遍历所有文档的方式更高效。
3️⃣ 适用于用户自定义搜索
- 适合用户订阅系统(例如,用户提前定义感兴趣的关键词,当新内容到来时匹配并通知用户)。
- 适用于个性化推荐(例如,新文章到来时匹配订阅条件并推送给合适的用户)。
4️⃣ 支持复杂查询
- 可以存储
match
、bool
、range
、geo
等多种类型的查询,灵活度高。
5️⃣ 高效的查询存储
- 存储的查询会被索引优化,Elasticsearch 使用 Bitset 进行缓存,可以加快匹配速度。
❌ 缺点(Disadvantages)
1️⃣ 查询存储的扩展性有限
- 不适用于超大量查询:
- 如果存储的查询数量达到百万级别 ,会导致 索引膨胀 和 查询性能下降。
- Elasticsearch 需要遍历所有查询,这会影响匹配效率。
2️⃣ 不能处理超复杂查询
- 正则表达式查询 (
regexp
) 性能较差:- Percolate Query 适合结构化查询,如
match
、bool
。 - 但如果查询中包含复杂的
regexp
或script
,会影响查询性能。
- Percolate Query 适合结构化查询,如
3️⃣ 需要预先存储查询
- 适用于查询相对稳定 的场景,例如:
- 日志监控(规则不会频繁变动)。
- 订阅推送(用户订阅的兴趣通常不会每天修改)。
- 但如果查询规则频繁变更,则维护和存储查询的成本较高。
4️⃣ 查询匹配成本较高
- 与普通搜索相比,查询成本较高:
- 普通搜索是查询文档 ,而 Percolate Query 是查询查询 ,需要将新文档与所有存储的查询进行匹配,计算量大。
5️⃣ 依赖 Elasticsearch
- 需要额外的索引存储 和计算资源 ,对于数据量较大的场景,可能需要专门的集群优化。
💡 使用建议
1️⃣ 限制存储的查询数量
✅ 建议:
- 尽量减少存储的查询数量,建议控制在 几千到几万级,避免超大规模索引影响查询性能。
- 如果查询量过大,考虑使用 分片(sharding)或 其他查询优化策略。
🔴 避免:
- 存储百万级查询,因为每次新文档到来时,都要匹配所有存储的查询,可能会导致性能下降。
2️⃣ 采用 filter
而非 query
进行筛选
✅ 建议:
- 使用
filter
而不是query
来减少计算开销,因为filter
查询不会计算相关性评分,性能更高。
示例:用 filter
优化查询
json
GET /percolator_index/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "category": "news" } },
{
"percolate": {
"field": "query",
"document": { "content": "Breaking news: AI is revolutionizing search" }
}
}
]
}
}
}
🔹 效果:
- 先筛选
category = "news"
的查询,再执行percolate
,减少不必要的匹配计算。
3️⃣ 结合 routing
进行查询分片
✅ 建议:
- 使用
routing
让查询分布在不同的分片上,避免所有查询都存放在单个分片,提升查询效率。
示例:使用 routing
存储查询
json
PUT /percolator_index/_doc/1?routing=user_123
{
"query": {
"match": { "content": "Elasticsearch" }
},
"user_id": "user_123"
}
🔹 效果:
- 这样可以根据
user_id
进行分片存储和查询,提高查询效率。
4️⃣ 使用 Bitset
缓存提高查询速度
✅ 建议:
- Elasticsearch 自动缓存
filter
查询的 Bitset,但可以手动优化:- 频繁使用的查询 应该存入索引,以便被
Bitset
缓存。 - 结合
bool
查询 先筛选静态条件,再执行percolate
查询。
- 频繁使用的查询 应该存入索引,以便被
5️⃣ 控制 percolate
查询的字段类型
✅ 建议:
- 仅在
percolator
索引中存储必要的字段,减少索引体积。 - 避免存储大字段,如
text
类型,改用keyword
,能大幅提升查询速度。
🔴 避免存储超长 text
字段,如:
json
{
"content": "This is a very long paragraph of text, which might affect the performance of the percolate query..."
}
✅ 改进方式(使用 keyword
):
json
{
"content_short": "AI news",
"content_category": "technology"
}
6️⃣ 适当调整索引刷新间隔
✅ 建议:
- 由于
percolate
查询依赖索引,所以新查询存入索引后,必须等待索引刷新(默认 1 秒)。 - 如果查询更新较频繁,可以手动调整刷新间隔:
示例:减少索引刷新频率(提升批量插入性能)
json
PUT /percolator_index/_settings
{
"index": {
"refresh_interval": "30s"
}
}
🔹 效果:
- 适用于批量导入查询时,减少不必要的刷新,提高索引速度。
7️⃣ 避免使用 regexp
或 script
查询
🔴 避免:
- 正则表达式 (
regexp
) 查询:regexp
查询消耗 CPU 资源大,建议改用wildcard
查询或match_phrase
。
- Script 计算:
script
查询执行时需要动态计算,会大幅降低percolate
查询速度。
8️⃣ 结合其他方法优化
如果 percolate
查询性能仍然不理想,可以结合其他方法:
优化方案 | 适用场景 |
---|---|
预计算结果 + 缓存 | 当查询规则变化不频繁时,可提前计算匹配结果存入缓存,提高查询速度 |
Elasticsearch term 查询 + 关键词匹配 |
如果 percolate 查询压力大,可以使用 match 或 bool 直接匹配文档 |
Kafka + 批量匹配 | 如果 percolate 查询在高流量场景下存在瓶颈,可以用 Kafka 先存储数据,再批量处理 |
实践
1️⃣ 删除索引(若索引存在)
- 请求
json
DELETE sensitive_keyword
- 成功响应
json
{
"acknowledged": true
}
- 失败响应
json
{
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index [sensitive_keyword]",
"resource.type": "index_or_alias",
"resource.id": "sensitive_keyword",
"index_uuid": "_na_",
"index": "sensitive_keyword"
}
],
"type": "index_not_found_exception",
"reason": "no such index [sensitive_keyword]",
"resource.type": "index_or_alias",
"resource.id": "sensitive_keyword",
"index_uuid": "_na_",
"index": "sensitive_keyword"
},
"status": 404
}
2️⃣ 创建索引
json
PUT /sensitive_keyword
{
"settings" : {
"index" : {
"number_of_replicas":2,
"number_of_shards" : 5
}
},
"mappings" : {
"properties": {
"name" : {
"type" : "text"
},
"reason" : {
"type" : "text"
},
"trademark" : {
"type" : "keyword"
},
"type_num" : {
"type" : "keyword"
},
"type" : {
"type" : "keyword"
},
"special_mark" : {
"type" : "keyword"
},
"site_name" : {
"type" : "keyword"
},
"query" : {
"type" : "percolator"
}
}
}
}
- 成功响应
json
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "sensitive_keyword"
}
- 索引已存在, 重复创建响应
json
{
"error": {
"root_cause": [
{
"type": "resource_already_exists_exception",
"reason": "index [sensitive_keyword/EX7SEFAcR-yfwlfFe8FG2Q] already exists",
"index_uuid": "EX7SEFAcR-yfwlfFe8FG2Q",
"index": "sensitive_keyword"
}
],
"type": "resource_already_exists_exception",
"reason": "index [sensitive_keyword/EX7SEFAcR-yfwlfFe8FG2Q] already exists",
"index_uuid": "EX7SEFAcR-yfwlfFe8FG2Q",
"index": "sensitive_keyword"
},
"status": 400
}
3️⃣ 写入文档
🌰1️⃣ "operator": "AND"
json
POST /sensitive_keyword/_doc/5
{
"name": "MacBook Pro",
"reasion": "MacBook Pro是苹果公司(Apple Inc.)注册的商标",
"trademark": "9",
"type_num": "0901,0907,0909,0913,0920",
"type": "3",
"special_mark": 1,
"site_name": "US",
"query": {
"match": {
"name": {
"query": "MacBook Pro",
"operator": "AND"
}
}
}
}
参数含义:
- "match" -- 全文匹配查询 (Match Query),用于搜索
name
字段中包含"MacBook Pro"
这个词的文档- "name" -- 需要查询的字段(
name
)- "query" -- 需要匹配的内容(
"MacBook Pro"
)- "operator" : "AND" -- 查询逻辑 ,表示查询
name
字段时,必须完全匹配所有词(AND 逻辑,意味着 MacBook Pro 必须完整出现)- "operator" : "OR" -- 意味着 MacBook Pro 仅需其中一个词出现即可命中
🌰2️⃣ "operator": "OR" (默认)
json
POST /sensitive_keyword/_doc/6
{
"name": "MacBook Air",
"reasion": "MacBook Air是苹果公司(Apple Inc.)注册的商标",
"trademark": "9",
"type_num": "0901,0907,0909,0913,0920",
"type": "3",
"special_mark": 1,
"site_name": "US",
"query": {
"match": {
"name": {
"query": "MacBook Air",
"operator": "OR"
}
}
}
}
- 成功响应
json
{
"_index": "sensitive_keyword",
"_type": "_doc",
"_id": "5",
"_version": 1,
"result": "created",
"_shards": {
"total": 3,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
4️⃣ 渗透查询文档
🙋♀️🌰1️⃣ 无命中
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new book with m5 chip"
}
}
}
}
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new pro with m5 chip"
}
}
}
}
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new with m5 mac chip book"
}
}
}
}
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new with macbookm5 chip"
}
}
}
}
- 无命中响应
json
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
🙋♀️🌰2️⃣ "operator": "OR" 命中
- 请求
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new macbook with m5 chip"
}
}
}
}
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new air with m5 chip"
}
}
}
}
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new AIR with m5 Macbook chip"
}
}
}
}
- 响应
json
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.13076457,
"hits": [
{
"_index": "sensitive_keyword",
"_type": "_doc",
"_id": "6",
"_score": 0.13076457,
"_source": {
"name": "MacBook Air",
"reasion": "MacBook Air是苹果公司(Apple Inc.)注册的商标",
"trademark": "9",
"type_num": "0901,0907,0909,0913,0920",
"type": "3",
"special_mark": 1,
"site_name": "US",
"query": {
"match": {
"name": {
"query": "MacBook Air",
"operator": "OR"
}
}
}
},
"fields": {
"_percolator_document_slot": [
0
]
}
}
]
}
}
🙋♀️🌰3️⃣ "operator": "AND" 和 "operator": "OR"
- 请求
json
POST /sensitive_keyword/_search
{
"query": {
"percolate": {
"field": "query",
"document": {
"name": "new PRo with m5 Macbook chip"
}
}
}
}
- 响应
json
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.26152915,
"hits": [
{
"_index": "sensitive_keyword",
"_type": "_doc",
"_id": "5",
"_score": 0.26152915,
"_source": {
"name": "MacBook Pro",
"reasion": "MacBook Pro是苹果公司(Apple Inc.)注册的商标",
"trademark": "9",
"type_num": "0901,0907,0909,0913,0920",
"type": "3",
"special_mark": 1,
"site_name": "US",
"query": {
"match": {
"name": {
"query": "MacBook Pro",
"operator": "AND"
}
}
}
},
"fields": {
"_percolator_document_slot": [
0
]
}
},
{
"_index": "sensitive_keyword",
"_type": "_doc",
"_id": "6",
"_score": 0.13076457,
"_source": {
"name": "MacBook Air",
"reasion": "MacBook Air是苹果公司(Apple Inc.)注册的商标",
"trademark": "9",
"type_num": "0901,0907,0909,0913,0920",
"type": "3",
"special_mark": 1,
"site_name": "US",
"query": {
"match": {
"name": {
"query": "MacBook Air",
"operator": "OR"
}
}
}
},
"fields": {
"_percolator_document_slot": [
0
]
}
}
]
}
}
参考链接
- 官方文档
- Elasticsearch:理解 Percolator 数据类型及 Percolate 查询
- Elasticsearch(ES) 创建索引
- Percolator查询
- 你用过Elasticsearch Percolate 反向检索吗?
- Elasticsearch反向搜索查询
- ES在运维监控领域的其他玩法 - percolator接口
- elasticsearch 的 Percolator操作
ps:如有错误,欢迎批评指正,谢谢!