如果说protocol buffer是google事实上的individual data record 描述语言,那SSTable应该是google最流行的dataset存储、处理和交换方案之一。SSTable是高效存储大量KV数据集合的一个简单抽象,并针对高吞吐量、顺序读写负载等进行了优化。
本文阐述SSTable的工作原理,以及levelDB中如何使用SSTable。
SSTable数据格式、索引、读写性能
SSTable全称是Sorted String Table,它就是一组有序的KV对,这便是文件的基本结构。非常简单。
对这样的数据,如何创建索引?简直太方便了!将整个文件顺序读一遍,就可以得到sorted index;或者如果value占的空间很大,可以创建一个key:offset索引文件,将其prepend到数据文件,也可以单独保存。总之,索引很容易建立。
SSTable文件一旦在内存中读入了索引,读取任何数据都只需要一次磁盘read操作。或者直接将整个文件mmap。可见,SSTable的随机读简单而快速。
没有免费的午餐,也没有读写均秒杀同侪的数据机构。SSTable的随机写操作代价高多了,因为修改一个元素就会导致整个SSTable文件都需要重写,这代价高到让所有人都绝望了,因而通常SSTable一经写到磁盘,就不再修改。
解决写性能问题:LSM-tree
绝望之余,还是得想办法改善写性能。怎么办?把sstable放到内存里即可,这也就是著名的memtable(我们将sstable特指磁盘上的数据结构,memtable特指内存中的,避免混淆)。我们按照如下的约定来访问数据:
1) sstable的index总是被加载到内存中
2) 所有的写操作都写到memtable
3) read操作先检查memtable,然后在读取sstable index
4) 周期性地将memtable 数据 flush到磁盘sstable
5) 周期性地将磁盘上的sstable进行合并
按上面的方法,写操作都在内存中,因而性能也很好。当memtable达到一定大小,将会flush为不可修改的sstable,其索引依然保留在内存中。每次读操作会先访问memtable,然后是sstable index,从而最多只有一次磁盘访问。
到这里我们发现,我们用的刚好就是LSM-Tree的做法,BigTable的tablets使用的也是这样的机制。
这种LSM-tree数据结构确保写操作总是很快,无论数据集合有多大;而随机读操作则要么在memtable命中,要么最多只有一次磁盘访问,性能也很令人满意。有些读者可能已经在思考更新和删除操作了。
更新、删除操作
SSTable是不能修改的,因而update和delete的做法也跟传统方法有不同。Update时,只需要在memtable中存一个新版,删除时则保存一个“墓碑标记”,老版本的数据暂时不用管。在后续的read操作中,读到新版本或者“墓碑标记”时,就立即返回,而老版本数据由于读取顺序,根本就不会被读到!
那这些残留的数据如何清理呢?当SSTable文件过多时(通常数百个),会运行一个后台任务对其进行合并,此时残留的老版本数据、已删除数据就被覆盖、删除等等。
总结
使用SSTable和MemTable,并遵守一定的用于管理大量SSTable的"log structured" 处理约定,我们就可以得到一个数据库引擎,在某些特定的负载环境下,性能非常不错。事实上,除了levelDB,bigtable/hbase/cassandra也都使用了类似的方法。
参考:
https://www.igvita.com/2012/02/06/sstable-and-log-structured-storage-leveldb/
http://nosqlsummer.org/paper/lsm-tree
http://en.wikipedia.org/wiki/BigTable#Design
http://blog.sina.com.cn/s/blog_4575b5690102wnyh.html