提高性能

短语和邻近度查询比简单的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. Github Atom

    码代码新神器-Github Atom   周末闲着没事,逛论坛发现了一个新的编辑器,由github发布的Atom编辑器.瞬间被吸引了,所以就去尝试着折腾了一下,后来发现这个编辑器确实很不错,他的特点就 ...

  2. JSTL分割字符 fn:split()

    <%@ page language="java" contentType="text/html; charset=UTF-8"%><%@ ta ...

  3. 单例模式(Singleton)Holder

    public class Singleton { /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */ ...

  4. KMP算法(具体求串的next[n])

     怎么求串的模式值next[n]   )next[0]= -1  意义:不论什么串的第一个字符的模式值规定为-1. )next[j]= -1   意义:模式串T中下标为j的字符,假设与首字符 同样,且 ...

  5. WM_NCHITTEST有21种取值,常用的有HTCAPTION,HTCLIENT,HTBORDER,HTSYSMENU,HTTRANSPARENT,罗列所有VCL里对其使用的情况

    我为了移动一个无标题栏的窗体,使用了WM_NCHITTEST消息,这个消息大概如下: 通常,我们拖动对话框窗口的标题栏来移动窗口,但有时候,我们想通过鼠标在客户区上拖动来移动窗口. 一个容易想到的方案 ...

  6. VC++ 视频播放器 图文步骤记录

    1.安装DirectShow9.0 SDK DirectShow9 SDK下载链接http://download.csdn.net/detail/jindou910101/5591169 2.运行Di ...

  7. ubuntu 64位下安装wps

    首先,去官网下载wps.地址:http://community.wps.cn/download/ 然后切换到下载文件夹.运行例如以下命令: $ sudo apt-get install ia32-li ...

  8. 非对称加密RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等。使用最广泛的是RSA算法

          非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey).公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密:如果用私 ...

  9. OCP读书笔记(11) - 使用闪回技术II

    闪回归档 1. 什么是闪回数据归档? 闪回归档是用来保存一个或多个表的历史数据的新数据库对象,以及该数据的存储保留和清除策略.归档只是保存数据库中一个或多个表的所有事务处理的变化的一个或多个表空间,数 ...

  10. Rudiments 0.42 发布,C++ 常用工具包 - 开源中国社区

    Rudiments 0.42 发布,C++ 常用工具包 - 开源中国社区 Rudiments 0.42 发布,C++ 常用工具包