基于合并和压缩排序文件原理,以日志结构为主的的存储引擎——LSM引擎。
使用相关算法的数据库:LevelDB、RocksDB、Cassandra、HBase等
- log写入(追加写入)时,将其添加到内存中的平衡树(内存表)数据结构中。
- 当内存大于某个阈值(通常为若干MB,如redis自动aof的size是64mb)时,将其作为SSTable文件写入磁盘。由于树已经维护了按key排序的key-value对,写磁盘可以相对高效。新的SSTable已经成为数据库的最新部分。当SSTable写磁盘的时候,写入可以继续添加到一个新的内存表实例。
- 每次处理读请求,首先常识在内存表中查找key,然后是最新的磁盘段文件,然后是次新的磁盘段文件,以此类推,直到找到目标或者为空。
- 后台进程周期性地执行合并与压缩的过程,来合并多个段文件(可变大小),并丢弃哪些被覆盖或者被删除的部分。
- 如果数据库崩溃,在内存表中但是尚未写入磁盘的log将会丢失,为了避免这种情况,可以在磁盘上保存单独的日志,每个写入都会立刻追加到日志。日志文件不需要按键排序,它唯一的目的是在崩溃中恢复内存表,当内存表写入SSTable时,相应的日志就可以被丢弃了。
一些可以优化的点
- 布隆过滤器,直接判断key是否存在,节省空key的查找
- 改变段合并和压缩的时机
- 大小分级,HBase
- 分层压缩,LevelDB,RockesDB
- Cassandra支持以上两种
相对于B-tree的优点
- LSM顺序写入磁盘的效率远高于随机写,并且定期重写SSTable可以去除磁盘碎片化。B-Tree则可能使磁盘空间部分空间无法使用(比如分裂页崩溃时导致的孤儿页)。
- LSM相对于B-Tree拥有较小的写放大,B-Tree索引需要至少两次写入(一次WAL日志,一次写入树的页),每次更改有都要承受整个页的更改开销。而LSM以顺序方式写入文件,不必重写树中多个页。
相对于B-tree的缺点
- 压缩过程容易干扰正在进行的读写操作,造成读写等待。B-Tree查询响应则更稳定。
- 写入吞吐高的情况下,压缩带宽可能被写入带宽占用导致不足,造成磁盘空间有大量未合并段占用空间,因此需要额外的监控来及时发现这种情况。
- 同一个键可能存在多个位置,B-Tree则每个键都唯一卫浴索引中的某个位置,更适合事务的使用。
最广泛使用的索引——B-Tree
拥有n个分支因子的B-Tree的深度为logn,一般是3-4层,分支因子为500的4kb的页可以存储数据256TB。
- 将数据库分解为固定大小的页(内部读写最小单元),一般是4kb。每个页使用地址/位置进行标识,可以让一个页引用另一个页,形成一个树状结构。(类似指针,但是指向的是磁盘地址,不是内存)页包含若干键和对子页的引用等内容
- 指定b树的一页为根,每当查询索引的一个键时候,从根节点开始
- 键的更新:搜索含此键的叶子页,更改页的值,将页回写到磁盘。
- 键的新增:类似于更新,如页空间满了,需要分裂成两个页,父页也需要进行更新
- b树的增删改算法
一些存在的隐患以及可以采取防止手段
- 键的新增需要更新父页时,数据库崩溃导致索引被破坏
- 增加WAL(write-ahead log)日志,只支持追加写。在数据库奔溃的时候先用日志恢复B-tree
- 使用写时复制,修改的页被写入不同的位置,树中的父页的新版本被创建,指向新的位置。(也能解决并发下多线程访问的问题)
- 节省页的空间,只保存键的缩略信息。B+树
- 为叶子页添加指向同级别的兄弟页,可以顺序扫描页不同跳回父页