短语和邻近度查询比简单的match查询在性能上更昂贵。match查询只是查看词条是否存在于倒排索引(Inverted Index)中,而match_phrase查询则须要计算和比较多个可能重复词条(Multiple possibly repeated)的位置。html
在Lucene Nightly Benchmarks中,显示了一个简单的term查询比一个短语查询快大概10倍,比一个邻近度查询(一个拥有slop的短语查询)快大概20倍。固然,这个代价是在搜索期间而不是索引期间付出的。算法
TIPapache
一般,短语查询的额外代价并不像这些数字说的那么吓人。实际上,性能上的差别只是说明了一个简单的term查询时多么的快。在标准全文数据上进行的短语查询一般可以在数毫秒内完成,所以它们在实际生产环境下是彻底可以使用的,即便在一个繁忙的集群中。json
在某些特定的场景下,短语查询可能会很耗费资源,可是这种状况时不常有的。一个典型的例子是DNA序列,此时会在不少位置上出现很是之多的相同重复词条。使用高slop值会使位置计算发生大幅度的增加。数据结构
所以,如何可以限制短语和邻近度查询的性能消耗呢?一个有用的方法是减小须要使用短语查询进行检查的文档总数。app
在上一节中,咱们讨论了使用邻近度查询来调整相关度,而不是使用它来将文档从结果列表中添加或者排除。一个查询可能会匹配百万计的结果,可是咱们的用户极可能只对前面几页结果有兴趣。elasticsearch
一个简单的match查询已经经过排序将含有全部搜索词条的文档放在结果列表的前面了。而咱们只想对这些前面的结果进行从新排序来给予那些同时匹配了短语查询的文档额外的相关度。ide
search API经过分值重计算(Rescoring)来支持这一行为。在分值重计算阶段,你可以使用一个更加昂贵的分值计算算法 - 好比一个短语查询 - 来为每一个分片的前K个结果从新计算其分值。紧接着这些结果就会按其新的分值从新排序。性能
该请求以下所示:测试
match查询用来决定哪些文档会被包含在最终的结果集合中,结果经过TF/IDF进行排序。 window_size是每一个分片上须要从新计算分值的数量。
尽管短语和邻近度查询很管用,它们仍是有一个缺点。它们过于严格了:全部的在短语查询中的词条都必须出如今文档中,即便使用了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须要在索引期间,做为分析过程的一部分被建立。咱们能够将Unigram和Bigram都索引到一个字段中,可是将它们放在不一样的字段中会更加清晰,也可以让它们可以被独立地查询。Unigram字段造成了咱们搜索的基础部分,而Bigram字段则用来提高相关度。
首先,咱们须要使用shingle词条过滤器来建立解析器:
默认Shingle的min/max值就是2,所以咱们也能够不显式地指定它们。 output_unigrams被设置为false,用来避免将Unigram和Bigram索引到相同字段中。
让咱们使用analyze API来测试该解析器:
不出所料,咱们获得了3个词条:
如今咱们就能够建立一个使用新解析器的字段了。
将Unigram和Bigram分开索引会更加清晰,所以咱们将title字段建立成一个多字段(Multifield)(参见字符串排序和多字段(String Sorting and Multifields)):
有了上述映射,JSON文档中的title字段会以Unigram(title字段)和Bigram(title.shingles字段)的方式索引,从而让咱们能够独立地对这两个字段进行查询。
最后,咱们能够索引示例文档:
搜索Shingles
为了理解添加的shingles字段的好处,让咱们首先看看一个针对"The hungry alligator ate Sue"的简单match查询的返回结果:
该查询会返回全部的3份文档,可是注意文档1和文档2拥有相同的相关度分值,由于它们含有相同的单词:
如今让咱们将shingles字段也添加到查询中。记住咱们会将shingle字段做为信号 - 以增长相关度分值 - 咱们仍然须要将主要的title字段包含到查询中:
咱们仍然匹配了3分文档,可是文档2如今排在了第一位,由于它匹配了Shingle词条"ate sue":
即便在查询中包含了没有在任何文档中出现的单词hungry,咱们仍然经过使用单词邻近度获得了最相关的文档。
Shingle不只比短语查询更灵活,它们的性能也更好。相比每次搜索须要为短语查询付出的代价,对Shingle的查询和简单match查询同样的高效。只是在索引期间会付出一点小代价,由于更多的词条须要被索引,意味着使用了Shingle的字段也会占用更多的磁盘空间。可是,多数应用是写入一次读取屡次的,所以在索引期间花费一点代价来让查询更迅速是有意义的。
这是一个你在ES中常常会碰到的主题:让你在搜索期间可以作不少事情,而不须要任何预先的设置。一旦更好地了解了你的需求,就可以经过在索引期间正确地建模来获得更好的结果和更好的性能。