在所有的组件对(Ci-1,Ci)之间都有一个异步的rolling
merge过程负责在较小的组件Ci-1超过阈值大小时,将它的记录移到Ci中。一般来说,为了保证LSM-tree中的所有记录都会被检查到,对于一个精确匹配查询和range查询来说,需要访问所有的Ci组件。当然,也存在很多优化方法,可以使搜索限制在这些组件的一个子集上。
所以,LSM-Tree之多组件算法便是:我们将一个具有组件C0,C1,C2…Ck-1和Ck的多组件LSM-tree,索引树的大小伴随着下标的增加而增大,其中C0是驻留在内存中的,其他则是在磁盘上。在所有的组件对(Ci-1,Ci)之间都有一个异步的rolling
merge过程负责在较小的组件Ci-1超过阈值大小时,将它的记录移到Ci中。
1.3.1、LSM-Tree之插入与删除
LSM-Tree之插入
首先,如果生成逻辑可以保证索引值是唯一的,比如使用时间戳来进行标识时,如果一个匹配查找已经在一个早期的Ci组件中找到时那么它就可以宣告完成了。再比如,如果查询条件里使用了最近时间戳,那么我们可以让那些查找到的值不要向最大的组件中移动。当merge游标扫描(Ci,Ci+1)对时,我们可以让那些最近某个时间段(比如τi秒)内的值依然保留在Ci中,只把那些老记录移入到Ci+1。
在那些最常访问的值都是最近插入的值的情况下,很多查询只需要访问C0就可以完成,这样C0实际上就承担了一个内存缓冲区的功能。比如,用于短期事务UNDO日志的索引访问模式,在中断事件发生时,通常都是针对相对近期的数据的访问,这样大部分的索引就都会是仍处在内存中。通过记录每个事务的启动时间,就可以保证所有最近的τ0秒内发生的事务的所有日志都可以在C0中找到,而不需要访问磁盘组件。
LSM-Tree之删除
需要指出的是删除操作可以像插入操作那样享受到延迟和批量处理带来的好处。当某个被索引的行被删除时,如果该记录在C0树中对应的位置上不存在,那么可以将一个删除标记记录(delete
node entry)放到该位置,该标记记录也是通过相同的key值进行索引,同时指出将要被删除的记录的Row
ID(RID)。实际的删除可以在后面的rolling merge过程中碰到实际的那个索引entry时执行:也就是说delete node
entry会在merge过程中移到更大的组件中,同时当碰到相关联的那个entry,就将其清除。与此同时,查询请求也必须在通过该删除标记时进行过滤,以避免返回一个已经被删除的记录。
该过滤很容易进行,因为删除标记就是位于它所标识的那个entry所应在的位置上,同时在很多情况下,这种过滤还起到了减少判定记录是否被删除所需的开销(比如对于一个实际不存在的记录的查找,如果没有该删除标记,需要搜索到最大的那个Ci组件为止,但是如果存在一个删除标记,那么在碰到该标记后就可以停止了)。对于任何应用来说,那些会导致索引值发生变化(比如一条记录包含了ID和name,同时是以ID进行索引的,那么如果是name更新了,很容易,只需要对该记录进行一个原地改动即可,但是如果是ID该了,那么该记录在索引中的位置就要调整了,因此是很棘手的)的更新都是不平凡的,但是这样的更新却可以被LSM-tree一招化解,通过将该更新操作看做是一个删除操作+一个插入操作。
1.3.2、LSM-Tree多组件算法的开销
通过上文,我们已经了解到:一个具有K+1个组件的LSM-tree具有组件C0,C1,C2…,Ck-1和Ck,组件大小依次递增;C0组件是基于内存的,其他都是基于磁盘的(对于那些经常访问的页面来说会被缓存在内存中)。在所有的组件对(Ci-1,Ci)之间都存在一个异步的rolling merge过程,它负责在Ci-1超过阈值大小时,将记录从较小的组件中移入到较大的组件中。当一个生命期很长的记录被插入到LSM-tree之后,它首先会进入C0树,然后通过这一系列的K个异步rolling merge过程,最终将被移出到Ck。还是引用上图,如下: