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

percolator(渗透器)数据类型

percolator字段类型将一个 json 结构解析成一个本地查询并存储该查询,以便percolate查询可以使用它来匹配提供的文档。

任何包含 json 对象的字段都可以被配置为一个 percolator 字段。 只需配置percolator字段类型就足以指示 Elasticsearch 将字段视为查询。

如果下面这个映射将query字段配置为percolator字段类型:

PUT my_index
{
    "mappings": {
        "properties": {
            "query": {
                "type": "percolator"
            },
            "field": {
                "type": "text"
            }
        }
    }
}

然后你就可以索引一个查询:

PUT my_index/_doc/match_value
{
    "query" : {
        "match" : {
            "field" : "value"
        }
    }
}

percolator 查询中引用的字段必须已经存在于与用于渗透的索引相关联的映射中。 为了确保这些字段存在,请通过创建索引(create index)设置映射(put mapping) API添加或更新映射。

重新索引 percolator 查询

有时需要重新索引 percolator 查询来从新版本中对percolator字段类型的改进中获益。

重新索引 percolator 查询可以通过使用 reindex api 来重新索引。 让我们来看看下面的一个带有 percolator 类型字段的索引:

PUT /index
{
  "mappings": {
    "properties": {
      "query" : {
        "type" : "percolator"
      },
      "body" : {
        "type": "text"
      }
    }
  }
}

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "index",
        "alias": "queries" 
      }
    }
  ]
}

PUT /queries/_doc/1?refresh
{
  "query" : {
    "match" : {
      "body" : "quick brown fox"
    }
  }
}

总是建议为索引定义一个别名,这样在重新索引的情况下,系统/应用程序不需要改变就知道 percolator 查询现在是在一个不同的索引中。

假设要升级到新的 Elasticsearch 主版本,为了让新版本仍然能够读取你的查询,你需要将你的查询重新索引到当前 Elasticsearch 版本的新索引中:

PUT /new_index
{
  "mappings": {
    "properties": {
      "query" : {
        "type" : "percolator"
      },
      "body" : {
        "type": "text"
      }
    }
  }
}

POST /_reindex?refresh
{
  "source": {
    "index": "index"
  },
  "dest": {
    "index": "new_index"
  }
}

POST /_aliases
{
  "actions": [ 
    {
      "remove": {
        "index" : "index",
        "alias": "queries"
      }
    },
    {
      "add": {
        "index": "new_index",
        "alias": "queries"
      }
    }
  ]
}

如果有一个别名,不要忘记将它指向新的索引。

通过queries别名执行percolate查询:

GET /queries/_search
{
  "query": {
    "percolate" : {
      "field" : "query",
      "document" : {
        "body" : "fox jumps over the lazy dog"
      }
    }
  }
}

现在从新索引中返回了匹配的项:

{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.13076457,
    "hits": [
      {
        "_index": "new_index", 
        "_type": "_doc",
        "_id": "1",
        "_score": 0.13076457,
        "_source": {
          "query": {
            "match": {
              "body": "quick brown fox"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      }
    ]
  }
}

percolator 查询的命中现在呈现在新的索引中。

优化查询时的文本分析

当 percolator 验证 percolator 候选匹配时,它将进行解析,执行查询时文本分析,并在被渗透的文档上实际运行 percolator 查询。 每次执行percolate查询时,都会对每个候选匹配进行这样的操作。 如果查询时的文本分析是查询解析中相对昂贵的部分,那么文本分析可能成为渗透时花费时间的主要因素。 当 percolator 最终验证了许多候选 percolator 查询匹配时,这种查询解析开销会变得很明显。

为了避免在渗透时进行最昂贵的文本分析,可以选择在索引 percolator 查询时进行高昂的文本分析。 这个 requires 使用两个不同的分析器。 第一个分析器实际上执行需要执行的文本分析(代价高的部分)。 第二个分析器(通常是“空格”)只是拆分第一个分析器生成的词元(token)。 然后,在索引 percolator 查询之前,应该使用 analyze api 通过更昂贵的分析器来分析查询文本。 analyze api的结果,即词元(token),应该用来替换 percolator 查询中的原始查询文本。 现在应该配置查询以覆盖映射中的分析器,并且只覆盖第二个分析器,这一点很重要。 大多数基于文本的查询支持一个analyzer选项(matchquery_stringsimple_query_string)。 使用这种方法,昂贵的文本分析只需要执行一次,而不是多次。

让我们通过一个简单的例子来演示这个工作流程。

假设我们想要索引下面这个 percolator 查询:

{
  "query" : {
    "match" : {
      "body" : {
        "query" : "missing bicycles"
      }
    }
  }
}

使用这些设置和映射:

PUT /test_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer" : {
          "tokenizer": "standard",
          "filter" : ["lowercase", "porter_stem"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "query" : {
        "type": "percolator"
      },
      "body" : {
        "type": "text",
        "analyzer": "my_analyzer" 
      }
    }
  }
}

