在这个wiki中,我们解释文件在他们不再被需要的时候是如何被删除的。

SST文件

当压缩结束,输入的SST文件会被LSM树里输出的文件替代。然而,他们可能还不能马上被删除。依赖于旧版本的LSM树的进行中的操作 使得这些文件不能马上被删除,需要等待这些操作完成。参考我们如何追踪存活SST文件了解LSM树版本是怎么工作。

可以保留旧版本的LSM树的操作包括:

  • 存活迭代器。迭代器创建的时候会固定LSM树的版本。该版本的所有的SST文件都不能被删除。这是因为一个迭代器从一个虚拟的快照读取数据,那个瞬间的所有SST文件都要为了保证这个快照正常工作而被保留起来。
  • 进行中的压缩。尽管其他的压缩没有在压缩这些SST文件,整个LSM树版本都会被固定
  • Get过程中的很短一个时间。在Get执行中的一个很短的时间内,LSM树版本是被固定,以保证他可以完整读取所有不可变得SST文件。

当没有操作固定一个旧的LSM树版本的SST文件时,这个文件才可以被删除。

这些可以删除的文件会通过两个机制被删除

引用计数

RocksDB在内存中为每个SST文件保留一份引用计数。每个LSM树的版本都会有对该版本的所有SST文件有一个引用计数。基于这个版本的操作(上面提到了)会持有LSM树的版本的引用计数,或者直接通过“super versoin”间接持有。一旦一个版本的引用计数归零,他会丢弃所有SST文件的引用计数。如果一个SST文件的引用计数归零,他就可以被删除了。通常他们会马上被删除,除了一下情况:

  • 在关闭一个迭代器的时候,发现这个文件不再需要了。如果用户设置ReadOptions.background_purge_on_iterator_cleanup=true,我们不会马上删除,而是在高优先线程池中调度一个后台任务来删除(跟删除落盘任务的池是一个池)。
  • 在Get或者其他操作中,如果他对LSM树版本的解引用导致某些SST文件需要被删除。他们不会被删除,而是会被保留。下一次落盘任务会清理他们,或者如果某些SST文件会在其他线程被删除,他们会一起被删除。这样,我们保证Get不会有文件删除的IO操作。注意,如果没有落盘发生,过期文件会保留在那里,不会删除。
  • 如果用户调用DB::DisableFileDeletions()。所有即将删除的文件都会被保留。一旦DB::EnableFileDeletions()清理了删除限制,他会删除所有挂起的SST文件。

罗列所有文件,以找出过期文件

引用计数机制在大多数场景都是有用的。然而,引用计数不能持久化,所以一重启就丢失了,所以我们需要其他的机制来回收文件。重启DB的时候,我们做一次全量垃圾回收,然后根据options.background_purge_on_iterator_cleanup周期性执行清理。后面的选项只是为了保证安全。

在这个全量垃圾回收模式中,我们罗列DB目录里的所有文件,然后检查一个个文件比对检查所有的LSM树,看看是不是已经不用了。对于不需要的文件,我们删除他们。然而,并不是在DB目录,但是没有在存活版本的文件,就是没用的。对于正在进行中的压缩或者落盘创建的文件不应该被删除。为了阻止这个发生,我们使用一个好功能,他要求文件名单调递增。在落盘或者压缩开始前,我们记住当时最新的SST文件编号。如果一次全量垃圾回收在这个工作结束前开始了,所有编号大于该编号的SST文件会被保留。这个条件会在相关任务结束之后被释放。由于多个压缩和落盘工作可以并行运行,所有编号大于最早固定的SST文件编号的文件,都会被保留。可能我们会有一些假阳性,但是他们最终会被清理。

日志文件

一个日志文件在所有数据都落盘到SST文件之后就可以被删除了。对于一个单列族的DB,决定一个日志文件是否可以删除是非常简单的,对于多列族的DB,则稍微复杂点,更复杂的情况是,如果你开启了两阶段提交(2PC)的时候

单列族DB

一个日志文件与一个memtable有1:1对应关系。一旦一个memtable落盘了,对应的日志文件就会被删除。会在落盘工作的最后进行。

多列族DB

当有多个列族,一个新的日志文件会在任何一个列族落盘的时候创建。一个日志文件只可以在所有列族的数据都落盘的时候删除。RocksDB的实现方式是每个列族追踪 本列族 还有 未落盘数据的 最早的日志文件。一个日志文件只能在他比 所有列族各自的最早的日志 的最早那个还要早的时候,才能被删除。

两阶段提交

在两阶段提交中,有一次写入,有两个日志需要写:一个准备和一个提交。只有当提交了的日志落盘之后,我们才能释放包含准备日志和提交日志的文件。不再有一个简单的从memtable到日志文件的映射了。例如,考虑下面的一个DB列族的序列:

  1. --------------
  2. 001.log
  3. Prepare Tx1 Write (K1, V1)
  4. Prepare Tx2 Write (K2, V2)
  5. Commit Tx1
  6. Prepare Tx3 Write (K3, V3)
  7. -------------- <= Memtable Flush <<<< Point A
  8. 002.log
  9. Commit Tx2
  10. Prepare Tx4 Write (K4, V4)
  11. -------------- <= Memtable Flush <<<< Point B
  12. 003.log
  13. Commit Tx3
  14. Prepare Tx5 Write (K5, V5)
  15. -------------- <= Memtable Flush <<<< Point C

在Ponit A,尽管memtable落盘了,001.log还是不能删除,因为Tx2和Tx3还没有提交。类似的,在PointB,001.log还是不能被删除,因为Tx3没有提交。只有到了PointC,001.log才可以删除。但是002.log还是不能删除,因为Tx4还没提交。

RocksDB会使用一个错综复杂的低锁数据结构来决定一个日志文件是不是可以被删除。