如何跟踪实时的SST文件

在RocksDB中,除了WAL日志以外,LSM树还包含文件系统中的SST文件列表。每次压缩之后,压缩输出文件被添加到列表中, 而输入文件则从列表中删除。然而,输入文件不一定有资格立即删除,因为一些get或优秀的迭代器可能需要保留这些文件,直到它们的操作完成或迭代器释放。 在页面的其余部分,我们将介绍如何保存这些信息。

LSM树中的文件列表保存在称为version的数据结构中。在压缩或mem表刷新结束时,将为更新的LSM树创建一个新版本。 在某个时候,只有一个”当前”版本表示最新LSM树中的文件。新的get请求或新的迭代器将在迭代器的整个读取过程或生命周期中使用当前版本。 get或迭代器使用的所有版本都需要保留。需要删除任何get或迭代器都不使用过期的版本。所有其他版本不使用的文件都需要删除。例如,

如果我们从一个包含三个文件的版本开始:

  1. v1={f1, f2, f3} (current)
  2. files on disk: f1, f2, f3

现在用它创建了一个迭代器:

  1. v1={f1, f2, f3} (current, used by iterator1)
  2. files on disk: f1, f2, f3

现在刷新发生添加 f4,一个新的版本被创建:

  1. v2={f1, f2, f3, f4} (current)
  2. v1={f1, f2, f3} (used by iterator1)
  3. files on disk: f1, f2, f3, f4

现在压缩发生了紧凑 f2, f3和f4 到一个新的文件 f5 与一个新的版本v3创建:

  1. v3={f1, f5} (current)
  2. v2={f1, f2, f3, f4}
  3. v1={f1, f2, f3} (used by iterator1)
  4. files on disk: f1, f2, f3, f4, f5

现在v2既不是最新的,也不是被任何人使用的,所以它有资格与f4一起被删除。而v1仍然不能被删除,因为迭代器1仍然需要它:

  1. v3={f1, f5} (current)
  2. v1={f1, f2, f3} (used by iterator1)
  3. files on disk: f1, f2, f3, f5

假设iterator1被销毁:

  1. v3={f1, f5} (current)
  2. v1={f1, f2, f3}
  3. files on disk: f1, f2, f3, f5

现在v1没有使用,也不是最新的,所以它可以删除,文件f2, f3:

  1. v3= f1, f5(current)
  2. files on disk: f1, f5

这个逻辑是使用引用计数实现的。一个SST文件和一个版本都有一个引用计数。 在创建版本时,我们增加了所有文件的引用计数。如果不需要某个版本,则该版本的所有文件的引用计数都会递减。 如果文件的引用计数下降到0,则可以删除该文件。

以类似的方式,每个版本都有一个引用计数。当一个版本被创建时,它是一个最新的版本,所以它有引用计数1。 如果版本不再是最新的,它的引用计数将减少。任何需要处理该版本的人,其引用计数都将增加1,并在使用完该版本后减少1。当版本的引用计数为0时,应该删除它。 一个版本是最新的,或者有人正在使用它,它的引用计数不是0,所以它将被保存。

有时,reader直接持有版本的引用,比如用于压缩的源版本。更常见的情况是,reader通过一个名为super version的数据结构间接地持有它,该数据结构包含mem表列表的引用计数和一个版本(DB的整个视图)。 读取器只需要增加或减少一个引用计数,而保存版本的引用计数的是超级版本。它还支持进一步优化,以避免在大多数情况下锁定引用计数。参见博客文章( http://rocksdb.org/blog/677/avoid-expensive-locks-in-get/

RocksDB在名为VersionSet的数据结构中维护所有版本,该数据结构还记得谁是”当前”版本。 由于每个列族都是一个单独的LSM,所以它也有自己的版本列表,其中一个版本是”current”。但是每个DB只有一个版本,它为所有列族维护版本。