Compaction Filter

CompactionFilter目的

  • CompactionFilter提供了一种在rocksdb进行compaction时候,根据自定义逻辑去删除/修改 key/value对的方法。这种方式可以让用户实现自定义的垃圾收集方法,比如根据业务的ttl属性删除过期keys,或删除一批范围的key,或者更新已存在key的value。

什么时候执行compactionFilter

  • 在rocksdb进行compaction的时候会调用CompactionFilter实例的方法对compaction输入的key执行filter操作,基于compaction filter的结果,compaction过程会决定它的输出keys。
    • 如果结果(filter的实例方法false)是保存这个key,则什么都不会发生
    • 如果结果是过滤掉这个key, 那么key的vlaue会被替换成删除标记(delete marker)。如果compaction的输出等级是bottom level,那么没有删除标记的key/value都会被输出。
    • 如果结果是修改value, 那么value会被替换成修改的值。
    • 如果结果(kRemoveAndSkipUntil)是删除一批范围内的keys, 那么compaction会直接删除[currnt_key, skip_until) 之间的keys直接skip到skip_until指定的key, 而不是通过对每个key的value插入删除标记来实现对key删除。这种方式删除keys的效率高,但是缺点:因为被删除的key没有被插入标记,被skip过的key的老版本key可能会出现,如果你可以确定一个key没有老版本的key,那么使用这种方式是好的。
  • 但是在flush阶段不是执行compactionFilter,虽然flush是一种特殊的compaction.

定期compaction

  • 如果compaction filter存在的话,Rocksdb可以确保固定时间后数据都会经过compaction filter,这就是通过options.periodic_compaction_seconds参数控制,设置为0,则屏蔽该特性。如果使用默认值,rocksdb会将该值设置为30天。当进行compaction时,超过30天的数据都有资格去进行compaction(有些文件可能在compaction中会一直没有被选中),而且被compaction到原来的level中。
  • 如果没有compaction filter的compaction,其只会在合并过程中删除老的key,和保证level的文件大小,但是compaction filter的实现更多时为了根据业务逻辑实现对已有数据的删除/更新等操作。

注意

  • 如果一个compaction的输入中有一个key的多个版本,则compaction filter只在最新版本上调用,如果最新版本key是有删除删除标记的话,则不会被调用。但是,如果一个删除key的删除标记在compaction的输入中不存在,那么该删除key依然会调用compaction filter。

如何实现

  • 用户程序实现CompactionFilter接口,或者CompactionFilterFactory完成自定义操作key/value的逻辑。
options.compaction_filter = new CustomCompactionFilter();
// or
options.compaction_filter_factory.reset(new CustomCompactionFilterFactory());
  • CompactionFilterFactory可以根据条件返回不同的CompactionFilter实例,而且CompactionFilterFactory可以看到Compaction的Context,比如当前是一个full compaction还是manual compaction。参数CompactionFilter::Context提供。

  • 如果实现CompactionFilter,那么需要保证其方法的线程安全,因为并行的sub-compactions会共享这一个CompactionFilter实例。但是实现CompactionFilterFactory返回的的CompactionFilter实例不需要线程安全,因为并行的sub-compactions调用CompactionFilterFactory会返回不同的实例。

  • CompactionFilter里有三个主要接口,每个接口可以让你实现不同的过滤功能:

    • 接口之一Filter, 返回true,代表该key/value会被删除,false会被留下。merge操作的后的key/value在compaction时不会经过该方法调用。
    • FilterMergeOperand在Merge操作时被调用,返回true,表明merge操作会被忽略掉和从compaction的输出中删除,而且当使用TransactionDB时,不要实现该方法。
    • FilterV2对上面两种方法的增强,如果实现这个方法,上面两种方法就不需要实现了。允许修改value,或者从当前key开始删除一批范围内的所有keys。其方法返回结果是枚举类型:Decision::kRemove, kKeep, kChangeValue,kRemoveAndSkipUntil表明了对key/vale的的filter结果。更多请看文档。
virtual bool Filter(int /*level*/, const Slice& /*key*/,
                      const Slice& /*existing_value*/,
                      std::string* /*new_value*/,
                      bool* /*value_changed*/) const {
    return false;
  }
virtual bool FilterMergeOperand(int /*level*/, const Slice& /*key*/,
                                  const Slice& /*operand*/)
 virtual Decision FilterV2(int level, const Slice& key, ValueType value_type,
                            const Slice& existing_value, std::string* new_value,
                            std::string* /*skip_until*/)

compaction分类

  • Full-compaction: 所有数据都参与这次compaction
  • Manual-compaction: 客户端主动调用执行的compaction
  • 如果不是上面两种compaction,那么就是自动发生的compaction。(比如Level-N层文件大小到达了阈值)

注意

  • 在release 6.0版本之前,Rocksdb Snapshot(比如在DB* obeject上调用GetSnapshot())之前的key/value是可以保证不变的,除非compaction filter返回IgnoreSnapshots() = true。但是在6.0之后,当compaction filter开启后,rocksdDb会在任何keys上都执行filter操作,所以snapshot将的无法保证keys视图的一致性,所以要考虑好。

SST文件的Compact

Compact的入口在db/db_impl_compaction_flush.cc的BackgroundCompaction(),我们这里依然以Leveled Compaction为例,Compaction的执行函数在CompactionJob::Run():

RocksDB会将所有的Level计算出score,经过冒泡排序,首先寻找score最高的Level,如果Level的score大于1,则选择这个Level进行Compaction
选择Level-N中尚未被Compaction的文件PickCompaction()
对于Level-0层文件,RocksDB总是选择所有的文件进行Compact执行操作,因为Level-0层的文件之间,可能会有key范围的重叠
对于Level-N层,通过GetOverlappingInputs()选取Level-N+1中与Level-N中重叠的两部分SST文件
RocksDB的CompactionIterator::SeekToFirst()将这两部分文件里所有被删除的且不存在于更高层的Level的key、重复的key、Compaction Filter中过滤的key标记为为无效
将所有有效的key写入新的SST文件
合并结束,利用VersionEdit更新VersionSet,更新统计信息

推荐阅读更多精彩内容