RocksDB原理学习笔记

优点

  1. 增加了column family,这样有利于多个不相关的数据集存储在同一个db中,因为不同column family的数据是存储在不同的sst和memtable中,所以一定程度上起到了隔离的作用。
  2. 采用了多线程同时进行compaction的方法,优化了compact的速度。
  3. 增加了merge operator,优化了modify的效率
  4. 将flush和compaction分开不同的线程池,能有效的加快flush,防止stall。
  5. 增加了对write ahead log(WAL)的特殊管理机制,这样就能方便管理WAL文件,因为WAL是binlog文件。
  6. RocksDB典型的做法是Level 0-2不压缩,最后一层使用zlib(慢,压缩比很高),而其它各层采用snappy

rocksdb的文件类型

主要有以下几种类型sst文件,CURRENT文件,manifest文件,log文件,LOG文件和LOCK文件

  • sst文件存储的是落地的数据;
  • CURRENT文件存储的是当前最新的是哪个manifest文件;
  • manifest文件存储的是Version的变化;
  • log文件是rocksdb的write ahead log,就是在写db之前写的数据日志文件;
  • LOG文件是一些日志信息,是供调试用的;
  • LOCK是打开db锁,只允许同时有一个进程打开db。

配置信息(TODO)

ColumnFamilyOptions

这些option都是column family相关的,可以对不同的column family赋不同的值。

  • inplace_update_support: 字面含义是是否支持在原位置更新,如果支持的话,那么原来的数据就被擦除了,所以snapshot和iterator保留当时的数据的逻辑就没法实现了
  • num_levels: 记录的是version的level的数目,默认是7,即0~6
  • target_file_size_base: level1的sst文件的大小,默认为2MB
  • target_file_size_multiplier: level1以上的sst文件大小,乘数因子默认是1,即所有level的文件大小都是2MB
    • level0的文件大小是由write_buffer_size决定的,level1的文件大小是由target_file_size_base决定的,level2及以上,size = target_file_size_base * (target_file_size_multiplier ^ (L - 1))
  • max_bytes_for_level_base: level1的sst总的文件总和大小,默认是10MB
  • max_bytes_for_level_multiplier: level2及以上的level的sst文件总和大小的乘数因子,默认是10,
    • level0的sst文件总和大小是level0_stop_writes_trigger * write_buffer_size,因为level0的文件数目达到level0_stop_writes_trigger时候就会停止write。
    • level1及以上的文件总和大小是max_bytes_for_level_base * (max_bytes_for_level_multiplier ^ (L - 1)),默认的level0是4MB * 24 = 96MB,level1是10MB,level2是100MB,level3是1G,level4是10G。。

RocksDB Flush

Flush是指将memtable的数据导入到sst中,变成持久化存储,就不怕数据丢失了。

触发Flush的代码入口:

