原英文版地址: https://www.elastic.co/guide/en/elasticsearch/reference/7.7/query-dsl-percolate-query.html, 原文档版权归 www.elastic.co 所有
本地英文版地址: ../en/query-dsl-percolate-query.html

percolate 查询

percolate查询可用于匹配存储在索引中的查询。 percolate 查询本身包含将用作查询的文档,以匹配存储的查询。

使用示例

创建一个有2个字段的索引:

PUT /my-index
{
    "mappings": {
        "properties": {
             "message": {
                 "type": "text"
             },
             "query": {
                 "type": "percolator"
             }
        }
    }
}

message 字段是用于在将 percolator 查询中定义的文档编入临时索引之前对其进行预处理的字段。

query 字段用于索引查询文档。 它将保存一个 json 对象,表示一个实际的 Elasticsearch 查询。 字段 query 已在映射中配置为使用percolator字段类型。 这个字段类型理解查询领域专用语言(dsl),并以这样的方式存储查询,以便以后可以使用它来匹配在 percolate 查询中定义的文档。

在 percolator 中注册一个查询(相当于添加并索引一个id为1的文档)

PUT /my-index/_doc/1?refresh
{
    "query" : {
        "match" : {
            "message" : "bonsai tree"
        }
    }
}

将文档与注册的 percolator 查询进行匹配:

GET /my-index/_search
{
    "query" : {
        "percolate" : {
            "field" : "query",
            "document" : {
                "message" : "A new bonsai tree in the office"
            }
        }
    }
}

上面这个请求将产生如下响应:

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.26152915,
    "hits": [
      { 
        "_index": "my-index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.26152915,
        "_source": {
          "query": {
            "match": {
              "message": "bonsai tree"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0] 
        }
      }
    ]
  }
}

id 为 1 的 query 与文档匹配。

字段 _percolator_document_slot 指示哪个文档与该查询匹配。 在同时渗透(percolating)多个文档时非常有用。

为了举一个简单的例子,这个文档为 percolate 查询和文档使用一个名为 my-index 的索引。 当只注册了几个 percolate 查询时,这种设置可以很好地工作。 但是,如果使用量较大,建议将查询和文档存储在单独的索引中。 更多细节请参考 它如何在引擎盖下工作的

参数

渗透(percolating)文档时需要以下参数:

field

(必需) 保存索引查询的 percolator 类型的字段。

name

(可选) 在指定了多个 percolate 查询的情况下,用于 _percolator_document_slot 字段的后缀。

document

被渗透的文档源。

documents

类似于 document 参数,但是可以通过 json 数组接受多个文档。

document_type

被渗透的文档的类型(type)/映射(mapping)。此参数已被废弃,将在 Elasticsearch 8.0 中移除。

也可以从已经存储的文档中检索源,而不是指定要渗透的文档源。 然后,percolate 查询将在内部执行一个 get 请求来获取文档。

在这种情况下,document 参数可以用以下参数替换:

index

(必需) 文档所在的索引。

type

要获取的文档的类型。此参数已被废弃,将在 Elasticsearch 8.0 中移除。

id

(必需) 要获取的文档的 id。

routing

(可选) 用于获取要渗透(percolate)的文档的路由。

preference

(可选) 用于获取要渗透的文档的首选项。

version

(可选) 预期要获取的文档的版本。

在过滤上下文中渗透(percolating in a filter context)

如果你对相关性评分不感兴趣,可以通过将 percolator 查询包裹在 bool 查询的 filter 子句或 constant_score 查询中来获得更好的性能:

GET /my-index/_search
{
    "query" : {
        "constant_score": {
            "filter": {
                "percolate" : {
                    "field" : "query",
                    "document" : {
                        "message" : "A new bonsai tree in the office"
                    }
                }
            }
        }
    }
}

在索引时,从渗透器(percolator) query 中提取 词项(term),渗透器通常可以通过查看这些提取的词项来确定查询是否匹配。 然而,计算得分需要对每个匹配的查询进行反序列化,并在渗透后的文档上运行,这是一个成本很高的操作。 因此,如果不需要计算得分,则 percolate 查询应该包裹在一个 constant_score 查询或 bool 查询的 filter 子句中。

请注意,查询缓存永远不会缓存 percolate 查询。

