提高性能

短语和邻近度查询比简单的match查询在性能上更昂贵。match查询仅仅是查看词条是否存在于倒排索引(Inverted Index)中,而match_phrase查询则须要计算和比較多个可能反复词条(Multiple possibly repeated)的位置。

Lucene Nightly Benchmarks中,显示了一个简单的term查询比一个短语查询快大概10倍,比一个邻近度查询(一个拥有slop的短语查询)快大概20倍。

当然,这个代价是在搜索期间而不是索引期间付出的。

TIP

通常,短语查询的额外代价并不像这些数字说的那么吓人。

实际上,性能上的差异仅仅是说明了一个简单的term查询时多么的快。在标准全文数据上进行的短语查询通常可以在数毫秒内完毕,因此它们在实际生产环境下是全然可以使用的,即使在一个繁忙的集群中。

在某些特定的场景下。短语查询可能会非常耗费资源。可是这样的情况时不常有的。

一个典型的样例是DNA序列,此时会在非常多位置上出现非常之多的同样反复词条。使用高slop值会使位置计算发生大幅度的增长。

因此,怎样可以限制短语和邻近度查询的性能消耗呢?一个实用的方法是降低须要使用短语查询进行检查的文档总数。

结果的分值重计算(Rescoring
Results)

在上一节中,我们讨论了使用邻近度查询来调整相关度,而不是使用它来将文档从结果列表中加入或者排除。

一个查询可能会匹配百万计的结果。可是我们的用户非常可能仅仅对前面几页结果有兴趣。

一个简单的match查询已经通过排序将含有全部搜索词条的文档放在结果列表的前面了。而我们仅仅想对这些前面的结果进行又一次排序来给予那些同一时候匹配了短语查询的文档额外的相关度。

search API通过分值重计算(Rescoring)来支持这一行为。在分值重计算阶段。你可以使用一个更加昂贵的分值计算算法 - 比方一个短语查询 - 来为每一个分片的前K个结果又一次计算其分值。紧接着这些结果就会按其新的分值又一次排序。

该请求例如以下所看到的:

GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown fox",
"minimum_should_match": "30%"
}
}
},
"rescore": {
"window_size": 50,
"query": {
"rescore_query": {
"match_phrase": {
"title": {
"query": "quick brown fox",
"slop": 50
}
}
}
}
}
}

match查询用来决定哪些文档会被包括在终于的结果集合中。结果通过TF/IDF进行排序。 window_size是每一个分片上须要又一次计算分值的数量。

寻找关联的单词(Finding Associated Words)

虽然短语和邻近度查询非常管用,它们还是有一个缺点。

它们过于严格了:全部的在短语查询中的词条都必须出如今文档中。即使使用了slop。

通过slop获得的可以调整单词顺序的灵活性也是有代价的,由于你失去了单词之间的关联。虽然你可以识别文档中的sue。alligator和ate出如今一块,可是你不能推断是Sue ate还是alligator ate。

当单词结合在一起使用时,它们表达的意思比单独使用时要丰富。

"I’m not happy I’m working"和"I’m happy I’m not working"含有同样的单词,也拥有相近的邻近度,可是它们的意思大相径庭。

假设我们索引单词对,而不是索引独立的单词,那么我们就行保留很多其它关于单词使用的上下文信息。

对于句子"Sue ate the alligator",我们不仅索引每一个单词(或者Unigram)为一个词条:

["sue", "ate", "the", "alligator"]

我们同一时候会将每一个单词和它的邻近单词一起索引成一个词条:

["sue ate", "ate the", "the alligator"]

这些单词对(也叫做Bigram)就是所谓的Shingle。

TIP

Shingle不限于仅仅是单词对;你也能够索引三个单词(Word Triplet,也被称为Trigram)作为一个词条:

["sue ate the", "ate the alligator"]

Trigram可以给你更高的精度,可是也大大地添加了索引的不同词条的数量。在多数情况下,Bigram就足够了。

当然。仅仅有当用户输入查询的顺序和原始文档的顺序一致,Shingle才可以起作用。一个针对sue alligator的查询会匹配单独的单词,可是不会匹配不论什么Shingle。

幸运的是,用户会倾向于使用和他们正在搜索的数据中相似的结构来表达查询。可是这是非常重要的一点:仅使用Bigram是不够的。我们仍然须要Unigram。我们能够将匹配Bigram作为信号(Signal)来添加相关度分值。

产生Shingle

Shingle须要在索引期间,作为分析过程的一部分被创建。我们可以将Unigram和Bigram都索引到一个字段中,可是将它们放在不同的字段中会更加清晰,也可以让它们可以被独立地查询。Unigram字段形成了我们搜索的基础部分,而Bigram字段则用来提升相关度。

