为什么需要LSM算法
LSM被设计来提供比传统的B+树或者ISAM更好的写操作吞吐量,通过消去随机的本地更新操作来达到这个目标。
设计LSM算法的本质是:磁盘随机操作慢,顺序读写快。磁盘随机操作与顺序操作存在这巨大的差距,无论是磁盘还是SSD。
上图很好的说明我们要避免随机读写,最好设计成顺序读写。
如果我们对写操作敏感,最好的办法是采用顺序写,将数据直接添加到文件。这种策略通常被使用日志文件或者堆文件中。可以提供非常好的写操作性能。
因为简单和高效,基于日志的策略在大数据之间越来越流行,同时他们也有一些缺点,从日志文件中读一些数据将会比写操作需要更多的时间,需要倒序扫描,直接找到所需的内容。
这说明日志仅仅适用于一些简单的场景:
1. 数据是被整体访问,像大部分数据库的WAL(write-ahead log)
2. 知道明确的offset,比如在Kafka中。
所以,我们需要更多的日志来为更复杂的读场景(比如按key或者range)提供高效的性能,这儿有4个方法可以完成这个,它们分别是:
- 二分查找: 将文件数据有序保存,使用二分查找来完成特定key的查找。
- 哈希:用哈希将数据分割为不同的bucket
- B+树:使用B+树 或者 ISAM 等方法,可以减少外部文件的读取
- 外部文件: 将数据保存为日志,并创建一个hash或者查找树映射相应的文件。
所有的方法都可以有效的提高了读操作的性能(最少提供了O(log(n)) ),但是,却丢失了日志文件超好的写性能。上面这些方法,都强加了总体的结构信息在数据上,数据被按照特定的方式放置,所以可以很快的找到特定的数据,但是却对写操作不友善,让写操作性能下降。
所以这就是 LSM 被发明的原理, LSM 使用一种不同于上述四种的方法,保持了日志文件写性能,以及微小的读操作性能损失。本质上就是让所有的操作顺序化,而不是像散弹枪一样随机读写。
LevelDB 中SSTable 和Log Structured Storage
SSTable 和MemTable
SSTable本质上是一个文件。文件里的键值对都是有序的, SSTable一旦创建就是不可变的。为了能够快速访问SSTable,可以通过索引进行访问。MemTable顾名思义,是存放在内存里的key-value键值对。SSTable和MemTable的关系图如下:
- SSTable的索引文件总是加载到内存里。
- SSTable存放在磁盘文件里。
- 所有的写操作直接写到MemTable里。
- 读操作时先检查MemTable里是否存放数据。如果没有则在SSTable indexs中进行搜索。
- 定时的把MemTable中的数据刷新到磁盘中的SSTable中。
- 定时的,磁盘中的SSTable做merge操作合并为大的SSTable。
LSM & SSTables:update 和delete 维护
LSM算法有很多有趣的特性,写总是很快的无论数据集多大。写操作是(append-only),写操作在内存中完成,然后flush到disk。读操作先是从Memtable中查找,当一个读操作请求时,系统首先检查内存数据(memtable),如果没有找到这个key,就会逆序的一个一个检查sstable文件,直到key 被找到。因为每个sstable都是有序的,所以查找比较高效(O(logN)),但是读操作会变的越来越慢随着sstable的个数增加,因为每一个 sstable都要被检查。
LSM的更新操作存放在Memtable中。删除操作也存放在Memtable中,记录中标记为已删除记录。
所以系统会周期的执行合并操作(compaction)。 合并操作选择一些文件,并把他们合并到一起,移除重复的更新或者删除纪录,同时也会删除上述的冗余。更重要的是,通过减少文件个数的增长,保证读操作的性 能。