今天为我读了一篇和有意思的文章。它的意思是尽量避免使用 multi-match 查询。而是使用 copy_to 参数将多个字段的值复制到一个。 然后,仅在此字段中执行搜索。在这里,就他的这个问题,我来分两个方面展开一下这个话题。
“Elasticsearch 是有弹性的”。 很多方面。 不要指定任何内容,它仍然会为您的所有数据建立索引。 而且,这一点也不坏。 但是,要从 Elasticsearch 中获得最大价值(并减少费用),你将必须进行一些配置。更重要的是,你必须根据自己的业务需求需要来做一些自己的配置。
相关性是 Elasticsearch 非常关键的一个指标。在很多的情况下,我们需要对我们的搜索提供更好的相关性来查询到我们的想要的结果。比如我们来创建如下的一个索引:
PUT news_index
{
"settings": {
"number_of_shards": 1
}
}
PUT news_index/_mapping
{
"properties": {
"title": {
"type": "text"
},
"body_text": {
"type": "text"
}
}
}
在上面,我们已经创建了一个索引,它有两个字段。我们接着导入如下的文档:
PUT /news_index/_doc/1
{
"title": "China",
"body_text": "Great"
}
PUT /news_index/_doc/2
{
"title": "China",
"body_text": "flood"
}
PUT /news_index/_doc/3
{
"title": "Bad",
"body_text": "China is flooding"
}
我们接着使用 multi_search 进行如下的搜索:
GET news_index/_search
{
"query": {
"multi_match": {
"query": "China",
"fields": [ "title", "body_text"]
}
}
}
返回的结果是:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.7389809,
"hits" : [
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.7389809,
"_source" : {
"title" : "Bad",
"body_text" : "China is flooding"
}
},
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.4700036,
"_source" : {
"title" : "China",
"body_text" : "Great"
}
},
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.4700036,
"_source" : {
"title" : "China",
"body_text" : "flood"
}
}
]
}
}
从上面的返回结果我们可以看出来文档 id 为 3 的得分是第一。这是为啥呢?
我们可以参阅我之前的文章 “Elasticsearch:分布式计分” 里面提到一个很重要的概念 Inverse Document Frequency (IDF)。 它的意思是说:给定术语在所有文档中的唯一性。一个字段在越多的文档中出现,那么这个术语就越不重要,比如“the”,"to"等这些词经常出现在一些文档,那么这些词的重要性就不强。
结合我们上面的情况,我们可以看到 "China" 这个词在 id 为3的文档中作为 “body_text” 出现,只有一次,而在文档 id 为 1 和 3 的文档中出现两次。这也就说明了文档3比较重要,从而造成了整个文档的分数偏高。如果,这种情况,不是我们想要的,我们希望 title 和 body_text 都一样对待,那么我们可以通过改写我们的 mapping 使用 copy_to 来提高相关性:
PUT news_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"copy_to": "title_body_text"
},
"body_text": {
"type": "text",
"copy_to": "title_body_text"
},
"title_body_text": {
"type": "text"
}
}
}
}
在上面,我们重新添加了一个新的字段叫做 title_body_text。运行上面的命令。由于 mapping 已经改变,我们使用如下的命令来进行更新索引:
POST news_index/_update_by_query
更新完后,我们重新进行查询:
GET news_index/_search
{
"query": {
"match": {
"title_body_text": "China"
}
}
}
这次,我们重新查看我们的结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.14874382,
"hits" : [
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.14874382,
"_source" : {
"title" : "China",
"body_text" : "Great"
}
},
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.14874382,
"_source" : {
"title" : "China",
"body_text" : "flood"
}
},
{
"_index" : "news_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.110856235,
"_source" : {
"title" : "Bad",
"body_text" : "China is flooding"
}
}
]
}
}
这次,我们可以看到 id 为1的文档的得分是最高的。这其中的原因也是因为它的整个字段 title_body_text 比较短的缘故。关于 copy_to 的更多介绍,请参阅我之前的文章 “如何使用Elasticsearch中的copy_to来提高搜索效率”。
全文搜索本身就很昂贵。 一次搜索多个字段的成本甚至更高。 就计算能力而言,而不是存储方面的价格昂贵。必须打多个字段的查询速度很慢。这篇文章中描述的优化将优化搜索速度,但是,它将占用(略微)更多的磁盘空间。好消息! 存储成本低。 但是,计算能力仍然很昂贵。
我们可以通过 copy_to 来把多个字段合并为一个字段进而提高搜索的效率。
提前了解你的用例。这是使用 NoSQL 数据库的黄金法则。 Elasticsearch 也不例外。事先了解对数据的所有访问方式(如果可能)。 这将帮助你优化集群/索引设计。你可能会说:“这没什么大不了的。” 我可能同意。 有许多小细节,当群集承受重负载且不会崩溃时,所有这些细节将共同发挥重要作用。
参阅:
【1】https://codarium.substack.com/p/optimizing-elasticsearch-performance