出于这个例子的目的,这个分析器被认为是昂贵的。

首先,我们需要使用 analyze api 在编制索引之前执行文本分析:

POST /test_index/_analyze
{
  "analyzer" : "my_analyzer",
  "text" : "missing bicycles"
}

这会导致以下响应:

{
  "tokens": [
    {
      "token": "miss",
      "start_offset": 0,
      "end_offset": 7,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "bicycl",
      "start_offset": 8,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 1
    }
  ]
}

所有词元(token)都需要以返回的顺序去替换 percolator 查询中的查询文本:

PUT /test_index/_doc/1?refresh
{
  "query" : {
    "match" : {
      "body" : {
        "query" : "miss bicycl",
        "analyzer" : "whitespace" 
      }
    }
  }
}

在这里选择一个 空格(whitespace)分析器 是很重要的,否则将使用映射中定义的分析器,而这就失去了使用这个工作流的意义。 请注意,whitespace是一个内置的分析器,如果需要使用不同的分析器,需要先在索引的设置中进行配置。

在索引 percolator 流程之前,应该为每个 percolator 查询执行 analyze api。

在渗透时没有任何变化,percolate查询可以正常定义:

GET /test_index/_search
{
  "query": {
    "percolate" : {
      "field" : "query",
      "document" : {
        "body" : "Bycicles are missing"
      }
    }
  }
}

这将导致如下响应:

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.13076457,
    "hits": [
      {
        "_index": "test_index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.13076457,
        "_source": {
          "query": {
            "match": {
              "body": {
                "query": "miss bicycl",
                "analyzer": "whitespace"
              }
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      }
    ]
  }
}

优化通配符查询

对于 percolator 来说,通配符查询比其他查询开销更大,尤其是在通配符表达式很大的情况下。

在使用前缀通配符表达式的 wildcard(通配符) 查询或仅使用 prefix(前缀) 查询的情况下,edge_ngram词元过滤器可用于在配置了edge_ngram词元过滤器的字段上将这些查询替换为常规term查询。

使用自定义的 analysis 设置来创建一个索引:

PUT my_queries1
{
  "settings": {
    "analysis": {
      "analyzer": {
        "wildcard_prefix": { 
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "wildcard_edge_ngram"
          ]
        }
      },
      "filter": {
        "wildcard_edge_ngram": { 
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 32
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "query": {
        "type": "percolator"
      },
      "my_field": {
        "type": "text",
        "fields": {
          "prefix": { 
            "type": "text",
            "analyzer": "wildcard_prefix",
            "search_analyzer": "standard"
          }
        }
      }
    }
  }
}

生成仅在索引时使用的前缀词元的分析器。

根据前缀搜索需求,增加min_gram,减少max_gram设置。

此多字段应用于使用 termmatch 查询进行前缀搜索,而不是prefix(前缀) 或 wildcard(通配符)查询。

然后,不要索引以下查询:

{
  "query": {
    "wildcard": {
      "my_field": "abc*"
    }
  }
}

而应该对以下查询进行索引:

PUT /my_queries1/_doc/1?refresh
{
  "query": {
    "term": {
      "my_field.prefix": "abc"
    }
  }
}

这种方式可以使得第二个查询的处理比第一个查询更有效。

下面这个搜索请求将与先前索引的 percolator 查询相匹配:

GET /my_queries1/_search
{
  "query": {
    "percolate": {
      "field": "query",
      "document": {
        "my_field": "abcd"
      }
    }
  }
}
{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.18864399,
    "hits": [
      {
        "_index": "my_queries1",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.18864399,
        "_source": {
          "query": {
            "term": {
              "my_field.prefix": "abc"
            }
          }
        },
        "fields": {
          "_percolator_document_slot": [
            0
          ]
        }
      }
    ]
  }
}

同样的技术也可以用来加速后缀通配符搜索。 通过在edge_ngram词元过滤器之前使用reverse词元过滤器:

PUT my_queries2
{
  "settings": {
    "analysis": {
      "analyzer": {
        "wildcard_suffix": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "reverse",
            "wildcard_edge_ngram"
          ]
        },
        "wildcard_suffix_search_time": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "reverse"
          ]
        }
      },
      "filter": {
        "wildcard_edge_ngram": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 32
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "query": {
        "type": "percolator"
      },
      "my_field": {
        "type": "text",
        "fields": {
          "suffix": {
            "type": "text",
            "analyzer": "wildcard_suffix",
            "search_analyzer": "wildcard_suffix_search_time" 
          }
        }
      }
    }
  }
}