渗透多个文档 (percolating multiple documents)

percolate 查询可以用索引过的渗透查询同时匹配多个文档。 在单个请求中渗透多个文档可以提高性能,因为查询只需要解析和匹配一次,而不是多次。

同时渗透多个文档时,每个匹配的渗透器查询返回的 _percolator_document_slot 字段非常重要。 它指示哪些文档与特定的渗透器查询相匹配。 这些数字与 percolate 查询中指定的 documents 数组中的槽相关。

GET /my-index/_search
{
    "query" : {
        "percolate" : {
            "field" : "query",
            "documents" : [ 
                {
                    "message" : "bonsai tree"
                },
                {
                    "message" : "new tree"
                },
                {
                    "message" : "the office"
                },
                {
                    "message" : "office tree"
                }
            ]
        }
    }
}

documents 数组包含4个将被同时渗透的文档。

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.7093853,
    "hits": [
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.7093853,
        "_source": {
          "query": {
            "match": {
              "message": "bonsai tree"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0, 1, 3] 
        }
      }
    ]
  }
}

_percolator_document_slot 表示在 percolate 查询中指定的第一个、第二个和最后一个文档与该查询匹配。

渗透一个已存在的文档 (percolating an existing document)

为了渗透新索引的文档,可以使用 percolate 查询。 基于来自索引请求的响应,可以使用 _id 和其他元信息来立即渗透新添加的文档。

示例

基于前面的例子。

索引要渗透的文档:

PUT /my-index/_doc/2
{
  "message" : "A new bonsai tree in the office"
}

索引响应:

{
  "_index": "my-index",
  "_type": "_doc",
  "_id": "2",
  "_version": 1,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "result": "created",
  "_seq_no" : 1,
  "_primary_term" : 1
}

渗透现有文档,使用索引响应作为基础来构建新的搜索请求:

GET /my-index/_search
{
    "query" : {
        "percolate" : {
            "field": "query",
            "index" : "my-index",
            "id" : "2",
            "version" : 1 
        }
    }
}

version 是可选的,但在某些情况下很有用。 我们可以确保我们正在尝试渗透我们刚刚索引的文档。 在我们建立索引后,可能会进行更改,如果是这种情况,搜索请求将会失败,并抛出版本的冲突错误。

返回的搜索响应与前面的示例相同。

percolate 查询以及高亮

当需要高亮显示时,percolate 查询以一种特殊的方式处理。 查询命中用于高亮 percolate 查询中提供的文档。 而对于常规高亮显示,搜索请求中的查询用于高亮显示命中。

示例

这个示例基于第一个示例中的mapping。

保存一个查询:

PUT /my-index/_doc/3?refresh
{
    "query" : {
        "match" : {
            "message" : "brown fox"
        }
    }
}

保存另外一个查询:

PUT /my-index/_doc/4?refresh
{
    "query" : {
        "match" : {
            "message" : "lazy dog"
        }
    }
}

在启用高亮显示的情况下执行 percolate 查询搜索请求:

GET /my-index/_search
{
    "query" : {
        "percolate" : {
            "field": "query",
            "document" : {
                "message" : "The quick brown fox jumps over the lazy dog"
            }
        }
    },
    "highlight": {
      "fields": {
        "message": {}
      }
    }
}

这将产生以下响应:

{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 2,
        "relation": "eq"
    },
    "max_score": 0.26152915,
    "hits": [
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "3",
        "_score": 0.26152915,
        "_source": {
          "query": {
            "match": {
              "message": "brown fox"
            }
          }
        },
        "highlight": {
          "message": [
            "The quick <em>brown</em> <em>fox</em> jumps over the lazy dog" 
          ]
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      },
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "4",
        "_score": 0.26152915,
        "_source": {
          "query": {
            "match": {
              "message": "lazy dog"
            }
          }
        },
        "highlight": {
          "message": [
            "The quick brown fox jumps over the <em>lazy</em> <em>dog</em>" 
          ]
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      }
    ]
  }
}

文档中高亮显示了每个查询中的词项。

不是搜索请求中的查询高亮显示渗透器命中,而是渗透器查询高亮显示 percolate 查询中定义的文档。

当像下面的请求一样同时渗透多个文档时,高亮显示的响应是不同的:

