[Elasticsearch] 全文搜索 (一) - 基础概念和match查询
现在我们已经讨论了搜索结构化数据的一些简单用例,是时候开始探索全文搜索了 - 如何在全文字段中搜索来找到最相关的文档。
对于全文搜索而言,最重要的两个方面是:
相关度(Relevance)
查询的结果按照它们对查询本身的相关度进行排序的能力,相关度可以通过TF/IDF,参见什么是相关度,地理位置的邻近程度(Proximity to a Geo-location),模糊相似性(Fuzzy Similarity)或者其它算法进行计算。
解析(Analysis)
解析用来将一块文本转换成单独的,规范化的词条(Tokens),参见解析和解析器(Analysis and Analyzers),用来完成:(a)倒排索引(Inverted Index)的创建;(b)倒排索引的查询。
一旦我们开始讨论相关度或者解析,也就意味着我们踏入了查询(Query)的领域,而不再是过滤器(Filter)。
基于词条(Term-based)和全文(Full-text)
尽管所有的查询都会执行某种程度的相关度计算,并不是所有的查询都存在解析阶段。除了诸如bool
或者function_score
这类完全不对文本进行操作的特殊查询外,对于文本的查询可以被划分两个种类:
基于词条的查询(Term-based Queries)
类似term
和fuzzy
的查询是不含有解析阶段的低级查询(Low-level Queries)。它们在单一词条上进行操作。一个针对词条Foo
的term
查询会在倒排索引中寻找该词条的精确匹配(Exact term),然后对每一份含有该词条的文档通过TF/IDF进行相关度_score
的计算。
尤其需要记住的是term
查询只会在倒排索引中寻找该词条的精确匹配 - 它不会匹配诸如foo
或者FOO
这样的变体。它不在意词条是如何被保存到索引中。如果你索引了["Foo", "Bar"]
到一个not_analyzed
字段中,或者将Foo Bar
索引到一个使用whitespace
解析器的解析字段(Analyzed Field)中,它们都会在倒排索引中得到两个词条:"Foo"
以及"Bar"
。
全文查询(Full-text Queries)
类似match
或者query_string
这样的查询是高级查询(High-level Queries),它们能够理解一个字段的映射:
- 如果你使用它们去查询一个
date
或者integer
字段,它们会将查询字符串分别当做日期或者整型数。 - 如果你查询一个精确值(
not_analyzed
)字符串字段,它们会将整个查询字符串当做一个单独的词条。 - 但是如果你查询了一个全文字段(
analyzed
),它们会首先将查询字符串传入到合适的解析器,用来得到需要查询的词条列表。
一旦查询得到了一个词条列表,它就会使用列表中的每个词条来执行合适的低级查询,然后将得到的结果进行合并,最终产生每份文档的相关度分值。
我们会在后续章节中详细讨论这个过程。
在很少的情况下,你才需要直接使用基于词条的查询(Term-based Queries)。通常你需要查询的是全文,而不是独立的词条,而这个工作通过高级的全文查询来完成会更加容易(在内部它们最终还是使用的基于词条的低级查询)。
如果你发现你确实需要在一个not_analyzed
字段上查询一个精确值,那么考虑一下你是否真的需要使用查询,而不是使用过滤器。
单词条查询通常都代表了一个二元的yes|no
问题,这类问题通常使用过滤器进行表达更合适,因此它们也能够得益于过滤器缓存(Filter Caching):
GET /_search
{
"query": {
"filtered": {
"filter": {
"term": { "gender": "female" }
}
}
}
}
match
查询
在你需要对任何字段进行查询时,match
查询应该是你的首选。它是一个高级全文查询,意味着它知道如何处理全文字段(Full-text, analyzed
)和精确值字段(Exact-value,not_analyzed
)。
即便如此,match
查询的主要使用场景仍然是全文搜索。让我们通过一个简单的例子来看看全文搜索时如何工作的。
索引一些数据
首先,我们会创建一个新的索引并通过bulk
API索引一些文档:
DELETE /my_index
PUT /my_index
{ "settings": { "number_of_shards": 1 }}
POST /my_index/my_type/_bulk
{ "index": { "_id": 1 }}
{ "title": "The quick brown fox" }
{ "index": { "_id": 2 }}
{ "title": "The quick brown fox jumps over the lazy dog" }
{ "index": { "_id": 3 }}
{ "title": "The quick brown fox jumps over the quick dog" }
{ "index": { "_id": 4 }}
{ "title": "Brown fox brown dog" }
注意到以上在创建索引时,我们设置了number_of_shards
为1:在稍后的相关度坏掉了(Relevance is broken)一节中,我们会解释为何这里创建了一个只有一个主分片(Primary shard)的索引。
单词查询(Single word query)
第一个例子我们会解释在使用match
查询在一个全文字段中搜索一个单词时,会发生什么:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "QUICK!"
}
}
}
ES会按照如下的方式执行上面的match
查询:
-
检查字段类型
title
字段是一个全文字符串字段(analyzed
),意味着查询字符串也需要被分析。 -
解析查询字符串
查询字符串
"QUICK!"
会被传入到标准解析器中,得到的结果是单一词条"quick"
。因为我们得到的只有一个词条,match
查询会使用一个term
低级查询来执行查询。 -
找到匹配的文档
term
查询会在倒排索引中查询"quick"
,然后获取到含有该词条的文档列表,在这个例子中,文档1
,2
,3
会被返回。 -
对每份文档打分
term
查询会为每份匹配的文档计算其相关度分值_score
,该分值通过综合考虑词条频度(Term Frequency)("quick"
在匹配的每份文档的title
字段中出现的频繁程度),倒排频度(Inverted Document Frequency)("quick"
在整个索引中的所有文档的title
字段中的出现程度),以及每个字段的长度(较短的字段会被认为相关度更高)来得到。参考什么是相关度(What is Relevance?)
这个过程会给我们下面的结果(有省略):
"hits": [
{
"_id": "1",
"_score": 0.5,
"_source": {
"title": "The quick brown fox"
}
},
{
"_id": "3",
"_score": 0.44194174,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "2",
"_score": 0.3125,
"_source": {
"title": "The quick brown fox jumps over the lazy dog"
}
}
]
文档1最相关,因为它的title
字段短,意味着quick
在它所表达的内容中占比较大。 文档3比文档2的相关度更高,因为quick
出现了两次。
原文来自:http://blog.youkuaiyun.com/dm_vincent/article/details/41693125
[Elasticsearch] 全文搜索 (二) - 多词查询及查询的合并
多词查询(Multi-word Queries)
如果我们一次只能搜索一个词,那么全文搜索就会显得相当不灵活。幸运的是,通过match
查询来实现多词查询也同样简单:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "BROWN DOG!"
}
}
}
以上的查询会返回所有的四份文档:
{
"hits": [
{
"_id": "4",
"_score": 0.73185337,
"_source": {
"title": "Brown fox brown dog"
}
},
{
"_id": "2",
"_score": 0.47486103,
"_source": {
"title": "The quick brown fox jumps over the lazy dog"
}
},
{
"_id": "3",
"_score": 0.47486103,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "1",
"_score": 0.11914785,
"_source": {
"title": "The quick brown fox"
}
}
]
}
文档4的相关度最高因为它包含了"brown"
两次和"dog"
一次。 文档2和文档3都包含了"brown"
和"dog"
一次,同时它们的title
字段拥有相同的长度,因此它们的分值相同。 文档1只包含了"brown"
。
因为match
查询需要查询两个词条 - ["brown","dog"]
- 在内部它需要执行两个term
查询,然后将它们的结果合并来得到整体的结果。因此,它会将两个term
查询通过一个bool
查询组织在一起,我们会在合并查询一节中详细介绍。
从上面的例子中需要吸取的经验是,文档的title
字段中只需要包含至少一个指定的词条,就能够匹配该查询。如果匹配的词条越多,也就意味着该文档的相关度就越高。
提高精度(Improving Precision)
匹配任何查询词条就算作匹配的话,会导致最终结果中有很多看似无关的匹配。它是一个霰弹枪式的策略(Shotgun Approach)。我们大概只想要显示包含了所有查询词条的文档。换言之,相比brown OR dog
,我们更想要的结果是brown AND dog
。
match
查询接受一个operator
参数,该参数的默认值是"or"
。你可以将它改变为"and"
来要求所有的词条都需要被匹配:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "BROWN DOG!",
"operator": "and"
}
}
}
}
match
查询的结构需要被稍稍改变来容纳operator
参数。
这个查询的结果会将文档1排除在外,因为它只包含了一个查询词条。
控制精度(Controlling Precision)
在all和any中选择有种非黑即白的感觉。如果用户指定了5个查询词条,而一份文档只包含了其中的4个呢?将"operator"
设置成"and"
会将它排除在外。
有时候这正是你想要的,但是对于大多数全文搜索的使用场景,你会希望将相关度高的文档包含在结果中,将相关度低的排除在外。换言之,我们需要一种介于两者中间的方案。
match
查询支持minimum_should_match
参数,它能够让你指定有多少词条必须被匹配才会让该文档被当做一个相关的文档。尽管你能够指定一个词条的绝对数量,但是通常指定一个百分比会更有意义,因为你无法控制用户会输入多少个词条:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown dog",
"minimum_should_match": "75%"
}
}
}
}
当以百分比的形式指定时,minimum_should_match
会完成剩下的工作:在上面拥有3个词条的例子中,75%
会被向下舍入到66.6%
,即3个词条中的2个。无论你输入的是什么,至少有2个词条被匹配时,该文档才会被算作最终结果中的一员。
minimum_should_match
参数非常灵活,根据用户输入的词条的数量,可以适用不同的规则。具体可以参考minimum_should_match
参数的相关文档。
为了更好地了解match
查询是如何处理多词查询的,我们需要看看bool
查询是如何合并多个查询的。
合并查询(Combining Queries)
在合并过滤器中我们讨论了使用bool
过滤器来合并多个过滤器以实现and
,or
和not
逻辑。bool
查询也做了类似的事,但有一个显著的不同。
过滤器做出一个二元的决定:这份文档是否应该被包含在结果列表中?而查询,则更加微妙。它们不仅要决定是否包含一份文档,还需要决定这份文档有多相关。
和过滤器类似,bool
查询通过must
,must_not
以及should
参数来接受多个查询。比如:
GET /my_index/my_type/_search
{
"query": {
"bool": {
"must": { "match": { "title": "quick" }},
"must_not": { "match": { "title": "lazy" }},
"should": [
{ "match": { "title": "brown" }},
{ "match": { "title": "dog" }}
]
}
}
}
title
字段中含有词条quick
,且不含有词条lazy
的任何文档都会被作为结果返回。目前为止,它的工作方式和bool
过滤器十分相似。
差别来自于两个should
语句,它表达了这种意思:一份文档不被要求需要含有词条brown
或者dog
,但是如果它含有了,那么它的相关度应该更高。
{
"hits": [
{
"_id": "3",
"_score": 0.70134366,
"_source": {
"title": "The quick brown fox jumps over the quick dog"
}
},
{
"_id": "1",
"_score": 0.3312608,
"_source": {
"title": "The quick brown fox"
}
}
]
}
文档3的分值更高因为它包含了brown
以及dog
。
分值计算(Score Calculation)
bool
查询通过将匹配的must
和should
语句的_score
相加,然后除以must
和should
语句的总数来得到相关度分值_score
。
must_not
语句不会影响分值;它们唯一的目的是将不需要的文档排除在外。
控制精度(Controlling Precision)
所有的must
语句都需要匹配,而所有的must_not
语句都不能匹配,但是should
语句需要匹配多少个呢?默认情况下,should
语句一个都不要求匹配,只有一个特例:如果查询中没有must
语句,那么至少要匹配一个should
语句。
正如我们可以控制match
查询的精度,我们也能够通过minimum_should_match
参数来控制should
语句需要匹配的数量,该参数可以是一个绝对数值或者一个百分比:
GET /my_index/my_type/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "brown" }},
{ "match": { "title": "fox" }},
{ "match": { "title": "dog" }}
],
"minimum_should_match": 2
}
}
}
以上查询的而结果仅包含以下文档:
title
字段包含: "brown" AND "fox"
或者 "brown" AND "dog"
或者 "fox" AND "dog"
如果一份文档含有所有三个词条,那么它会被认为更相关。
原文来自:http://blog.youkuaiyun.com/dm_vincent/article/details/41720193
[Elasticsearch] 全文搜索 (三) - match查询和bool查询的关系,提升查询子句
match查询是如何使用bool查询的
现在,你也许意识到了使用了match
查询的多词查询只是简单地将生成的term
查询包含在了一个bool
查询中。通过默认的or
操作符,每个term
查询都以一个语句被添加,所以至少一个should
语句需要被匹配。以下两个查询是等价的:
{
"match": { "title": "brown fox"}
}
{
"bool": {
"should": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
使用and
操作符时,所有的term
查询都以must
语句被添加,因此所有的查询都需要匹配。以下两个查询是等价的:
{
"match": {
"title": {
"query": "brown fox",
"operator": "and"
}
}
}
{
"bool": {
"must": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }}
]
}
}
如果指定了minimum_should_match
参数,它会直接被传入到bool
查询中,因此下面两个查询是等价的:
{
"match": {
"title": {
"query": "quick brown fox",
"minimum_should_match": "75%"
}
}
}
{
"bool": {
"should": [
{ "term": { "title": "brown" }},
{ "term": { "title": "fox" }},
{ "term": { "title": "quick" }}
],
"minimum_should_match": 2
}
}
因为只有3个查询语句,minimum_should_match
的值75%
会被向下舍入到2
。即至少两个should语句需要匹配。
当然,我们可以通过match
查询来编写这类查询,但是理解match
查询的内部工作原理能够让你根据需要来控制该过程。有些行为无法通过一个match
查询完成,比如对部分查询词条给予更多的权重。在下一节中我们会看到一个例子。
提升查询子句(Boosting Query Clause)
当然,bool
查询并不是只能合并简单的单词(One-word)match
查询。它能够合并任何其它的查询,包括其它的bool
查询。它通常被用来通过合并数个单独的查询的分值来调优每份文档的相关度_score
。
假设我们需要搜索和"full-text search"相关的文档,但是我们想要给予那些提到了"Elasticsearch"或者"Lucene"的文档更多权重。更多权重的意思是,对于提到了"Elasticsearch"或者"Lucene"的文档,它们的相关度_score
会更高,即它们会出现在结果列表的前面。
一个简单的bool查询能够让我们表达较为复杂的逻辑:
GET /_search
{
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [
{ "match": { "content": "Elasticsearch" }},
{ "match": { "content": "Lucene" }}
]
}
}
}
content
字段必须含有full
,text
和search
这三个词条- 如果
content
字段也含有了词条Elasticsearch
或者Lucene
,那么该文档会有一个较高的_score
should
查询子句的匹配数量越多,那么文档的相关度就越高。目前为止还不错。
但是如果我们想给含有Lucene
的文档多一些权重,同时给含有Elasticsearch
的文档更多一些权重呢?
我们可以通过指定一个boost
值来控制每个查询子句的相对权重,该值默认为1
。一个大于1
的boost
会增加该查询子句的相对权重。因此我们可以将上述查询重写如下:
GET /_search
{
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [
{ "match": {
"content": {
"query": "Elasticsearch",
"boost": 3
}
}},
{ "match": {
"content": {
"query": "Lucene",
"boost": 2
}
}}
]
}
}
}
NOTE
boost
参数被用来增加一个子句的相对权重(当boost
大于1
时),或者减小相对权重(当boost
介于0
到1
时),但是增加或者减小不是线性的。换言之,boost
设为2
并不会让最终的_score
加倍。相反,新的
_score
会在适用了boost
后被归一化(Normalized)。每种查询都有自己的归一化算法(Normalization Algorithm),算法的细节超出了本书的讨论范围。但是能够说一个高的boost
值会产生一个高的_score
。如果你在实现你自己的不基于TF/IDF的相关度分值模型并且你需要对提升过程拥有更多的控制,你可以使用
function_score
查询,它不通过归一化步骤对文档的boost
进行操作。
在下一章中,我们会介绍其它的用于合并查询的方法,多字段查询(Multifield Search)。但是,首先让我们看看查询的另一个重要特定:文本分析(Text Analysis)。
[Elasticsearch] 全文搜索 (四) - 控制分析及相关度
控制分析(Controlling Analysis)
查询只能摘到真实存在于倒排索引(Inverted Index)中的词条(Term),因此确保相同的分析过程会被适用于文档的索引阶段和搜索阶段的查询字符串是很重要的,这样才能够让查询中的词条能够和倒排索引中的词条匹配。
尽管我们说的是文档(Document),解析器(Analyzer)是因字段而异的(Determined per Field)。每个字段都能够拥有一个不同的解析器,通过为该字段配置一个特定的解析器或者通过依赖类型(Type),索引(Index)或者节点(Node)的默认解析器。在索引时,一个字段的值会被该字段的解析器解析。
比如,让我们为my_index
添加一个新字段:
PUT /my_index/_mapping/my_type
{
"my_type": {
"properties": {
"english_title": {
"type": "string",
"analyzer": "english"
}
}
}
}
现在我们就能够通过analyze
API来比较english_title
字段和title
字段在索引时的解析方式,以Foxes
这一单词为例:
GET /my_index/_analyze?field=my_type.title
Foxes
GET /my_index/_analyze?field=my_type.english_title
Foxes
对于title
字段,它使用的是默认的standard
解析器,它会返回词条foxes
。 对于english_title
字段,它使用的是english
解析器,它会返回词条fox
。
这说明当我们为词条fox
执行一个低级的term
查询时,english_title
字段能匹配而title
字段不能。
类似match
查询的高阶查询能够理解字段映射(Field Mappings),同时能够为查询的每个字段适用正确的解析器。我们可以通过validate-query
API来验证这一点:
GET /my_index/my_type/_validate/query?explain
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Foxes"}},
{ "match": { "english_title": "Foxes"}}
]
}
}
}
它会返回这个explanation
:
(title:foxes english_title:fox)
match
查询会为每个字段适用正确的解析器,来确保该字段的查询词条的形式是正确的。
默认解析器(Default Analyzers)
尽管我们能够为字段指定一个解析器,但是当没有为字段指定解析器时,如何决定字段会使用哪个解析器呢?
解析器可以在几个级别被指定。ES会依次检查每个级别直到它找到了一个可用的解析器。在索引期间,检查的顺序是这样的:
- 定义在字段映射中的
analyzer
- 文档的
_analyzer
字段中定义的解析器 type
默认的analyzer
,它的默认值是- 在索引设置(Index Settings)中名为
default
的解析器,它的默认值是 - 节点上名为
default
的解析器,它的默认值是 standard
解析器
在搜索期间,顺序稍微有所不同:
- 直接定义在查询中的
analyzer
- 定义在字段映射中的
analyzer
type
默认的analyzer
,它的默认值是- 在索引设置(Index Settings)中名为
default
的解析器,它的默认值是 - 节点上名为
default
的解析器,它的默认值是 standard
解析器
NOTE
以上两个斜体表示的项目突出显示了索引期间和搜索期间顺序的不同之处。_analyzer字段允许你能够为每份文档指定一个默认的解析器(比如,
english
,french
,spanish
),而查询中的analyzer参数则让你能够指定查询字符串使用的解析器。然而,这并不是在一个索引中处理多语言的最佳方案,因为在处理自然语言中提到的一些陷阱。
偶尔,在索引期间和查询期间使用不同的解析器是有意义的。比如,在索引期间我们也许希望能够索引同义词(Synonyms)(比如,对每个出现的quick
,同时也索引fast
,rapid
和speedy
)。但是在查询期间,我们必须要搜索以上所有的同义词。相反我们只需要查询用户输入的单一词汇,不管是quick
,fast
,rapid
或者speedy
。
为了实现这个区别,ES也支持index_analyzer
和search_analyzer
参数,以及名为default_index
和default_search
的解析器。
将这些额外的参数也考虑进来的话,索引期间查找解析器的完整顺序是这样的:
- 定义在字段映射中的
index_analyzer
- 定义在字段映射中的
analyzer
- 定义在文档
_analyzer
字段中的解析器 type
的默认index_analyzer
,它的默认值是type
的默认analyzer
,它的默认值是- 索引设置中
default_index
对应的解析器,它的默认值是 - 索引设置中
default
对应的解析器,它的默认值是 - 节点上
default_index
对应的解析器,它的默认值是 - 节点上
default
对应的解析器,它的默认值是 standard
解析器
而查询期间的完整顺序则是:
- 直接定义在查询中的
analyzer
- 定义在字段映射中的
search_analyzer
- 定义在字段映射中的
analyzer
type
的默认search_analyzer
,它的默认值是type
的默认analyzer
,它的默认值是- 索引设置中的
default_search
对应的解析器,它的默认值是 - 索引设置中的
default
对应的解析器,它的默认值是 - 节点上
default_search
对应的解析器,它的默认值是 - 节点上
default
对应的解析器,它的默认值是 standard
解析器
配置解析器
能够指定解析器的地方太多也许会吓到你。但是实际上,它是非常简单的。
使用索引设置,而不是配置文件
第一件需要记住的是,即使你是因为一个单一的目的或者需要为一个例如日志的应用而使用ES的,很大可能你在将来会发现更多的用例,因此你会在相同的集群上运行多个独立的应用。每个索引都需要是独立的并且被独立地配置。
所以这就排除了在节点上配置解析器的必要。另外,在节点上配置解析器会改变每个节点的配置文件并且需要重启每个节点,这是维护上的噩梦。让ES持续运行并且只通过API来管理设置是更好的主意。
保持简单(Keep it simple)
多数时候,你都能提前知道你的文档会包含哪些字段。最简单的办法是在创建索引或者添加类型映射时为每个全文字段设置解析器。尽管该方法稍微有些繁琐,它能够让你清晰地看到每个字段上使用的解析器。
典型地,大多数字符串字段都会是精确值类型的not_analyzed
字段,比如标签或者枚举,加上一些会使用standard
或者english
以及其他语言解析器的全文字段。然后你会有一两个字段需要自定义解析:比方title
字段的索引方式需要支持"输入即时搜索(Find-as-you-type)"。
你可以在索引中设置default
解析器,用来将它作为大多数全文字段的解析器,然后对某一两个字段配置需要的解析器。如果在你的建模中,你需要为每个类型使用一个不同的默认解析器,那么就在类型级别上使用analyzer
设置。
NOTE
对于日志这类基于时间的数据,一个常用的工作流是每天即时地创建一个新的索引并将相应数据索引到其中。尽管这个工作流会让你无法预先创建索引,你仍然可以使用索引模板(Index Templates)来为一个新的索引指定其设置和映射。
相关度出问题了(Relevance is Broken)
在继续讨论更加复杂的多字段查询前,让我们先快速地解释一下为何将测试索引创建成只有一个主分片的索引。
经常有新的用户反映称相关度出问题了,并且提供了一个简短的重现:用户索引了一些文档,运行了一个简单的查询,然后发现相关度低的结果明显地出现在了相关度高的结果的前面。
为了弄明白它发生的原因,让我们假设我们创建了一个拥有两个主分片的索引,并且索引了10份文档,其中的6份含有单词foo。在分片1上可能保存了包含单词foo的3份文档,在分片2上保存了另外3份。换言之,文档被均匀的分布在分片上。
在什么是相关度一节中,我们描述了在ES中默认使用的相似度算法 - 即词条频度/倒排文档频度(Term Frequency/Inverse Document Frequency,TF/IDF)。词条频度计算词条在当前文档的对应字段中出现的次数。词条出现的次数越多,那么该文档的相关度就越高。倒排文档频度将一个词条在索引的所有文档中出现程度以百分比的方式考虑在内。词条出现的越频繁,那么它的权重就越小。
但是,因为性能上的原因,ES不会计算索引中所有文档的IDF。相反,每个分片会为其中的文档计算一个本地的IDF。
因为我们的文档被均匀地分布了,两个分片上计算得到的IDF应该是相同的。现在想象一下如果含有foo的5份文档被保存在了分片1上,而只有1份含有foo
的文档被保存在了分片2上。在这种情况下,词条foo
在分片1上就是一个非常常见的词条(重要性很低),但是在分片2上,它是非常少见的词条(重要性很高)。因此,这些IDF的差异就会导致错误的结果。
实际情况下,这并不是一个问题。当你向索引中添加的文档越多,本地IDF和全局IDF之间的差异就会逐渐减小。考虑到真实的世界中的数据量,本地IDF很快就会变的正常。问题不是相关度,而是数据量太小了。
对于测试,有两种方法可以规避该问题。第一种方法是创建只有一个主分片的索引,正如我们在介绍match
查询时做的那样。如果你只有一个分片,那么本地IDF就是全局IDF。
第二种方法是在搜索请求中添加?search_type=dfs_query_then_fetch
。dfs表
示分布频度搜索(Distributed Frequency Search),它会告诉ES首先从每个分片中获取本地IDF,然后计算整个索引上的全局IDF。
TIP
不要在生产环境中使用
dfs_query_then_fetch
。它真的是不必要的。拥有足够的数据就能够确保你的词条频度会被均匀地分布。没有必要再为每个查询添加这个额外的DFS步骤。
原文来自:http://blog.youkuaiyun.com/dm_vincent/article/details/41773959