停用词与短语查询

所有查询中 短语匹配 大约占到5%,但是在慢查询里面它们又占大部分。 短语查询性能相对较差,特别是当短语中包括常用词的时候,如 “To be, or not to be” 短语全部由停用词组成,这是一种极端情况。原因在于几乎需要匹配全量的数据。

在 停用词的两面 停用词的优缺点,中,我们提到移除停用词只能节省倒排索引中的一小部分空间。这句话只部分正确,一个典型的索引会可能包含部分或所有以下数据:

词项字典(Terms dictionary)
索引中所有文档内所有词项的有序列表,以及包含该词的文档数量。
倒排表(Postings list)
包含每个词项的文档(ID)列表。
词频(Term frequency)
每个词项在每个文档里出现的频率。
位置(Positions)
每个词项在每个文档里出现的位置,供短语查询或近似查询使用。
偏移(Offsets)
每个词项在每个文档里开始与结束字符的偏移,供词语高亮使用,默认是禁用的。
规范因子(Norms)
用来对字段长度进行规范化处理的因子,给较短字段予以更多权重。

将停用词从索引中移除会节省 词项字典倒排表 里的少量空间,但 位置偏移 是另一码事。位置和偏移数据很容易变成索引大小的两倍、三倍、甚至四倍。

位置信息

analyzed 字符串字段的位置信息默认是开启的, 所以短语查询能随时使用到它。 词项出现的越频繁,用来存储它位置信息的空间就越多。在一个大的文档集合中,对于那些非常常见的词,它们的位置信息可能占用成百上千兆的空间。

运行一个针对高频词 the 的短语查询可能会导致从磁盘读取好几G的数据。这些数据会被存储到内核文件系统的缓存中,以提高后续访问的速度,这看似是件好事,但这可能会导致其他数据从缓存中被剔除,进一步使后续查询变慢。

这显然是我们需要解决的问题。

索引选项

我们首先应该问自己:是否真的需要使用短语查询 或 近似查询

答案通常是:不需要。在很多应用场景下,比如说日志,我们需要知道一个词 是否 在文档中(这个信息由倒排表提供)而不是关心词的位置在哪里。或许我们要对一两个字段使用短语查询,但是我们完全可以在其他 analyzed 字符串字段上禁用位置信息。

index_options 参数 允许我们控制索引里为每个字段存储的信息。 可选值如下:

docs
只存储文档及其包含词项的信息。这对 not_analyzed 字符串字段是默认的。
freqs
存储 docs 信息,以及每个词在每个文档里出现的频次。词频是完成TF/IDF 相关度计算的必要条件,但如果只想知道一个文档是否包含某个特定词项,则无需使用它。
positions
存储 docsfreqsanalyzed ,以及每个词项在每个文档里出现的位置。 这对 analyzed 字符串字段是默认的,但当不需使用短语或近似匹配时,可以将其禁用。
offsets
存储 docs,freqs,positions, 以及每个词在原始字符串中开始与结束字符的偏移信息( postings highlighter )。这个信息被用以高亮搜索结果,但它默认是禁用的。

我们可以在索引创建的时候为字段设置 index_options 选项,或者在使用 put-mapping API新增字段映射的时候设置。我们无法修改已有字段的这个设置:

PUT /my_index
{
  "mappings": {
    "my_type": {
      "properties": {
        "title": { 
          "type":          "string"
       },
        "content": { 
          "type":          "string",
          "index_options": "freqs"
      }
    }
  }
}

title 字段使用默认的 positions 设置,所以它适于短语或近似查询。

content 字段的位置设置是禁用的,所以它无法用于短语或近似查询。

停用词

删除停用词是能显著降低位置信息所占空间的一种方式。 一个被删除停用词的索引仍然可以使用短语查询,因为剩下的词的原始位置仍然被保存着,这正如 保持位置(Maintaining Positions) 中看到的那样。 尽管如此,将词项从索引中排除终究会降低搜索能力,这使我们难以区分 Man in the moonMan on the moon 这两个短语。

幸运的是,鱼与熊掌是可以兼得的:请查看 common_grams 过滤器