渗透器字段类型编辑

percolator 字段类型将 JSON 结构解析为原生查询并存储该查询,以便 渗透查询 可以使用它来匹配提供的文档。

任何包含 JSON 对象的字段都可以配置为渗透器字段。渗透器字段类型没有设置。只需配置 percolator 字段类型就足以指示 Elasticsearch 将字段视为查询。

如果以下映射为 query 字段配置了 percolator 字段类型

response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        query: {
          type: 'percolator'
        },
        field: {
          type: 'text'
        }
      }
    }
  }
)
puts response
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "query": {
        "type": "percolator"
      },
      "field": {
        "type": "text"
      }
    }
  }
}

那么您可以索引一个查询

response = client.index(
  index: 'my-index-000001',
  id: 'match_value',
  body: {
    query: {
      match: {
        field: 'value'
      }
    }
  }
)
puts response
PUT my-index-000001/_doc/match_value
{
  "query": {
    "match": {
      "field": "value"
    }
  }
}

渗透器查询中引用的字段必须 已经 存在于与用于渗透的索引关联的映射中。为了确保这些字段存在,请通过 创建索引更新映射 API 添加或更新映射。

重新索引您的渗透器查询编辑

有时需要重新索引渗透器查询,以便从新版本中对 percolator 字段类型的改进中受益。

可以使用 重新索引 API 重新索引渗透器查询。让我们看一下以下具有渗透器字段类型的索引

response = client.indices.create(
  index: 'index',
  body: {
    mappings: {
      properties: {
        query: {
          type: 'percolator'
        },
        body: {
          type: 'text'
        }
      }
    }
  }
)
puts response

response = client.indices.update_aliases(
  body: {
    actions: [
      {
        add: {
          index: 'index',
          alias: 'queries'
        }
      }
    ]
  }
)
puts response

response = client.index(
  index: 'queries',
  id: 1,
  refresh: true,
  body: {
    query: {
      match: {
        body: 'quick brown fox'
      }
    }
  }
)
puts response
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"
    }
  }
}

始终建议为您的索引定义别名,以便在重新索引的情况下,系统/应用程序无需更改即可知道渗透器查询现在位于不同的索引中。

假设您要升级到新的主要版本,并且为了使新的 Elasticsearch 版本仍然能够读取您的查询,您需要在当前的 Elasticsearch 版本上将您的查询重新索引到一个新的索引中

response = client.indices.create(
  index: 'new_index',
  body: {
    mappings: {
      properties: {
        query: {
          type: 'percolator'
        },
        body: {
          type: 'text'
        }
      }
    }
  }
)
puts response

response = client.reindex(
  refresh: true,
  body: {
    source: {
      index: 'index'
    },
    dest: {
      index: 'new_index'
    }
  }
)
puts response

response = client.indices.update_aliases(
  body: {
    actions: [
      {
        remove: {
          index: 'index',
          alias: 'queries'
        }
      },
      {
        add: {
          index: 'new_index',
          alias: 'queries'
        }
      }
    ]
  }
)
puts response
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 查询

response = client.search(
  index: 'queries',
  body: {
    query: {
      percolate: {
        field: 'query',
        document: {
          body: 'fox jumps over the lazy dog'
        }
      }
    }
  }
)
puts response
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", 
        "_id": "1",
        "_score": 0.13076457,
        "_source": {
          "query": {
            "match": {
              "body": "quick brown fox"
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      }
    ]
  }
}

渗透器查询命中现在从新索引中呈现。

优化查询时文本分析编辑

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

为了避免在渗透时进行最昂贵的文本分析部分。可以选择在索引渗透器查询时进行昂贵的文本分析部分。这需要使用两个不同的分析器。第一个分析器实际执行需要执行的文本分析(昂贵的部分)。第二个分析器(通常是空格)只是拆分第一个分析器生成的标记。然后,在索引渗透器查询之前,应使用分析 API 使用更昂贵的分析器分析查询文本。分析 API 的结果(标记)应用于替换渗透器查询中的原始查询文本。重要的是,现在应该将查询配置为覆盖映射中的分析器,而只使用第二个分析器。大多数基于文本的查询都支持 analyzer 选项(matchquery_stringsimple_query_string)。使用这种方法,昂贵的文本分析只执行一次,而不是多次。

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

假设我们要索引以下渗透器查询

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

使用以下设置和映射

