在leveldb中,除了sstable文件格式,还有log文件格式。该文件格式用于存储写操作的日志与manifest文件(不同的文件名)。前者用于异常回滚。后者用于记录sstable文件的元数据。整体架构中提到过,leveldb在将记录写入内存中的memtable之前,会先写入log文件,memtable会延后持久化。在这个过程中进程可能down掉。有了log写操作文件后,即使系统发生故障,levelDB也可以根据log写操作日志文件恢复内存的memtable内容,不会造成丢失数据。而manifest文件用于记录所有的sstable文件的元数据,比如sstable文件的编号,key范围。
log文件格式也是分块存储的。跟sstable文件不同的是,一方面sstable是有序存储的,因此为了加速读取,有相关索引,而log文件始终是顺序读写的,不需要定位某个key,因而不需要索引信息。另一方面sstable的data block虽大致按4KB分块,但实际上存储的块大小通常会比4KB大,这主要是因为单条record不会跨block。而log文件中的block则严格保证为一样,默认值为32KB。因此,log文件中的record可能会跨块,为了区分record是否结束,不同的block有不同的类型(kFullType/kFirstType/kMiddleType/kLastType)。如果block剩余的空间足以存储新的record,则type取值为kFullType,否则则有可能出现1个FirstType的block(剩余部分block存储record的部分内容)+ 0或者多个kMiddleType的block + 1个kLastType的block。除了type字段之外,record还包括check sum与数据的长度,最后才是具体的数据。校验和针对type+length+data。详细的格式如下图所示。如果剩余block的空间<7(checksum+length+type),则剩余的直接填充0,另起一块存储数据。leveldb严格按照32k为一个单位读写一个block。
doc/log_format.txt中讲述了这种文件格式的benefit与downside。
好处如下:
(1)容错性好。不需要额外的信息来同步。发现出错了,直接跳至下一个block(32K为一个block)。
(2)容易切分,适合mapreduce等大数据处理方式。切分时,需按逻辑上的一个Record。
(3)对于大记录,也不需要额外的字段来表示其长度。自然的按32k切分了嘛。
一些限制如下:
(1)对于小记录,没有pack机制。
(2)没有压缩。
对于这些限制,作者认为可以通过添加新的type来实现。
代码主要在log_format.h,log_reader.cc/.h,log_writer.cc/.h。前者定义了一些,log::Writer类用于写,比较简单,log::Reader考虑到容错,稍微复杂些。但整体上还是很简单的,给点耐心看吧。