doc里的文档就打算翻译到这,之后应该是针对源码的分析了。
doc/里还有一个benchmarks.html,主要是讲leveldb和SQLite3,Kyoto Cabinet's的性能对比,就不翻译了。
看了这个impl.html之后对于leveldb的实现还是完全没有概念,只是知道为何称为"level"DB,应该是采用了对数据划分为不同level这种策略,所以取名为LevelDB。
具体的每个部分理解都和模糊。一方面是因为翻译的时候作者的意思可能理解错了。
另外一方面是因为还没有看过实现的源码,没有结合源码空谈实现机制很虚。
所以在之后的源码分析中,对于翻译错误的部分我一定会及时修改,希望这样的反馈过程能够促进我的学习。
Files
leveldb的实现,在精神层面上是与Bigtable table的十分相似的。但是由于文件组织的不同,还是有些差异的。每个database都是通过目录下的一些文件来代表的。下面有很多不同类型的文件类型:Log files
一个log文件(*.log)中存储的是一些最近的更新。每个更新都会被添加到当前的log文件文件中。当log文件达到了预先定义的一个大小(大约4MB),他会被转化为一个排好序的表,紧接着生成一个新的log文件用来存储之后的更新。
在内存中有一份对当前的log文件的拷贝,这个拷贝是以memtable的形式存储的。所以每次read操作都会先从这个内存的中拷贝中寻找,你通过read操作也可以读取最近的所有更新记录。
Sorted tables
一个sorted table(.sst)存储着根据key值排序后记录。每一个记录要么是一个key的value,要么是对这个key的删除标志(删除标志是用来覆盖旧的sorted table里存储的数据)。
这些sorted tables被划分为几个level。刚刚从log file生成的sorted table被分在一个特殊的level--young level(也可以称为level-0)。当level为young的文件达到一定数量(目前是4),就会把所有的young level的文件和那些与young level有重叠的key的level-1的文件重新整合在一起,产生一列新的level-1的文件。(一般每个level-1的文件大小为2MB)。
level为young的文件里可能包含了重复的key。但是level大于等于1的文件中的key肯定都是独一无二的。对于level为L的文件(L>=1),这些等级为L文件的总和一旦超过(10^L)MB,就进行下面的整合操作:
举例:
L_II集合中,与L_I1有相同key的文件为{L_II1,L_II2,L_II4,L_II7}
当 L_I1+L_I2+...+L_In>(10^L)MB
就把 L_I1和L_II1,L_II2,L_II4,L_II7整合生成新的文件,新生成的文件划分到Level-(L+1)这个等级中。
这样的整合操作可以把young level中新更新的数据逐渐往下一个level中迁移(这样可以使查询的成本降低到最小)。Manifest
一个Manifest文件中列出了以下内容:
1.组成每个level的sorted tables
2.对应的key的范围
3.其他重要的元数据
每当database重新打开,就会生成一个新的Manifest文件(每个Manifest类型把 的文件名中都会有一个数字)。Manifest文件的内容格式就像log文件的格式一样,每当服务状态发生改变(例如文件的添加或者删除)就会添加到Manifest的末尾。
Current
CURRENT是个简单的text文件,内容是最新的Manifest文件名。
Info logs
通知性质的消息会存储到LOG或者LOG.old文件中。
Others
还有其他作用复杂的文件,例如LOCK, *.dbtmp等。
Level 0
当写入log文件的数据量达到一个预定的值(默认的是1MB):
生成一个新的memtable和log文件,将之后的更新直接写入到这里。
在后台:
把先前在memtable中的内容写入到一个sstable中。
丢弃memtable.
删除旧的log文件和旧的memtable文件。
把新的sstable文件划分为level-0等级。
Compactions
当level-L的文件总和超过预定的大小后,后台线程就会对数据进行整合。从L等级的文件中挑选一个,把它和L+1等级中所有有重叠的文件整合在一起。对于Level-0的文件,整合规则比较特殊:把level-0中所有有重叠的文件都挑出来,然后和level-1中有重叠的文件一起整合。
在上面的整合操作中,我么挑选level-L的文件,生成level-L+1的文件。有以下规则:
当输入的level-L文件足够生成2MB的level-L+1文件时我们才执行上面的操作。
当新的level-L+1文件中,已经存在能够和10个level-L+2的文件重叠的时候,我们会对level-L+1进行整合。
第二条规则是为了使得之后对level-L+1的整合能够简单一些。
旧的文件被丢弃之后,新的文件被加入到服务状态。
对特定level文件之间的整合是通过key的空间来转换的。详细的解释,例如对于level-L文件的整合,我们记录下最后一次整合的key,在下一次整合level-L文件的时候我们挑选的文件的key是在上次记录的key之后的(如果不存在这样的文件,那就从第一个文件开始)。
整合的时候丢弃掉被重写的值,那些被标记为deletion的记录,如果在level更加高级的文件中并不存在的话,就可以直接删除了。
Timing
level-0的文件整合工作最坏情况下,我们将读取4个level-0的文件(每个1MB)和所有的level-1文件(一共10MB)。我们将会一共读取14MB的文件。
除了对levle-0以外,当我们挑选了一个level-L的2MB文件时,我们最坏情况下可能有12个level-L+1文件与这个文件重叠(其中10个是因为level-L+1的总规模是level-L的10倍,其他2个文件因为level-L的文件分布在level-L+1的时候可能不是对齐的,所以出现了两个边界的文件)。这样一来整合操作需要读写一共26MB的文件。现代的硬盘IO速率大致是100MB/s,对于最坏的整合情况用时大概是0.5s。
如果我们限制了后台对写入的速率,比如降低为原来的10%,那么我们的整合需要花费5s。如果真的限制了写入速率为10MB/s,我们可能需要建立更多的level-0文件(大约50个,一共50MB)。这样一来由于需要我们整合的文件数目上限提高了,我们的每次读取的时候费时将变多。
当level-0文件的数量变多,我们可以提高log文件的转换门槛。但是随着门槛的提高,我们装载对应的memtable所需的内存就越大。
解决方法2:
当level-0文件的数量提高了,我们可以人为地减少写入的效率。
解决方法3:
我们可以致力于减少大文件整合的费时。或许很多level-0的文件都有对应的block来缓存那些未经过压缩的数据。这样我们仅需考虑O(n)复杂度的整合迭代。
Number of files
文件的大小随着level的提高而提高,不能一直维持在2MB,这样才能减少level较高的文件的总数,尽管有时突发性的整合操作是很消费资源。我们可以选择在多个文件目录中共享同一系列的文件。
一个装载着ext3的文件系统的设备,目录中装载的文件大小都是100k,打开目录的耗时统计如下:
1000 9
10000 10
100000 16
Recovery
.从CURRENT文件中读取出最后更新的MANIFEST文件名。
.读取MANIFEST文件的内容。
.清除过期的文件。
.这个时候我们本可以打开所有的sstable,但是最好不要这样做。
.把log记录转化为level-0的sstable
.开始恢复的数据写入一个新的log中。
Garbage collection of files
在每次文件整合或者数据恢复之后都调用DeleteObsoleteFiles()方法。这个方法将找出database中所有的文件。删除所有非不是最新的log文件。还删除所有不是level文件引用的或者不是文件整合需要使用的table。