在搜索时也需要一个自定义的分析器,否则查询的词项不会被反转,也不会与保留的后缀词元匹配。

然后,不要索引以下查询:

{
  "query": {
    "wildcard": {
      "my_field": "*xyz"
    }
  }
}

而应该要索引下面这个查询:

PUT /my_queries2/_doc/2?refresh
{
  "query": {
    "match": { 
      "my_field.suffix": "xyz"
    }
  }
}

应该使用match查询而不是term查询,因为文本分析需要反转查询的词项。

下面这个搜索请求将与先前索引的 percolator 查询相匹配:

GET /my_queries2/_search
{
  "query": {
    "percolate": {
      "field": "query",
      "document": {
        "my_field": "wxyz"
      }
    }
  }
}

专用的渗透器索引

渗透查询可以添加到任何索引中。 除了将渗透查询添加到数据所在的索引中,还可以将这些查询添加到专用的索引中。 这样做的好处是,这个专用的 percolator 索引可以有自己的索引设置(例如主分片和副本分片的数量)。 如果你选择使用专用的渗透索引,需要确保普通索引的映射在渗透索引上也是可用的。 否则渗透查询可能会被错误地解析。

强制将未映射的字段作为字符串处理

在某些情况下,不知道注册了哪种类型的 percolator 查询,如果 percolator 查询引用的字段没有映射,则添加 percolator 查询会失败。 这意味着需要更新映射,使字段具有适当的设置,然后可以添加 percolator 查询。 但有时,如果所有未映射的字段都被当做默认文本字段来处理,这就足够了。 在这些情况下,可以将index.percolator.map_unmapped_fields_as_text设置配置为true(默认为false),然后如果 percolator 查询中引用的字段不存在,它将被作为默认文本字段处理,以便添加 percolator 查询时不会失败。

局限性

父-子

因为percolate查询一次处理一个文档,所以它不支持针对子文档(如has_childhas_parent)运行的查询和过滤器。

获取查询

有许多查询在查询解析期间通过 GET 调用获取数据。 例如,使用词项查找时的terms查询、使用索引脚本时的template查询以及使用预索引形状时的geo_shape。 当这些查询被percolator字段类型索引时,GET 调用执行一次。 因此,每次percolator查询评估这些查询时,都会获取词项、形状等。 需要注意的一个重要的点是,每次 percolator 查询在主分片和副本分片上被索引时,都会发生获取词项的查询,因此,如果在建立索引时源索引发生了变化,那么在分片副本之间实际建立索引的词项可能会有所不同。

脚本查询

script查询中的脚本只能访问文档值字段。 percolate查询将提供的文档编入内存索引。 此内存索引不支持 存储字段,因此_source字段和其他存储字段不会被存储。 这就是script查询中_source和其他存储字段不可用的原因。

字段别名

包含字段别名的渗透查询可能并不总是如预期的那样运行。 特别是,如果注册了包含字段别名的 percolator 查询,然后在映射中更新该别名以引用不同的字段,则存储的查询仍将引用原始的目标字段。 要获取字段别名的更改,必须显式地重新索引 percolator 查询。