response = client.indices.create(
  index: 'test_index',
  body: {
    settings: {
      analysis: {
        analyzer: {
          my_analyzer: {
            tokenizer: 'standard',
            filter: [
              'lowercase',
              'porter_stem'
            ]
          }
        }
      }
    },
    mappings: {
      properties: {
        query: {
          type: 'percolator'
        },
        body: {
          type: 'text',
          analyzer: 'my_analyzer'
        }
      }
    }
  }
)
puts response
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" 
      }
    }
  }
}

在本例中,此分析器被认为是昂贵的。

首先,我们需要使用分析 API 在索引之前执行文本分析

response = client.indices.analyze(
  index: 'test_index',
  body: {
    analyzer: 'my_analyzer',
    text: 'missing bicycles'
  }
)
puts response
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
    }
  ]
}

返回顺序中的所有标记都需要替换渗透器查询中的查询文本

response = client.index(
  index: 'test_index',
  id: 1,
  refresh: true,
  body: {
    query: {
      match: {
        body: {
          query: 'miss bicycl',
          analyzer: 'whitespace'
        }
      }
    }
  }
)
puts response
PUT /test_index/_doc/1?refresh
{
  "query" : {
    "match" : {
      "body" : {
        "query" : "miss bicycl",
        "analyzer" : "whitespace" 
      }
    }
  }
}

在这里选择空格分析器很重要,否则将使用映射中定义的分析器,这违背了使用此工作流程的初衷。请注意,whitespace 是一个内置分析器,如果需要使用其他分析器,则需要先在索引的设置中配置它。

在索引渗透器流之前,应为每个渗透器查询执行分析 API。

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

response = client.search(
  index: 'test_index',
  body: {
    query: {
      percolate: {
        field: 'query',
        document: {
          body: 'Bycicles are missing'
        }
      }
    }
  }
)
puts response
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",
        "_id": "1",
        "_score": 0.13076457,
        "_source": {
          "query": {
            "match": {
              "body": {
                "query": "miss bicycl",
                "analyzer": "whitespace"
              }
            }
          }
        },
        "fields" : {
          "_percolator_document_slot" : [0]
        }
      }
    ]
  }
}

优化通配符查询。编辑

对于渗透器来说,通配符查询比其他查询更昂贵,特别是当通配符表达式很大时。

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

创建具有自定义分析设置的索引

response = client.indices.create(
  index: 'my_queries1',
  body: {
    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'
            }
          }
        }
      }
    }
  }
)
puts response
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 查询而不是 prefixwildcard 查询进行前缀搜索。

然后,应该索引以下查询,而不是索引以下查询

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

下面的这个查询

response = client.index(
  index: 'my_queries1',
  id: 1,
  refresh: true,
  body: {
    query: {
      term: {
        'my_field.prefix' => 'abc'
      }
    }
  }
)
puts response
PUT /my_queries1/_doc/1?refresh
{
  "query": {
    "term": {
      "my_field.prefix": "abc"
    }
  }
}

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

以下搜索请求将与先前索引的渗透器查询匹配

response = client.search(
  index: 'my_queries1',
  body: {
    query: {
      percolate: {
        field: 'query',
        document: {
          my_field: 'abcd'
        }
      }
    }
  }
)
puts response
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",
        "_id": "1",
        "_score": 0.18864399,
        "_source": {
          "query": {
            "term": {
              "my_field.prefix": "abc"
            }
          }
        },
        "fields": {
          "_percolator_document_slot": [
            0
          ]
        }
      }
    ]
  }
}

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

response = client.indices.create(
  index: 'my_queries2',
  body: {
    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'
            }
          }
        }
      }
    }
  }
)
puts response
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"
    }
  }
}

应该索引以下查询

response = client.index(
  index: 'my_queries2',
  id: 2,
  refresh: true,
  body: {
    query: {
      match: {
        'my_field.suffix' => 'xyz'
      }
    }
  }
)
puts response
PUT /my_queries2/_doc/2?refresh
{
  "query": {
    "match": { 
      "my_field.suffix": "xyz"
    }
  }
}

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

以下搜索请求将与先前索引的渗透器查询匹配

response = client.search(
  index: 'my_queries2',
  body: {
    query: {
      percolate: {
        field: 'query',
        document: {
          my_field: 'wxyz'
        }
      }
    }
  }
)
puts response
GET /my_queries2/_search
{
  "query": {
    "percolate": {
      "field": "query",
      "document": {
        "my_field": "wxyz"
      }
    }
  }
}

专用渗透器索引编辑

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

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

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

限制编辑

父子关系编辑

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

获取查询编辑

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

脚本查询编辑

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

字段别名编辑

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