倒排序索引的不变性
es的json文档中,每个被索引的字段都有自己的倒排索引,倒排索引会保存每个词项出现过的文档总数,在对应文档中一个具体词项出现的总次数,词项在文档中的顺序,每个文档的长度,所有文档的平均长度等。这些统计信息用于计算哪些词比其它词更重要,哪些文档比其它文档更重要。
倒排序索引被写入磁盘后是不可变的,不可变性可以带来以下好处:
- 不需要锁,不用担心多进程同时修改数据的问题
- 一旦索引被读入内核的文件系统缓存便会留在那里,由于不变性,大部分请求会直接请求内存而不会命中磁盘,提升性能
- 其它缓存(如filter缓存)在索引的生命周期内始终有效,不需要在每次数据改变时被重建
- 写入单个大的倒排序索引允许数据被压缩,减少磁盘I/O和需要被缓存到内存的使用量
倒排序索引的更新
es基于lucene,lucene引入按段搜索的概念,每一个段本身都是一个倒排索引,索引在lucene中除了表示所有段的集合外,还增加了提交点的概念——提交点是一个列出所有已知段的文件。
新的文档首先被添加到内存索引缓存中,然后写入到一个基于磁盘的段。当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的管理都被准确计算。
每个提交点会包含一个.del 文件,文件中会列出这些被删除文档的段信息。当一个文档被删除时,它实际只是在.del文件中被标记删除。一个被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前从结果集中移除。
文档更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的旧版本文档在结果集返回前被移除了。
段合并
由于自动刷新流程每秒创建一个新的段,会导致段时间内段数量快速增长,每个段都会消耗文件句柄、内存和CPU运行周期,并且每个搜索请求都必须轮流检查每个段,所以段越多,搜索就越慢。
es通过段合并解决这个问题,小的段被合并到大的段,大的段被合并成更大的段。段合并时会将那些旧的已删除文档从文件系统中清除,被删除文档或被更新文档的旧版本不会被拷贝到新的大段中。合并结束后,老的段被删除。
提高搜索性能
内存缓冲区中的文档会被写入到一个新的段中,新段会被先写入到文件系统缓存(代价小),稍后被刷新到磁盘(代价高)。不过只要文件已经在缓存中,就可以像其它文件一样被打开和读取了,这种方式比进行一次提交代价小得多。
es中写入和打开一个新段的过程叫做refresh,默认情况下,每个分片每秒自动刷新一次,文档的变化在刷新后才能被搜索到。刷新的时间间隔通过refresh_interval配置。
事务日志
es中的事务日志(translog)记录了所有还没被刷到磁盘的操作,当es启动时,会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放translog中所有在最后一次提交发生的变更操作。
默认translog每5秒被fsync刷新到磁盘,或者在每次写请求完成之后执行(index、delete、update、bulk),这个过程在主分片和复制分片上都会发生,在写请求被fsync到主分片和复制分片的translog之前,客户端不会得到200 ok的响应
执行一次提交并截断translog的行为在es中被称作一次flush,分片每30分钟被自动刷新(flush),或者translog太大时也会刷新,可以通过配置更改这些参数。