在 Cassandra 中使用 LCS 时什么会延迟墓碑清除

What delays a tombstone purge when using LCS in Cassandra

在 C* 1.2.x 集群中,我们有 7 个键space,每个键space 包含一个使用宽行的列族。 cf使用LCS。我定期在行中删除。最初,每一行每天最多包含 1 个条目。超过 3 个月的条目将被删除,每周最多保留 1 个条目。我已经 运行 这几个月了,但磁盘 space 并没有真正回收。我需要调查原因。对我来说,墓碑似乎没有被清除。每个 keyspace 有大约 1300 个 sstable 文件(*-Data.db),每个文件的大小约为 130 Mb(sstable_size_in_mb 是 128)。每个 CF 中的 GC 宽限秒数为 864000。 tombstone_threshold 未指定,因此应默认为 0.2。我应该查看什么来找出磁盘space 未被回收的原因?

我之前在 cassandra 邮件列表上回答过类似的问题 here

为了进一步详细说明,了解 Levelled Compaction Strategy and leveldb 的一般性(给定正常写入行为)

至关重要

综上所述:

  • 数据存储组织为 "levels"。每个级别都比它下面的级别大 10 倍。级别 0 中的文件具有重叠范围。更高级别的文件在每个级别中没有重叠范围。
  • 新写入存储为进入级别 0 的新 sstables。每隔一段时间,级别 0 中的所有 sstables 都会 "compacted" 向上到 1 级 sstables,然后这些被向上压缩到 2 级 sstables 等。
  • 读取给定键将执行~N 次读取,N 是树中的级别数(它是总数据集大小的函数)。 0 级 sstables 都被扫描(因为没有限制每个具有与兄弟姐妹不重叠的范围)。然而,1 级和更高级别的 sstables 没有重叠范围,因此数据库知道 1 级中哪 1 个确切的 sstable 涵盖了您要求的密钥范围,2 级相同等...
  • cassandra 中 LCS 树的布局存储在一个 json 文件中,您可以轻松查看 - 您可以在与密钥的 sstables 相同的目录中找到它space+列族。这是我的一个节点的示例(结合 jq 工具 + awk 进行总结):

    $ cat users.json | jq ".generations[].members|length" | awk '{print "Level", NR-1, ":", [=10=], "sstables"}'
    Level 0 : 1 sstables
    Level 1 : 10 sstables
    Level 2 : 109 sstables
    Level 3 : 1065 sstables
    Level 4 : 2717 sstables
    Level 5 : 0 sstables
    Level 6 : 0 sstables
    Level 7 : 0 sstables
    

正如您所注意到的,sstables 通常大小相同,因此您可以看到每个级别的大小大约是前一个级别的 10 倍。我希望在上面的节点中满足 ~5 sstable 读取中的大多数读取操作。一旦我为 4 级添加了足够的数据以达到 10000 个 sstables 并且 5 级开始填充,我的读取延迟将略有增加,因为每次读取都会产生 1 个以上的 sstable 读取以满足。 (在切线上,cassandra 提供了分桶直方图供您检查所有这些统计数据)。

完成上述内容后,让我们来看看一些操作:


  • 我们发出一个写 ["bob"]["age"] = 30。这将进入 level0。通常在它被压缩到 level1 之后不久。慢慢地它会在每个级别花费时间但是随着更多的写入进入系统,它会向上迁移到最高级别 N
  • 我们发布 ["bob"]["age"] 的读取。然后,数据库可以从最低到最高检查每个级别 - 一旦找到数据,它就可以 return 它。如果到达最高层还没有找到,说明这个节点上不存在数据。如果在任何级别找到墓碑,它可以 return "not found" 因为数据已被删除

  • 我们发出删除 ["bob"]["age"]。这将作为具有特殊值 "column tombstone" 的正常写入进入 level0。通常在它被压缩到 level1 之后不久。慢慢地,它会在每个级别上花费时间,但随着更多的写入进入系统,它会向上迁移到最高级别 N。在每次压缩期间,如果被压缩在一起的 sstables 有一个墓碑(例如在 l1 中)和一个实际值(例如 l2 中的“30”),墓碑 "swallows up" 值并影响该级别的逻辑删除。然而,tomstone 还不能被丢弃,并且必须坚持直到它有机会压缩每个级别,直到达到最高级别 - 这是确保如果 L2 的年龄 = 30,L3 的年龄更大的唯一方法29岁,L4还有一个更大的年龄=28岁,他们都有几率被墓碑毁掉。墓碑只有达到最高等级,才能真正彻底丢弃
  • 我们发布 ["bob"]["age"] 的读取。然后,数据库可以从最低到最高检查每个级别 - 一旦找到数据,它就可以 return 它。如果到达最高层还没有找到,说明这个节点上不存在数据。如果在任何级别找到墓碑,它可以 return "not found" 因为数据已被删除

  • 我们发出删除 ["bob"]。这将作为具有特殊值 "row tombstone" 的正常写入进入 level0。它将遵循与上述列级墓碑相同的逻辑,除非它与行 "bob" 下任何列的任何现有数据冲突,它会丢弃它。
  • 我们发布 ["bob"]["age"] 的读取。然后,数据库可以从最低到最高检查每个级别 - 一旦找到数据,它就可以 return 它。如果到达最高层还没有找到,说明这个节点上不存在数据。如果在任何级别找到墓碑,它可以 return "not found" 因为数据已被删除

我希望这能回答您关于为什么在 cassandra 中删除,尤其是在 LCS 中删除实际上消耗 space 而不是释放 space 的问题(至少在最初是这样)。墓碑附加到自身的行+列有一个大小(如果你有简单的值,它实际上可能大于你试图删除的值的大小)。

这里的关键点是它们必须遍历所有级别直到最高级别 L,然后 cassandra 才会真正丢弃它们,而冒泡的主要驱动因素是总写入量。

感谢 @minaguib 对 LCS 的精彩解释。我认为 Datastax 的声明具有误导性,至少对我来说是这样

 at most 10% of space will be wasted by obsolete rows.

取决于我们如何定义“过时行”。如果“过时的行”被定义为所有应该被压缩的行,在你的例子中,这些“过时的行”将是年龄=30,年龄=29,年龄=28。我们最终会浪费(N-1 )/N space因为这些“年龄”可以有不同的等级。

我希望这里有魔法酱。

我们将在集群中以滚动方式执行 JMX 触发的 LCS -> STCS -> LCS。压缩策略的切换迫使 LCS 结构化 sstables 重组和应用墓碑(在我们的 cassandra 版本中,我们不能强制 LCS 压缩)。

有 nodetool 命令可以在表之间强制压缩,但这可能会搞砸 LCS。还有 nodetool 命令可以重新分配 sstables 的级别,但同样,如果你弄乱了它的结构,那可能会 foobar LCS。

真正应该发生的是,行墓碑应该放在一个单独的 sstable 类型中,可以根据 "data" sstables 独立处理以进行清除。 tombstone sstable <-> data sstable 处理不会移除tombstone sstable,只是从tombstone sstable中移除数据sstable之后不再需要的墓碑processed/pared/pruned。也许这些可以归类为 "PURGE" 用于大规模数据删除的墓碑,而不是与数据混合的更多临时 "DELETE" 墓碑。但谁知道什么时候会添加到 Cassandra 中。