Lucene 索引原理系列(1):索引分段

背景

刚接触 Lucene 时,只求能用就行,至于背后复杂的设计和原理,我看得晕头转向,云里雾里。

比如,作为搜索引擎核心的倒排索引概念,原理上其实不难理解,但怎么一到了具体的实现,就多出了这么多复杂的设计!

  • 倒排索引本质上不就是个哈希表吗,为什么在 Lucene 里要分段(Segment)?
  • 为什么每个段都是不变的,从而带来了段合并(segment merge)的问题?

等后来闲下来,终于有功夫耐心地看别人的回答、查文档,慢慢把这些问题搞明白了。

搜索引擎的核心:倒排索引

这里简单解释一下倒排索引,如果已经熟悉这个概念大可以跳过这一节

倒排索引是个很简单的概念,其实就像书后面的术语表索引。你很可能读过这样的书(大概率是教材):在正文之后的索引中,会按字母或者拼音顺序列出书中出现的重要术语,以及该术语在书中每次出现的页码。

这样,如果你希望快速定位书中出现某个术语的地方,就不需要把书从头到尾读一遍了,只需要执行下面两个简单的步骤:

  1. 在术语表中找到该索引。由于术语表是按字母或拼音排序的,这一步不会很难,和查字典差不多;
  2. 根据术语表中记录的页码,直接跳转到书中相应的那些位置。这一步同样也会很快;

倒排索引和这个很像,可以理解为一个哈希表:

  • 哈希表中的每个键是一个词项(term),相当于术语表中的术语;
  • 每个词项的对应值则是出现该词项的所有文档的编号,相当于术语表中出现该术语的所有页码位置;

利用倒排索引,如果希望找到出现某个词项的所有文档(例如网页),那么同样也只需要两个简单的步骤:

  1. 在倒排索引中找到该词项;
  2. 根据该词项对应的文档编号 ,找到相应的文档;

这样一来,倒排索引就可以大大降低搜索文档的时间成本。

Lucene 的索引实现

基本原理很简单,但具体实现就比较麻烦了。

Lucene 中的索引是分段的,每个段(segment)对应不同的文件。

为什么 Lucene 要对索引分段?为什么段是不可变的 ?

既然倒排索引本质上就是个哈希表,存成一个文件不就好了?为什么要分段呢?

其实这也是一种无可奈何。在理想情况下,如果只需要建一次索引,那么只存为一个文件当然没问题,完全不需要分段。

但现实情况往往会更复杂,比如你已经对现有的网页建了索引,落成了索引文件,但每天都会有新的网页冒出来,旧的网页也会修改,这时候就需要更新索引。

如果索引不分段,那么就意味着你唯一的这个索引文件,在每次更新索引的时候都会被修改。

如果索引更新非常频繁(比如在 Elasticsearch 中默认是每秒),那么也就意味着你唯一的这个索引文件,每秒都在被更新。

这样会带来非常不好的影响。比如:

  • 单个文件不断被频繁写入和读取,容易产生性能瓶颈,也容易导致数据损坏
  • 对该文件的缓存将变得毫无意义

第二点尤为关键。例如在 Elasticsearch 集群内,为了提供更高的查询性能、保证更高的可用性,每份 Lucene 索引一般都会有几个副本,每次索引更新都会同步给所有副本。问题是,如果索引每秒都在更新,每秒钟都需要重写所有副本,效率也太低了吧!

这些问题都可以通过分段解决。分段之后,每次更新索引时会写入新的段,而之前的段则不会被修改。

比如,如果你有 10 个段,那么实际上相当于有 10 份倒排索引,每次查询某个词项时去这 10 个倒排索引中分别查询,再对结果进行汇总,这样和查一个单独的倒排索引的效果是一样的。

这样的好处是,由于之前的段都是不变的,缓存效率会很高;每个段只会被写入一次,也避免了频繁写入的问题。

可以发现,分段策略和“段的不可变性”是紧密关联的。如果段不是不可变的,那么分段的好处也就所剩无几了。

分段会带来什么问题?

分段策略非常好,但也会带来一些问题。

最大的问题是:由于之前的段是不可变的,如果希望删除之前段中的某些内容(比如,删除一些文档),那么只能过通过“标记删除”的方式实现,即在新增的段中说明哪些段被删除了,然后在结果汇总阶段把相关结果剔除掉。

这样带来的影响就是,本来应该已经删除的内容,实际上还保留在倒排索引中,一方面会影响查询效率,另一方面也占用着存储空间。

文档更新也是同样的道理。如果某个文档的内容变化了,那么 Lucene 实际上会先把该文档删除掉(通过标记删除的方法),然后再新增一份最新版本的文档。

换句话说,文档更新会带来和文档删除一样的问题:查询效率降低、存储空间浪费。

如果你的索引更新主要是文档新增,那么还好;如果有频繁的文档删除和文档更新,那么索引就会逐渐变得非常低效。

这个时候就需要“段合并”(segment merge)了。段合并操作会删除旧有段,写入合并之后的新段。

从搜索结果来看,段合并之前和之后没有任何变化。不过,段合并期间会剔除已经被删除的文档,提高查询效率、减少储存空间浪费。

小结

本文简单介绍了倒排索引、Lucene 中的索引分段。至于段合并的具体细节,就放到下一篇来写吧!

参考链接

What are segments in Lucene?

Why lucene’s segment is immutable

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值