本文是阅读了DDIA的第三章后整理的读书笔记,总结了什么是LSM存储引擎,以及在实现上的一些细节优化。数据库
LSM一词最先来自于Partrick O'Neil et al.发表的文章[1],全称为"Log-Structured Merged-Tree"。后来,Google发表的Bigtable论文[2]将其发扬光大。apache
目前全部基于该思想实现的存储引擎,咱们均可以称之为“LSM存储引擎”,例如:分布式
不一样于传统的基于B+树的数据库存储引擎,基于LSM的引擎尤为适合于写多读少的场景。oop
咱们先从一个最简单的存储引擎示例出发,而后再描述LSM引擎的基本原理。性能
一个最基本的存储引擎,须要支持下面两个操做:优化
咱们知道,磁盘特别是机械硬盘,其随机写的速度是很是惨不忍睹的。可是,若是咱们是顺序写磁盘的话,那速度跟写内存是至关的:由于减小了寻道时间和旋转时间。并且顺序写的状况下,还能将数据先放到buffer,等待数量达到磁盘的一页时,再落盘,能进一步减小磁盘IO次数。ui
因此,这里咱们规定,每一次写数据都追加到数据文件的末尾。spa
#!/bin/sh
# usage: ./Put key value
echo "$1:$2" >> simpledb.data
复制代码
注:若是对同一个key写屡次,最终以最后一次的值为准(即对于读请求,应返回最后一次写入的值)。线程
要从数据文件里面查询:设计
# usage: ./Get key
grep "^$1:" simpledb.data | sed -e 's/^://g' | tail -n 1
复制代码
直接从数据文件查询的效率是很低的:咱们须要遍历整个数据文件。
这时候就须要“index”来加快读操做了。咱们能够在内存中保存一个key到文件偏移量的映射关系(哈希表索引):在查找时直接根据哈希表获得偏移量,再去读文件便可。
固然,加了索引表也相应地增长了写操做的复杂度:写数据时,在追加写数据文件的同时,也要更新索引表。
这样的建索引的方式有个缺点:由于索引map必须常驻内存,因此它无法处理数据量很大的状况。当内存没法加载完整索引数据时,就没法工做了。
咱们再来看看另一个问题:传统的B+树,每一个key只会存一份值,占用的磁盘空间是跟数据量严格对应的。可是在追加写的方案中,磁盘空间是永无止尽的,只要这个系统在线上运行,产生写请求,文件体积就会增长。
解决文件无限增加的方法就是 compaction:
以上就是一个简单的基于内存索引+文件分段并按期压缩的存储引擎。能够看到,它可以提供很好的写入性能,可是没法应对数据量过大的场景。
假定如今咱们要存储N
对key-value
,那么咱们一样须要在索引里面保存N
对key-offset
。可是,若是数据文件自己是按序存放的,咱们就不必对每一个key建索引了。咱们能够将key划分红若干个block
,只索引每一个block
的start_key
。对于其它key,根据大小关系找到它存在的block
,而后在block
内部作顺序搜索便可。
在LSM里面,咱们把按序组织的数据文件称为SSTable
(Sorted String Table)。只保存block
起始key的offset的索引,咱们称为“稀疏索引”(Sparse Index)。
并且,有了block
的概念以后,咱们能够以block
为单位将数据进行压缩,以达到减小磁盘IO吞吐量。
SSTable
呢?咱们能够在内存里面维护一个平衡二叉树(例如AVL树或者红黑树)。每当有Put(Key, Value)
请求时,先将数据写入二叉树,保证其顺序性。当二叉树达到既定规模时,咱们将其按序写入到磁盘,转换成SSTable
存储下来。
在LSM里面,咱们把内存里的二叉树称为memtable
。
注意,这里的
memtable
虽然也是存在内存中的,可是它跟上面说的稀释索引不同。对每个SSTable
,咱们都会为它维护一个稀疏的内存索引;可是memtable
只是用来生成新的SSTable
。
对SSTable
,咱们一样是经过 segment + compaction 来解决磁盘占用的问题。
分segment
在memtable
转SSTable
的时候就已经作了。
compaction
则依赖后台线程按期执行了。可是对于有序的的SSTable
,咱们可使用归并排序的思路来合并和压缩文件:
若是在将memtable
转存SSTable
时,进程挂掉了,怎么保证未写入SSTable
的数据不丢失呢?
参考数据库的redo log
,咱们也能够搞一个log
记录当前memtable
的写操做。在有Put
请求过来时,除了写入memtable,还将操做追加到log。当memtable
成功转成SSTable
以后,它对应的log
文件就能够删除了。在下次启动时,若是发现有残留的log
文件,先经过它恢复上次的memtable
。
对于查询那些不存在的key,咱们须要搜索完memtable
和全部的SSTable
,才能肯定地说它不存在。
在数据量不大的状况下,这不是个问题。可是当数据量达到必定的量级后,这会对系统性能形成很是严重的问题。
咱们能够借助Bloom Filter(布隆过滤器)来快速判断一个key是否存在。
布隆过滤器的特色是,它可能会把一个不存在的key断定为存在;可是它毫不会把一个存在的key断定为不存在。这是能够接受的,由于对于极少数误判为存在的key,只是多几回搜索而已,只要不会将存在的key误判为不存在就行。并且它带来的好处是显而易见的:能够节省大量的对不存在的key的搜索时间。
上文已经提到,咱们须要对SSTables
作合并:将多个SSTable
文件合并成一个SSTable
文件,并对同一个key,只保留最新的值。
那这里讨论的合并策略(Compaction Strategy)又是什么呢?
A compaction strategy is what determines which of the sstables will be compacted, and when.
也就是说,合并策略是指:1)选择何时作合并;2)哪些SSTable
会合并成一个SSTable
。
目前普遍应用的策略有两种:size-tiered
策略和leveled
策略。
size-tiered
策略。leveled
策略。这里简要介绍下两种策略的基本原理。后面研究LevelDB
源码时再详细描述leveled
策略。
size-tiered
策略简称STCS(Size-Tiered Compaction Strategy)。其基本原理是,每当某个尺寸的SSTable
数量达到既定个数时,合并成一个大的SSTable
,以下图所示:
它的优势是比较直观,实现简单,可是缺点是合并时的空间放大效应(Space Amplification)比较严重,具体请参考Scylla’s Compaction Strategies Series: Space Amplification in Size-Tiered Compaction。
空间放大效应,好比说数据自己只占用2GB,可是在合并时须要有额外的8G空间才能完成合并,那空间放大就是4倍。
leveled
策略STCS
策略之因此有严重的空间放大问题,主要是由于它须要将全部SSTable文件合并成一个文件,只有在合并完成后才能删除小的SSTable文件。那若是咱们能够每次只处理小部分SSTable
文件,就能够大大改善空间放大问题了。
leveled
策略,简称LCS(Leveled Compaction Strategy),核心思想就是将数据分红互不重叠的一系列固定大小(例如 2 MB)的SSTable
文件,再将其分层(level)管理。对每一个Level
,咱们都有一份清单文件记录着当前Level
内每一个SSTable
文件存储的key的范围。
Level和Level的区别在于它所保存的SSTable
文件的最大数量:Level-L
最多只能保存 10 L 个SSTable
文件(可是Level 0
是个例外,后面再说)。
注:上图中,"run of"就表示一个系列,这些文件互不重叠,共同组成该
level
的全部数据。Level 1
有10个文件;Level 2
有100个文件;依此类推。
下面对照着上图再详细描述下LCS
压缩策略:
先来看一下当Level >= 1
时的合并策略。以Level 1
为例,当Level 1
的SSTable
数量超过10个时,咱们将多余的SSTable
转存到Level-2
。为了避免破坏Level-2
自己的互不重叠性,咱们须要将Level-2
内与这些待转存的SSTable
有重叠的SSTable
挑出来,而后将这些SSTable
文件从新合并去重,造成新的一组SSTable
文件。若是这组新的SSTable
文件致使Level-2
的总文件数量超过100个,再将多余的文件按照一样的规则转存到Level-3
。
再来看看Level 0
。Level 0
的SSTable
文件是直接从memtable
转化来的:你无法保证这些SSTable
互不重叠。因此,咱们规定Level 0
数量不能超过4个:当达到4个时,咱们将这4个文件一块儿处理:合并去重,造成一组互不重叠的SSTable
文件,再将其按照上一段描述的策略转存到Level 1
。
[1] Patrick O’Neil, Edward Cheng, Dieter Gawlick, and Elizabeth O’Neil: “The Log- Structured Merge-Tree (LSM-Tree),” Acta Informatica, volume 33, number 4, pages 351–385, June 1996. doi:10.1007/s002360050048
[2] Fay Chang, Jeffrey Dean, Sanjay Ghemawat, et al.: “Bigtable: A Distributed Storage System for Structured Data,” at 7th USENIX Symposium on Operating System Design and Implementation (OSDI), November 2006.