Status DBImpl::ScheduleFlushes(WriteContext* context) {
  autovector<ColumnFamilyData*> cfds;
  if (immutable_db_options_.atomic_flush) {
    SelectColumnFamiliesForAtomicFlush(&cfds);
    for (auto cfd : cfds) {
      cfd->Ref();
    }
    flush_scheduler_.Clear();
  } else {
    ColumnFamilyData* tmp_cfd;
    while ((tmp_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
      cfds.push_back(tmp_cfd);
    }
    MaybeFlushStatsCF(&cfds);
  }
  Status status;
  for (auto& cfd : cfds) {
    if (!cfd->mem()->IsEmpty()) {
      status = SwitchMemtable(cfd, context);
    }
    if (cfd->Unref()) {
      delete cfd;
      cfd = nullptr;
    }
    if (!status.ok()) {
      break;
    }
  }
  if (status.ok()) {
    if (immutable_db_options_.atomic_flush) {
      AssignAtomicFlushSeq(cfds);
    }
    FlushRequest flush_req;
    GenerateFlushRequest(cfds, &flush_req);
    SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
    MaybeScheduleFlushOrCompaction();
  }
  return status;
  1. 首先在memtable的add的时候,会检测是否memtable的大小达到了max write buffer,如果是就将should_flush_置为true(CheckMemtableFull还有其他情况触发),并会在WriteBatch的Handler里面调用CheckMemtableFull,将当前column family加入flush_scheduler;
    • CheckMemtableFull调用的FlushScheduler::ScheduleWork方法只是将cfd添加到checking_set_队列中,并未真正地执行Flush调度;
  2. 在Write的时候,调用ScheduleFlushes,将需要flush的column family的memtable切换一个新的,同时将原来的memtable加入cfd的imm中;
    • 由于真正的Flush过程是在另一个线程完成的,所以这个地方并不会block写过程;
    • Write中调用PreprocessWrite做些预先处理的工作;
    • 如果发现checking_set_不为空,会调用DBImpl::ScheduleFlushes方法,然后调用SwitchMemtable切换新的memtable;DBImpl::SwitchMemtable执行流程:
      • 如果开启two_write_queues_: 等待没有并发的wal写入线程;
      • WriteRecoverableState在memtable中写入recoverable_state状态;
      • 如果开启enable_pipelined_write: 等待所有的memtable写入线程完毕;
      • 如果需要创建新的wal,则调用CreateWAL创建wal writer;
      • 调用cfd->ConstructNewMemtable,创建新的memtable;
      • cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_),将原来的memtable加入到imm中;
  3. 当mem切换imm切换成功,会触发MaybeScheduleFlushOrCompaction,尝试flush或者compaction;
    • 当然也有其他case触发flush/compaction: 如果这个column family data的imm数量大于min_write_buffer_number_to_merge,并启动一个新的线程调用BGWorkFlush;
    • BGWorkFlush->BackgroundCallFlush->BackgroundFlush->FlushJob
    • FlushJob::PickMemTable选择需要Flush的imm
      • 由于cfd中可能包含多个imm,从cfd获取一个可以进行flush的memtable的list:待合并、flush的imm结合;
      • 从memtable列表中获取第一个memtable,使用其edit结构来保存本次flush的元信息: 该次flush的版本信息通过第一个imm设定;
      • 调用version_set的NewFileNumber接口为新的文件生成一个filenumber(同时可以指定对应level的路径, level=0)
    • FlushJob::Run, 执行flush逻辑
      • WriteLevel0Table: 将imm写入level=0的sst文件中
        • 遍历待合并的Imm集合:
          • 待flush的数据:构造InternalIterator迭代器数组;
          • 待删除的数据:构造FragmentedRangeTombstoneIterator迭代器数组;
        • 基于InternalIterator构造NewMergingIterator归并迭代器,基于最小堆实现多路归并算法;
        • BuildTable:将数据写入sst中:
          • TableFileName: 构造flush的文件名;
          • NewWritableFile: 创建新的文件;
          • WritableFileWriter: 构造writer;
          • NewTableBuilder: 构建table builder;
          • CompactionIterator: 构建合并迭代器;
          • 遍历迭代器,调用BlockBasedTableBuilder.Add方法逐一添加k/v数据,中间可能触发flush;
        • 处理完成 之后如果output_file_directory不为空则同步该目录(output_file_directory_->Fsync())
        • 调用edit_->AddFile,将生成的文件添加到L0
        • 记录本次Flush的状态

RocksDB Compaction

  • 通过minor compaction,内存中的数据不断地写入的磁盘,保证有足够的内存来应对新的写入;
  • 而通过major compaction,多层之间的SST文件的重复数据和无用的数据可以迅速减少,进而减少sst文件占用的磁盘空间。

Compaction的触发条件是两类:文件个数和文件大小。

  • 对于level0,触发条件是:
    • sst文件个数,通过参数level0_file_num_compaction_trigger控制;
    • score通过sst文件数目与level0_file_num_compaction_trigger的比值得到。
  • level1-levelN触发条件是:
    • sst文件的大小,通过参数max_bytes_for_level_base和max_bytes_for_level_multiplier来控制每一层最大的容量;
    • score是本层当前的总容量与能存放的最大容量的比值

Compaction的主要流程如下:

  1. 首先找score最高的level,如果level的score>1,则选择从这个level进行compaction
  2. 根据一定的策略,从level中选择一个sst文件进行compact,对于level0,由于sst文件之间(minkey,maxkey)有重叠,所以可能有多个。
  3. 从level中选出的文件,我们能计算出(minkey,maxkey)
  4. 从level+1中选出与(minkey,maxkey)有重叠的sst文件
  5. 多个sst文件进行归并排序,合并写出到sst文件
  6. 根据压缩策略,对写出的sst文件进行压缩
  7. 合并结束后,利用VersionEdit更新VersionSet,更新统计信息

触发Compaction的方式:

  • DBImpl::RunManualCompaction: 手动触发Compaction
    • 判断触发MannulCompaction条件、变量;
      • 确保没有非mannul compaction执行,这样的话mannual compaction可以执行任意range的compaction;
    • 调用BGWorkCompaction线程开启调度;
  • 自动Compaction:
    • DBImpl::MaybeScheduleFlushOrCompaction: 在每次触发mem的flush的时,会判定是否进行flush/compaction
    • DBImpl::BackgroundCallFlush: 包含了mem的flush、compaction的判定执行逻辑;
    • DBImpl::MaybeScheduleFlushOrCompaction
      • 调用BGWorkCompaction线程开启调度
  • BGWorkCompaction的执行逻辑:可以发现不论是手动、自动触发的模式,的最终都会调用Compaction线程进行处理:
    • DBImpl::BackgroundCallCompaction
    • DBImpl::BackgroundCompaction(真正的执行逻辑,这个函数巨长)
      • 如果是mannul compaction:
        • 调用EnoughRoomForCompaction判定是否有足够的Compaction空间,没有空间的话直接返回CompactionTooLarge异常;
      • 如果是auto compaction:
        • 调用PickCompactionFromQueue,从queue选择需要执行的cfd,如果为空,直接返回;
        • 调用EnoughRoomForCompaction判定是否有足够的Compaction空间,没有空间的话,更新统计信息不返回异常;
      • 在进行完准备工作之后,判定需要合并的compcation(c)不为空的话,
        • 如果c为deletion_compaction:
          • 删除c执行的fd和edit信息;
          • 调用VersionSet::LogAndApply进行更新manifest操作;
          • 调用DBImpl::InstallSuperVersionAndScheduleWork更新SuperVersion;
        • 如果c为IsTrivialMove:
          • 类似于上述操作,先进性fileMeta变更;
          • 然后调用VersionSet::LogAndApply进行更新manifest操作;
          • 再调用DBImpl::InstallSuperVersionAndScheduleWork更新SuperVersion;
        • 如果c是BottomCompaction(最开始引入是为了universal-compaction,后来也对level-compaction进行适配,主要用于长时间(long running)合并,以避免同short-live上层合并逻辑的冲突):
          • 调用DBImpl::BGWorkBottomCompaction执行;
        • 否则,执行通用Compaction逻辑:
          • 构造并提交CompactionJob
            • Prepare:
              • 构造边界值和统计信息;
            • Run:
              • 构造合并迭代器;
            • Install:
              • 调用VersionSet::LogAndApply变更edit/fileMeta信息;

ColumnFamilyData构造信息中会根据配置信息初始化,如下变量用于compaction的统计信息更新、并确定下一次compaction的判断:

std::unique_ptr<CompactionPicker> compaction_picker_;

CompactionPicker提供的主要接口有:

  • NeedsCompaction: 是否进行合并;
  • MaxOutputLevel: 最大output level;
  • PickCompaction: 根据level和inputs文件产生新的compaction;
  • CompactRange: 根据在指定level的[begin,end]信息构造compaction信息;

在RocksDB中,compaction的CompactionPicker实现有如下几种:

enum CompactionStyle : char {
  // level based compaction style
  kCompactionStyleLevel = 0x0,
  // Universal compaction style
  // Not supported in ROCKSDB_LITE.
  kCompactionStyleUniversal = 0x1,
  // FIFO compaction style
  // Not supported in ROCKSDB_LITE
  kCompactionStyleFIFO = 0x2,
  // Disable background compaction. Compaction jobs are submitted
  // via CompactFiles().
  // Not supported in ROCKSDB_LITE
  kCompactionStyleNone = 0x3,
};

Level Compaction

某个level的sst文件与level+1中存在重叠的sst文件进行合并,然后将合并后的文件写入到level+1层的过程。

  • 通过判断每个level的score是否大于1,确定level是否需要compact
    • 默认是选择文件size较大,包含delete记录较多的sst文件,这种文件尽快合并有利于缩小空间。
    • 每次会从level中选取一个sst文件与下层compact,但由于level0中可能会有多个sst文件存在重叠的范围,因此一次compaction可能有多个level0的sst文件参与。

在Level-Based的Compaction中,决定从一个level到下一个level进行合并的方法有(参考VersionStorageInfo::UpdateFilesByCompactionPri方法):

  • kByCompensatedSize: 根据sst文件的compensated_file_size补偿文件大小排序选择;
    • compensated_file_size大致可以理解为:file_meta->fd.GetFileSize() + (file_meta->num_deletions * 2 - file_meta->num_entries) * average_value_size * kDeletionWeightOnCompaction,同文件大小与删除文件数量有关系(参考VersionStorageInfo::ComputeCompensatedSizes);
  • kOldestLargestSeqFirst: 根据sst文件的largest_seqno序列号排序选择(大者优先);
  • kOldestSmallestSeqFirst: 根据sst文件的smallest_seqno序列号排序选择(小者优先);
  • kMinOverlappingRatio: 根据sst文件的overlapping大小/file_size排序;

Universal Compaction

相对于level compaction,Univeral compaction由于每一次合并的文件较多,相对于level compaction的多层合并,写放大较小,付出的代价是空间放大较大。

  • Univeral模式中,所有的sst文件都可能存在重叠的key范围。对于R1,R2,R3,...,Rn,每个R是一个sst文件,R1中包含了最新的数据,而Rn包含了最老的数据;
  • 合并的前提条件是sst文件数目大于level0_file_num_compaction_trigger,如果没有达到这个阀值,则不会触发合并。在满足前置条件的情况下,按优先级顺序触发以下合并。
  1. 如果空间放大超过一定的比例,则所有sst进行一次compaction,所谓的full compaction,通过参数max_size_amplification_percent控制。
  2. 如果前size(R1)小于size(R2)在一定比例,默认1%,则与R1与R2一起进行compaction,如果(R1+R2)*(100+ratio)%100<R3,则将R3也加入到compaction任务中,依次顺序加入sst文件
  3. 如果第1和第2种情况都没有compaction,则强制选择前N个文件进行合并。

FIFO Compaction

FIFO顾名思义就是先进先出,这种模式周期性地删除旧数据。在FIFO模式下,所有文件都在level0,当sst文件总大小超过阀值max_table_files_size,则删除最老的sst文件。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • 最近项目中用到这个nb的玩意,所以就花时间研究了下,同时整理下助自己记忆。这个猛虎上山的logo就是rocksdb...
    小东_16d3阅读 9,074评论 3 10
  • 在先前我们讨论了 RocksDB 的 statistics 和 write stall,但这些只能让我们发现问题,...
    siddontang阅读 8,081评论 2 16
  • 前言 这篇从半个月前就开始写,断断续续写到现在,终于能发了(被简书吞了好几次),不容易。 最近笔者正在补习与Roc...
    LittleMagic阅读 13,550评论 13 29
  • /旗袍,有一种不言的高贵/ 旧日年画里,纤弱的女子, 盘云的发髻,鬓旁如镂银花, 更有那织金点翠的旗袍, 白皙的手...
    stacey思思阅读 299评论 0 0
  • 在那之前 狗一直都对我很好 它是一条好狗 我也不后悔养过它 养了四年的一条狗 因为有一段时间我沉迷于游戏 而疏忽了...
    111_十二阅读 277评论 0 0