Elasticsearch 的性能优化 (2)

第三步: 位压缩,计算每组3个数中最大的那个数需要占用bit位数,比如30、11、29中最大数30最小需要5个bit位存储,这样11、29也用5个bit位存储,这样才占用15个bit,不到2个字节,压缩效果很好。

如下面原理图所示,这是一个区块大小为3的示例(实际上是256):

Elasticsearch 的性能优化

考虑到频繁出现的term(所谓low cardinality的值),比如gender里的男或者女。如果有1百万个文档,那么性别为男的 posting list 里就会有50万个int值。用 Frame of Reference 编码进行压缩可以极大减少磁盘占用。这个优化对于减少索引尺寸有非常重要的意义。

因为这个 FOR 的编码是有解压缩成本的。利用skip list,除了跳过了遍历的成本,也跳过了解压缩这些压缩过的block的过程,从而节省了cpu。

Roaring bitmaps

Frame Of Reference 压缩算法对于倒排表来说效果很好,但对于需要存储在内存中的Filter缓存等不太合适,两者之间有很多不同之处:倒排表存储在磁盘,针对每个词都需要进行编码,而Filter等内存缓存只会存储那些经常使用的数据,而且针对Filter数据的缓存就是为了加速处理效率,对压缩算法要求更高;这就产生了下面针对内存缓存数据可以进行高效压缩解压和逻辑运算的roaring bitmaps算法。

说到Roaring bitmaps,就必须先从bitmap说起。Bitmap是一种数据结构,假设有某个posting list:

[3,1,4,7,8]

对应的Bitmap就是:

[0,1,0,1,1,0,0,1,1]

非常直观,用0/1表示某个值是否存在,比如8这个值就对应第8位,对应的bit值是1,这样用一个字节就可以代表8个文档id(1B = 8bit),旧版本(5.0之前)的Lucene就是用这样的方式来压缩的。但这样的压缩方式仍然不够高效,Bitmap自身就有压缩的特点,其用一个byte就可以代表8个文档,所以100万个文档只需要12.5万个byte。但是考虑到文档可能有数十亿之多,在内存里保存Bitmap仍然是很奢侈的事情。而且对于个每一个filter都要消耗一个Bitmap,比如age=18缓存起来的话是一个Bitmap,18<=age<25是另外一个filter缓存起来也要一个Bitmap。

Bitmap的缺点是存储空间随着文档个数线性增长,所以秘诀就在于需要有一个数据结构打破这个魔咒,那么就一定要用到某些指数特性:

可以很压缩地保存上亿个bit代表对应的文档是否匹配filter;

这个压缩的Bitmap仍然可以很快地进行AND和 OR的逻辑操作。

Lucene使用的这个数据结构叫做 Roaring Bitmap

Elasticsearch 的性能优化

其压缩的思路其实很简单。与其保存100个0,占用100个bit。还不如保存0一次,然后声明这个0重复了100遍。

这两种合并使用索引的方式都有其用途。Elasticsearch 对其性能有详细的对比,可阅读 Frame of Reference and Roaring Bitmaps。

分片策略

创建索引的时候,我们需要预分配 ES 集群的分片数和副本数,即使是单机情况下。如果没有在 mapping 文件中指定,那么索引在默认情况下会被分配5个主分片和每个主分片的1个副本。

分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分配后由于索引的路由机制,我们是不能重新修改分片数的。例如某个创业公司初始用户的索引 t_user 分片数为2,但是随着业务的发展用户的数据量迅速增长,这时我们是不能重新将索引 t_user 的分片数增加为3或者更大的数。

可能有人会说,我不知道这个索引将来会变得多大,并且过后我也不能更改索引的大小,所以为了保险起见,还是给它设为 1000 个分片吧…

一个分片并不是没有代价的。需要了解:

一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。

每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。

用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwgxd.html