levelDB实现细节

这篇文章是levelDB官方文档的译文,原文地址:Implementation notes

1. Files

leveldb的实现和一个单点的Bigtable tablet (section 5.3)很相近。然而,文件的组织形式又有些不太一样,下文会解释这一点。

每一个数据库都是存储在一个目录的一系列文件的集合。有以下几种不同类型的文件:

1.1 Log files

(译者注:这里的log files指的是保存key_value对的文件)

log文件(*.log)存储一系列最近的更新。每次更新都是追加在当前的log文件后面。当log文件达到预设的大小(默认是4MB),就转换成一个有序表sorted table,并为后续的更新创建一个新的log文件。

当前log文件的副本保存在内存中(the memtable)。每次读都会检索当前的log文件,因此读操作反应了所有log文件中的更新。

1.2 Sorted tables

排序表(sorted table *.sst)存储一系列的排好序的key-value entry。每个entry要么是一个key-value对,要么是key对应的删除标记。(在老版本的排序表里面,删除标记是一个隐式的过期值)

排序表的集合被组织成一系列级别(level)的形式。从一个log file生成的排序表放置在一个特殊的年轻级别(young level, 也称作level-0),当level-0的文件数量超过阈值(目前是4)时,所有level-0的文件和一些有重合的level-1级别的文件归并成一系列新的level-1级别的文件,level-1级别的文件数据大小是2MB.

level-0级别的不同文件之间存在key的重合。然而,在其他同一级别上的不同文件之间的key区间是唯一的不重合的。例如,level L(L>=1),当Level-L上的文件总和超过 10^L MB,(Level-1: 10MB, Level-2: 100MB, …),一个Level-L的文件和所有在Level-(L+1)有key重合的文件合并成一系列新的Level-(L+1)文件。这些合并通过数据块的读写逐渐将新的数据从level-0迁移到最大level,可以减少一些昂贵的磁盘寻址操作。

译者注:
每层文件的大小和总大小的关系表

levelsingle file sizelevel total size
level-01 MB4 MB(default 4 files)
level-12 MB10 MB
level-22 MB100 MB
level-N2 MB10^N MB
1.3 Manifest

MANIFEST文件列出了每一个level上的排序表(sorted tables)的集合 ,相应的key范围,以及一些其他重要的元数据(metadata)。数据库被重新打开的时候会生成一个新的MANIFEST文件,MANIFEST文件名里面包含一个区别于老的MANIFEST文件的数字。MANIFEST是一个log文件的格式,文件的新增和移除等服务状态的变化会追加到文件的末尾。

1.4 Current

CURRENT是一个保存当前正在使用的MANIFEST文件名的简单txt文件。

1.5 Info logs

一些输出到LOG或者LOG.old文件的有用信息。

1.6 Others

其他各式各样用途的文件,例如LOCK,*.dbtmp等。

2. Level 0

当log文件增长到超过一个大小的时候(默认是1MB):
- 新建一个全新的memtable和log文件,然后后续会更新到新的文件
- 一些后台的处理:
- 把之前替换下来的memtable的内容写到sstable
- 丢弃memtable
- 删除老的log文件和老的memtable
- level-0增加一个新的sstable

3. Compactions

(译者注:compaction不能直接翻译成压缩,因为这个是一个把Level-L文件向Level-(L+1)层合并的过程。)

当Level-L的大小达到限制之后,我们会在一个后台线程中对数据进行压缩合并。合并压缩时会从Level-L中取一个文件A,并把Level-(L+1)层中和A中存在key重合的文件全部取出来。如果A只与Level-(L+1)层的一个文件B的部分key存在重合,那么文件B会作为合并压缩的输入,并会在合并压缩完成之后丢掉。副作用:level-0的所有文件之间可能存在key的重合,所以当level-0向level-1合并的时候会有一些特殊:如果level-0的文件之间存在key重合,那么会取level-0的多个文件。

合并压缩会把输入的文件合并然后生成一系列的Level-(L+1)文件。在合并的时候生成一个新文件的条件:1,当前的输出文件达到2MB。2,如果当前的输出文件和10个以上Level-(L+2)层文件存在key重合。这个确保以后Level-(L+1)层和Level-(L+2)层合并的时候不至于一次性合并太多的文件。

当合并后的新文件合并完成并使用后丢弃旧的文件。

对一个特定层来讲,合并压缩会在所有的key之间进行轮转。例如,对于level-L层来说,我们记下来上次合并压缩的最后一个key,在下次合并压缩的时候,我们从上次记下的key后面的第一个key的那个文件开始,如果key是key space的最后一个key,就从key space的第一个重新开始。

合并压缩会丢弃被覆盖的(overwritten)值。如果更高层中不包含key的值,那么也会丢弃删除标记。

3.1 Timing

Level-0层的合并压缩会从level-0读取4个1MB的文件,最坏的情况下会和level-1的所有文件(10MB)进行合并。也就是读14MB,写14MB。

对于非level-0的合并压缩,从level-L读取一个2MB的文件,在最坏的情况下,会和level-(L+1)的最多12个文件存在key重合,那么合并压缩会读取26MB,写26MB,在100MB/S读写速度的磁盘上,最长的合并压缩将花费0.5秒。

如果我们控制后台写的速度在一个比较小的情况下,假如100MB速度的10%,那么一次合并压缩将花费5秒。如果用户以10MB的速度在写,那么就会生成很多的level-0文件。那么就会因为在每次读数据时的合并操作,显著的增加读的开销。
解决方案:
1. 当level-0文件比较多的时候,我们可以增加生成新的log文件的阈值。副作用就是,阈值越大,生成memtable时的内存开销也越大。
2. 当level-0文件数量上涨的时候,可以人为降低写速度。
3. 减少合并的开销。大多的level-0文件是未经压缩的存放在cache里面,只需要一个O(N)复杂度的合并操作。

3.2 Number of files

取代总是生成2MB的数据文件,我们可以在比较高的层上生成大的文件,降低总的文件数量,当然这会提高合并压缩时的开销。相应的,我们可以用shard的方式把文件放在多个目录中。

An experiment on an ext3 filesystem on Feb 04, 2011 shows the following timings to do 100K file opens in directories with varying number of files:

Files in directoryMicroseconds to open a file
10009
1000010
10000016

So maybe even the sharding is not necessary on modern filesystems?

4. Recovery

  • 读取CURRENT文件找到最新的MANIFEST文件
  • 读取MANIFEST文件
  • 清除无效文件
  • 可以打开所有sstables, 但是如果晚一些会更好
  • 把log文件转成新的level-0文件
  • 把新的更新操作定向到新的log文件

5. Garbage collection of files

在数据合并压缩和恢复的最后阶段都会调用DeleteObsoleteFiles()。它会遍历数据库中的所有文件,删除所有不在current log file的所有log file,删除在所有层中都没有引用的所有表文件,以及那些失效的压缩输出文件。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值