首先。我们须要使用shingle词条过滤器来创建解析器:

DELETE /my_index

PUT /my_index
{
"settings": {
"number_of_shards": 1,
"analysis": {
"filter": {
"my_shingle_filter": {
"type": "shingle",
"min_shingle_size": 2,
"max_shingle_size": 2,
"output_unigrams": false
}
},
"analyzer": {
"my_shingle_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"my_shingle_filter"
]
}
}
}
}
}

默认Shingle的min/max值就是2。因此我们也能够不显式地指定它们。 output_unigrams被设置为false。用来避免将Unigram和Bigram索引到同样字段中。

让我们使用analyze API来測试该解析器:

GET /my_index/_analyze?analyzer=my_shingle_analyzer
Sue ate the alligator

不出所料,我们得到了3个词条:

  • sue ate
  • ate the
  • the alligator

如今我们就能够创建一个使用新解析器的字段了。

多字段(Multifields)

将Unigram和Bigram分开索引会更加清晰,因此我们将title字段创建成一个多字段(Multifield)(參见字符串排序和多字段(String
Sorting and Multifields)
):

PUT /my_index/_mapping/my_type
{
"my_type": {
"properties": {
"title": {
"type": "string",
"fields": {
"shingles": {
"type": "string",
"analyzer": "my_shingle_analyzer"
}
}
}
}
}
}

有了上述映射,JSON文档中的title字段会以Unigram(title字段)和Bigram(title.shingles字段)的方式索引,从而让我们能够独立地对这两个字段进行查询。

最后,我们能够索引演示样例文档:

POST /my_index/my_type/_bulk
{ "index": { "_id": 1 }}
{ "title": "Sue ate the alligator" }
{ "index": { "_id": 2 }}
{ "title": "The alligator ate Sue" }
{ "index": { "_id": 3 }}
{ "title": "Sue never goes anywhere without her alligator skin purse" }

搜索Shingles

为了理解加入的shingles字段的优点,让我们首先看看一个针对"The hungry alligator ate Sue"的简单match查询的返回结果:

GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "the hungry alligator ate sue"
}
}
}

该查询会返回全部的3份文档。可是注意文档1和文档2拥有同样的相关度分值,由于它们含有同样的单词:

{
"hits": [
{
"_id": "1",
"_score": 0.44273707,
"_source": {
"title": "Sue ate the alligator"
}
},
{
"_id": "2",
"_score": 0.44273707,
"_source": {
"title": "The alligator ate Sue"
}
},
{
"_id": "3",
"_score": 0.046571054,
"_source": {
"title": "Sue never goes anywhere without her alligator skin purse"
}
}
]
}

如今让我们将shingles字段也加入到查询中。记住我们会将shingle字段作为信号 - 以添加相关度分值 - 我们仍然须要将基本的title字段包括到查询中:

GET /my_index/my_type/_search
{
"query": {
"bool": {
"must": {
"match": {
"title": "the hungry alligator ate sue"
}
},
"should": {
"match": {
"title.shingles": "the hungry alligator ate sue"
}
}
}
}
}

我们仍然匹配了3分文档。可是文档2如今排在了第一位,由于它匹配了Shingle词条"ate sue":

{
"hits": [
{
"_id": "2",
"_score": 0.4883322,
"_source": {
"title": "The alligator ate Sue"
}
},
{
"_id": "1",
"_score": 0.13422975,
"_source": {
"title": "Sue ate the alligator"
}
},
{
"_id": "3",
"_score": 0.014119488,
"_source": {
"title": "Sue never goes anywhere without her alligator skin purse"
}
}
]
}

即使在查询中包括了没有在不论什么文档中出现的单词hungry,我们仍然通过使用单词邻近度得到了最相关的文档。

性能

Shingle不仅比短语查询更灵活,它们的性能也更好。相比每次搜索须要为短语查询付出的代价,对Shingle的查询和简单match查询一样的高效。仅仅是在索引期间会付出一点小代价,由于很多其它的词条须要被索引,意味着使用了Shingle的字段也会占用很多其它的磁盘空间。可是,多数应用是写入一次读取多次的,因此在索引期间花费一点代价来让查询更迅速是有意义的。

这是一个你在ES中常常会碰到的主题:让你在搜索期间可以做非常多事情,而不须要不论什么预先的设置。

一旦更好地了解了你的需求,就行通过在索引期间正确地建模来得到更好的结果和更好的性能。