GET /my-index/_search
{
    "query" : {
        "percolate" : {
            "field": "query",
            "documents" : [
                {
                    "message" : "bonsai tree"
                },
                {
                    "message" : "new tree"
                },
                {
                    "message" : "the office"
                },
                {
                    "message" : "office tree"
                }
            ]
        }
    },
    "highlight": {
      "fields": {
        "message": {}
      }
    }
}

响应略有不同,如下所示:

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.7093853,
    "hits": [
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.7093853,
        "_source": {
          "query": {
            "match": {
              "message": "bonsai tree"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0, 1, 3]
        },
        "highlight" : { 
          "0_message" : [
              "<em>bonsai</em> <em>tree</em>"
          ],
          "3_message" : [
              "office <em>tree</em>"
          ],
          "1_message" : [
              "new <em>tree</em>"
          ]
        }
      }
    ]
  }
}

高亮显示的字段已经以它们所属的文档槽(slot)为前缀,以便知道哪个高亮显示的字段属于哪个文档。

指定多个 percolate 查询

可以在单个搜索请求中指定多个 percolate 查询:

GET /my-index/_search
{
    "query" : {
        "bool" : {
            "should" : [
                {
                    "percolate" : {
                        "field" : "query",
                        "document" : {
                            "message" : "bonsai tree"
                        },
                        "name": "query1" 
                    }
                },
                {
                    "percolate" : {
                        "field" : "query",
                        "document" : {
                            "message" : "tulip flower"
                        },
                        "name": "query2" 
                    }
                }
            ]
        }
    }
}

name 参数将用于识别哪个 percolator 文档槽属于哪个 percolate 查询。

_percolator_document_slot 字段名将以参数 _name 中指定的内容作为后缀。 如果没有指定,那么将使用参数 field,在这种情况下会产生歧义。

上面的搜索请求返回类似于以下内容的响应:

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.26152915,
    "hits": [
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.26152915,
        "_source": {
          "query": {
            "match": {
              "message": "bonsai tree"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot_query1" : [0] 
        }
      }
    ]
  }
}

_percolator_document_slot_query1 渗透器槽字段指示这些匹配的槽(slot)来自 _name 参数设置为 query1percolate 查询。

它是如何工作的?

当将文档索引到配置了percolator字段类型映射的索引中时,文档的查询部分被解析成 Lucene 查询并存储到 Lucene 索引中。 存储查询的二进制表示,而且还分析查询的词项并存储到一个索引字段中。

在搜索时,请求中指定的文档被解析成 Lucene 文档,并存储在内存中的临时 Lucene 索引中。 这个内存索引只能保存这一个文档,它为此进行了优化。 在此之后,基于内存索引中的词项构建一个特殊的查询,该查询基于候选渗透器查询的索引查询词项来选择候选渗透器查询。 然后,如果这些查询确实匹配,则由内存中的索引对它们进行评估。

候选渗透器查询匹配的选择是 percolate 查询执行期间的重要性能优化,因为它可以显著减少内存索引需要评估的候选匹配的数量。 percolate 查询可以这样做的原因是因为在渗透器查询的索引过程中,查询词项被提取并用渗透器查询进行索引。 不幸的是,渗透器无法从所有查询中提取词项(例如wildcardgeo_shape查询),因此在某些情况下,渗透器无法进行选择优化(例如,如果在 bool 查询的必需的子句中定义了不支持的查询,或者不支持的查询是渗透器文档中唯一的查询)。 这些查询由渗透器标记,可以通过运行以下搜索找到:

GET /_search
{
  "query": {
    "term" : {
      "query.extraction_result" : "failed"
    }
  }
}

上面的例子假设在mapping中有一个 percolator 类型的 query 字段。

考虑到渗透的设计,对渗透查询和被渗透的文档使用单独的索引通常是有意义的,而不是像我们在示例中那样使用单一的索引。这种方法有几个好处: 这种方法有几个好处:

  • 因为 percolate 查询包含一组不同于被渗透的文档的字段,使用两个独立的索引允许以更密集、更有效的方式存储字段。
  • percolate 查询的可伸缩性与其他查询不同,因此使用不同的索引配置(如主分片的数量)可能有利于渗透性能。