论文:ElasticBF: Elastic Bloom Filter with Hotness Awareness

371 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

论文:ElasticBF: Elastic Bloom Filter with Hotness Awareness for Boosting Read Performance in Large Key-Value Stores

刚好此前有仔细阅读过这篇论文,这次把他发出来。这篇文章可以补充学习到许多bloomfilter的不错的知识。ElasticBF的通篇的想法是,在内存受限的机器上,由于设置了较小的bloomfilter bit-per-key,这导致假阳性特比大,导致bloomfilter带来的误报率太高,进而导致额外的IO过多,它带来了显著的性能退化,于是作者的想法是,如果bloomfilter可以根据热度动态变化误报率,事情是否会不一样呢?作者是中科大的学生,还是不错的。

我想说的是今天看来,内存变便宜了,我们并不吝啬LSM上的bit-per-key设置的过大,因为两点:

  • 内存变得便宜,使用LSM的系统往往具备较大的性能
  • 如果在嵌入式系统上,或者手机上,LSM可能不是一个好的存储选择,这是因为手机这类宿主机内存非常有限,LSM设计上要求是wal+memtable,而往往希望memtable缓存数据,定期flush。而手机这类宿主机往往希望内存尽快同步到磁盘(减少内存使用),以及lsm的compaction,手机可能真的受不了。

来自论文:LSM-based storage techniques: a survey 的评价

所有现有的LSM树实现,甚至Monkey[21],都采用静态方案来管理Bloom过滤器内存分配。也就是说,一旦为组件创建了Bloom过滤器,其误报率将保持不变。相反,ElasticBF[83]根据数据热度和访问频率动态调整Bloom填充器误报率,以优化读性能。假设每个key对应k个布隆过滤器位bits,ElasticBF构造了多个更小的布隆过滤器有 k1,...,kn 位,从而k1+...+kn=k 。当所有这些Bloom过滤器一起使用时,它们提供了与原始整体的Bloom过滤器相同的假阳性率。然后,ElasticBF根据访问频率动态地激活和关闭这些Bloom过滤器,以最小化额外I/O的总量。他们的实验表明,当整个Bloom过滤器内存非常有限,例如平均每个key只有4位时,ElasticBF是有效的。在这种情况下,由Bloom过滤器误报导致的磁盘I/O将占主导地位。当内存相对较大且每个键可以容纳更多位时,例如10位,ElasticBF的好处就会变得有限,因为由假阳性导致的磁盘I/O数量远远小于用于定位键的实际磁盘I/O数量。

这点我是深以为然的。不过既然看完了论文,那就记录下吧。

开局LSM架构的简单介绍,以及compaction的机理,注意,merge sort时,底层数据也要读出来进行合并。

image-20221206210811046

image-20221206211947431

基于LSM树的KV存储也存在严重的读取放大问题。因为当查询一个KV对时,KV存储需要从最低层到最高层检查多个SSTables,直到找到密钥或所有层都被检查过。此外,它需要读取多个元数据块来真正检查一个KV对是否存在于一个SSTable中。因此,读放大可以达到一个超过300倍的。

为了减少检查多个SSTable引起的额外I/O,现代设计在KV存储中使用Bloom过滤器来快速检查单个SSTable中是否存在一个KV对。然而,布隆过滤器存在假阳性的问题,所以即使在SSTable中不存在KV对,它们也可能返回一个阳性的结果,这就产生了不必要的I/O。我们还进行了实验,通过我们­ ,在一个100GB的KV存储中使用每键4比特的Bloom过滤器来测量假阳性的影响。一个key查询平均需要对SSTables进行7.6次检查,但有1.3次磁盘访问是不必要的,是由误报引起的。

image-20221206212453563

如上图,不同的bloomfilter 参数,其FPR误报率是不一样的,上图提到,我们需要

  • 给大点内存,调高bits-per-key数

  • 增加内存缓存(因为bloomfilter是缓存在内存中的)

    bits-per-key的计算方式: image-20221206213403946

    b即是bit-per-key k=hash function number k=ln2*b时,fpr最小

优化思路

image-20221206213603028

作者观察到,不均衡的访问频率,如上图中SSTable ID越大,访问频率越高,这是因为他们在更高的层次,所以,作者认为,对于Hot SSTable,使用更高的bits-per-key,而Cold的SSTable,则使用少的bits-per-bit,这样,我们就能节省内存了。

这里有个难题是LSM树的数据是不变的,一旦SST形成,他就是只读的,这也意味着需要找到一个中可以变bits/key的方案。为了不大改RocksDB,很容易想到,必须在构造SSTable时,就讲不同的bits/key构建出来。

image-20221206220120245

上图中,我们知道,SSTable的bloomfilter是immutable的,所以我们考虑将SSTable中的Bloomfilter切成多个FIlter Group,每个FIlterGroup由多个Filter unit构成。

这里有一个证明:FPR的可分离性。也就是分段bf相乘的FPR等于b=24 per bit的FPR,这个称之为可分离性。

image-20221206220440480

也就是说只要一个filter unit 返回false,key 就不存在,所有的启用的filter unit说key存在,我们才需要去读SST。

image-20221206220737391

作者提到,切分多少Filter unit,才能获得最高的收益呢?注意,这里的收益是:由于FPR导致的额外的IO最小。其中M指的是KV存储中的总段数。fi 表示第i段的访问频率, ri 表示为Seg I分配的BF的FPR。经验法则是调整过滤单元的数量,从而改变ri ,以使E[Extra IO]减少。

调整BF的程序如下。每次当一个段被访问时,我们更新它的访问频率和E[Extra IO],然后我们检查,如果为这个段多启用一个过滤单元(也就是它比较热,热才会被访问),为其他段禁用一个filter 单元(相当于LRU)以保证相同的内存使用的情况下,是否可以减少E[Extra IO]?

如果E[Extra IO]可以减少,那么我们就进行调整,否则,我们什么都不做。注意,在这个调整过程中,一个关键的问题是要找出应该禁用的过滤单元,我们通过维护一个基于多队列的内存索引来解决这个问题,这将在后面介绍。

image-20221206222310317

ElasticBF维护了多个内存中的最短最近使用时间(LRU)队列来管理每个片段的元数据,我们将这些队列表示为Q0...Qn,n是在每个segment中能分配的最大filter unit数。

每个队列元素对应这一个segment,和他管理的filter unit。例如在Qi的每个元素表示在segment的i个filter被启用。例如上图中,在Q2,有2个filter unit 被启用了。上图也描述了segment升级的过程。

image-20221206223837118

最后一个问题,各种混合的工作负载,可能重置hotness信息,以及,compaction也会导致hotness信息变化。如何继承hotness?只是简单地求hotness的平均数。