[Elasticsearch] 邻近匹配 (三) - 性能,关联单词查询以及Shingles的更多相关文章

  1. elasticsearch 关联单词查询以及Shingles

    Shingle Token Filter A token filter of type shingle that constructs shingles (token n-grams) from a ...

  2. [Elasticsearch] 邻近匹配 (一) - 短语匹配以及slop參数

    本文翻译自Elasticsearch官方指南的Proximity Matching一章. 邻近匹配(Proximity Matching) 使用了TF/IDF的标准全文搜索将文档,或者至少文档中的每一 ...

  3. [Elasticsearch] 部分匹配 (三) - 查询期间的即时搜索

    本章翻译自Elasticsearch官方指南的Partial Matching一章. 查询期间的即时搜索(Query-time Search-as-you-type) 如今让我们来看看前缀匹配可以怎样 ...

  4. [Elasticsearch] 邻近匹配 (二) - 多值字段,邻近程度与相关度

    多值字段(Multivalue Fields) 在多值字段上使用短语匹配会产生古怪的行为: PUT /my_index/groups/1 { "names": [ "Jo ...

  5. ElasticSearch查询 第三篇:词条查询

    <ElasticSearch查询>目录导航: ElasticSearch查询 第一篇:搜索API ElasticSearch查询 第二篇:文档更新 ElasticSearch查询 第三篇: ...

  6. 完爆Facebook/GraphQL,APIJSON全方位对比解析(三)-表关联查询

    相关阅读: 完爆Facebook/GraphQL,APIJSON全方位对比解析(一)-基础功能 完爆Facebook/GraphQL,APIJSON全方位对比解析(二)-权限控制 自APIJSON发布 ...

  7. Elasticsearch 邻近查询示例

    Elasticsearch 邻近查询示例(全切分分词) JAVA API方式: SpanNearQueryBuilder span = QueryBuilders.spanNearQuery(); s ...

  8. MySQL 三种关联查询的方式: ON vs USING vs 传统风格

    看看下面三个关联查询的 SQL 语句有何区别? 1SELECT * FROM film JOIN film_actor ON (film.film_id = film_actor.film_id) 2 ...

  9. Elasticsearch从0到千万级数据查询实践(非转载)

    1.es简介 1.1 起源 https://www.elastic.co/cn/what-is/elasticsearch,es的起源,是因为程序员Shay Banon在使用Apache Lucene ...

随机推荐

  1. 页面提交错误,页面间参数传递java.lang.NumberFormatException: null

    多次出现这样的错误,在点击一个按钮触发提交整个页面的事件时,总是报错,不止一次出现这样的错误了. 出现这种问题的分析: 1 我们从这个问题的本身来看,java.lang.NumberFormatExc ...

  2. 【老鸟学算法】包含 min函数的栈设计——java实现

    要求: 1. 定义栈的数据结构,要求添加一个 min函数,能够得到栈的最小元素. 2. 要求函数 min.push 以及 pop 的时间复杂度都是 O(1). 这是考验“栈”数据结构设计.众所周知,栈 ...

  3. Qt4在linux下的安装

    1.下载SDK ftp://ftp.informatik.hu-berlin.de/pub/Mirrors/ftp.troll.no/QT/qtsdk/qt-sdk-linux-x86-opensou ...

  4. Swift - 使用arc4random()、arc4random_uniform()取得随机数

    arc4random()这个全局函数会生成9位数的随机整数   1,下面是使用arc4random函数求一个1~100的随机数(包括1和100) 1 var temp:Int = Int(arc4ra ...

  5. A*寻路算法的实现

    原理:http://www.cppblog.com/christanxw/archive/2006/04/07/5126.html 算法理论请到原理这个传送门,代码中的注释,已经比较详细,所以我不会讲 ...

  6. [C#基础] 类

    类成员 字段和方法是最重要的类成员类型,字段是数据成员,方法是函数成员 字段 字段是隶属于类的变量 它可以是任何类型,无论是预定义类型还是用户定义类型 和所有变量一样,字段用来保存数据 它们可以被写入 ...

  7. common lisp wiki

    CLiki: index   http://www.cliki.net/

  8. 例3.1 猜猜数据结构 UVa11995

    1.标题叙述性说明:点击打开链接 2.解题思路:据来推測一种可能的数据结构,备选答案有"栈,队列.优先队列".结果也可能都不是或者不确定. STL中已经有这三种数据结构了,因此直接 ...

  9. 飘逸的python - yield简明教程

    发现还有非常多人对yield不理解,云里雾里,于是试着用文字表述. 仅仅要函数含有yield语句,它就返回一个生成器.所以我们与其把其看成函数定义,不如看作是生成器定义.函数用return返回,而生成 ...

  10. 4.2、Libgdx每个模块概述

    (原版的:http://www.libgdx.cn/topic/34/4-2-libgdx%E5%90%84%E4%B8%AA%E6%A8%A1%E5%9D%97%E6%A6%82%E8%A7%88) ...