压实类型(Types of compaction)
压实的概念用于Cassandra中的不同类型的操作,这些操作的常见之处在于它需要合并一个或多个sstables并输出新的sstables。压实的类型有:
- 轻度压实(Minor compaction)
- 在Cassandra中自动触发。
- 高度压实(Major compaction)
- 用户对节点上的所有sstables执行压实。
- 用户自定义压实(User defined compaction)
- 用户在给定的一组sstables上触发压实。
- 擦洗(Scrub)
- 尝试修复任何破碎的sstables。如果发生数据被损坏,删除了有效数据的情况,您将需要在节点上运行完全修复(full repair)。
- 升级sstables(Upgradesstables)
- 将sstables升级到最新版本。升级到新的主要版本后运行此操作。
- 清理(Cleanup)
- 删除该节点不再拥有的任何范围,通常在节点被引导之后在相邻节点上被触发,因为该节点将从那些节点获得一些范围的所有权。
- 二级索引重建(Secondary index rebuild)
- 重建节点上的二级索引。
- Anticompaction
- 修复后,实际修复的范围是从修复开始时存在的sstables中分离出来的。
- 子范围压实(Sub range compaction)
- 可以只压实给定的子范围 - 如果您知道一个行为不当的令牌亦或是采集了很多更新或者删除,这可能是有用的。(nodetool compact -st x -et y)将选择包含x和y之间范围的所有sstables,并为这些sstables进行压实。对于STCS,这很可能包括所有sstables,但是使用LCS它可以对sstables的子集进行压实。使用LCS,结果sstable将最终在L0。
什么时候触发轻度压实?
#当sstable通过刷新/流等添加到节点时。#禁用自动压实后(nodetool enableautocompaction)启用自动压实
#压实时添加新的sstables。
#每5分钟检查一次新的轻度压实。
合并sstables
压实其实就是合并sstables,因为sstables中的分区是基于分区键的散列排序,可以有效地合并单独的sstables。 另外每个分区的内容也都被排序,所以每个分区也都可以被有效地合并。
Tombstones和GC的魅力
为什么使用Tombstones
当Cassandra收到删除请求时,它并不会从底层存储中删除数据。相反,它会写一个称为Tombstones的特殊数据。Tombstones表示删除并导致在Tombstones之前发生的所有值不显示在数据库的查询中。使用此方法而不是删除值是因为Cassandra的分布式特性。
不使用tombstones删除数据
设想一个三节点集群,其值[A]复制到每个节点:
[A], [A], [A]
如果其中一个节点失败,并且我们的删除操作只删除现有值,我们最终可能会看到一个集群:
[], [], [A]
然后修复操作将[A]的值替换回缺失值的两个节点:
[A], [A], [A]
这将导致我们的数据复活,即使它已被删除。
使用tombstones删除数据
再次从具有复制到每个节点的值[A]的三节点集群开始:
[A], [A], [A]
如果不是删除数据,而是添加Tombstone记录,我们的单节点故障情况将如下所示:
[A, Tombstone[A]], [A, Tombstone[A]], [A]
现在当我们发出修复Tombstone将被复制到副本,而不是删除的数据被复活:
[A, Tombstone[A]], [A, Tombstone[A]], [A, Tombstone[A]]
我们的修复操作将正确地将系统的状态设置为我们期望的,所有节点上的记录[A]都被标记为已删除。但是这也意味着我们将最终把Tombstones永久的累积在磁盘空间。为了避免永远保持Tombstone,我们有一个称为gc_grace_seconds的参数,用于Cassandra中的每个表。
gc_grace_seconds参数和删除Tombstone
表级别gc_grace_seconds参数控制Cassandra在最终删除Tombstone之前通过compactionevent保留Tombstone的时间。此持续时间直接反映用户在恢复失败的节点之前期望允许的时间量。在gc_grace_seconds过期之后,Tombstone可能就被删除了(意味着将不再有任何记录表明某个数据被删除),但是由于Tombstone可以存在于一个sstable中并且覆盖在另一个数据中,因此为了去除Tombstone压实还必须包括以上两者。更确切地说,为了能够丢弃实际的Tombstone,以下需要是必须的;
-
Tombstone必须创建在gc_grace_seconds到期之前
- 如果分区X包含Tombstone,则包含该分区的sstable加上包含数据的所有sstables(包含X的Tombstone)必须包含在同一压实中。如果我们可以保证sstable中的所有数据都比Tombstone更新那么我们不需要关心分区是否稳定。如果Tombstone比数据更旧,则Tombstone不能影响该数据。
- 如果启用了only_purge_repaired_tombstones选项,则只有在数据也已修复后,Tombstone才会被删除。
如果节点保持关闭或断开时间长于gc_grace_seconds,则删除的数据将被修复回其他节点并重新显示在集群中。这基本上与“删除没有Tombstones”部分相同。注意,即使gc_grace_seconds已过,Tombstone在压实事件之前也不会被删除。
gc_grace_seconds的默认值为864000,相当于10天。当使用WITH gc_grace_seconds创建或更改表时,可以设置此值。
生存时间(TTL)
Cassandra中的数据可以有一个额外的属性称为生存时间 - 这用于自动删除已到期的数据。一旦TTL过期,数据将转换为至少保留gc_grace_seconds的逻辑删除。注意,如果你把数据跟TTL混合并且数据没有TTL(或只是不同的长度的TTL),那Cassandra将很难删除创建的Tombstone,因为分区可能会跨越许多sstables,而不是所有都立即压实。
完全过期的sstables
如果sstable只包含Tombstone并且保证sstable在任何其他sstable的压实中都不是阴影数据,则可以删除该sstable。如果你看到sstables只有Tombstone(注意:数据一旦生存时间已过期就被认为Tombstone)但是没有被压实,则很可能是其他sstables包含更旧的数据。有一个名为sstableexpiredblockers的工具,它将列出哪些sstables是可删除的,哪些是阻止被删除的。这对于使用TimeWindowCompactionStrategy(和已弃用的DateTieredCompactionStrategy)的时间序列压实特别有用。
已修复/未修复的数据
使用增量修复Cassandra必须跟踪哪些数据被修复,哪些数据未修复。修复和未修复的数据与反补救修复的数据被分裂为修复和未修复的sstables。为了避免混淆数据,压实策略实例单独地运行在两组数据上,每个实例只知道修复的或未修复的sstables。这意味着如果您只运行增量修复一次,然后从不再运行,您可能在修复的sstables中有非常旧的数据阻止在未修复(可能是较新的)sstables中压实删除Tombstone。
数据目录
由于Tombstone和数据可以生存在不同的sstable上,意识到sstable的丢失是非常重要的,sstable的丢失可能导致数据的复活,由于sstables丢失导致的最常见的问题就是硬盘驱动器崩溃。为了避免数据永久存在Tombstone和实际数据总是存储在同一个目录。通过这种方式,如果磁盘丢失,则分区的所有版本都将丢失,并且没有数据可以被取消删除。为了实现这一点,除了包含修复/未修复数据的压实策略实例之外,还要在每个数据目录上运行压实策略实例,这意味着如果您有4个数据目录,则会有8个压实策略实例运行。这样处理相对于只是避免数据取消删除有几个好处:
- 可以并行地运行更多的压实, 水平压实将有几个完全独立的平层,每个压实可以独立于其他压实。
- 用户可以备份和恢复单个数据目录。
- 注意,虽然当前所有的数据目录都被认为是相等的,所以如果你有一个微小的磁盘和一个大的磁盘支持两个数据目录,大的将受到小的限制。 围绕这个问题需要做的一个工作就是创建更多的由大磁盘支持的数据目录。
单个sstable上的tombstone压实
当一个sstable被写入一个带有Tombstone到期时间的柱状图中并且这个柱状图用于尝试找到具有大量Tombstone的sstables(在该sstable上运行单个sstable以希望能够删除该sstable中的Tombstone)时,在开始之前还要检查任何Tombstone实际上将可能删除多少这个sstable和其它sstable重叠的数据。为了避免大多数这些检查,可以启用压实选项unchecked_tombstone_compaction。
常用选项
所有压实策略都有一些常见的选项;
-
enabled
(默认值: true) - 是否应运行轻度压实。您可以将“enabled”:true作为压实选项,然后执行“nodetool enableautocompaction”以开始运行压实。
-
tombstone_threshold
(默认值: 0.2) - 多少sstable应该是Tombstone,便于考虑压实一个单独的sstable。
-
tombstone_compaction_interval
(默认值: 86400s (1天)) - 由于在执行单个sstable压实时可能无法删除任何Tombstone,因此我们需要确保一个sstable不会不断被重新压实 - 此选项指出我们应该多久尝试一次给定的sstable。
-
log_all
(默认值: false) - 新的详细压实日志记录,见下文。
-
unchecked_tombstone_compaction
(默认值: false) - 单个sstable的压实是否应该启动有相当严格的检查,这个选项禁用那些检查并且存在一些其它的用例这个选项可能也是需要的。请注意,这不会更改实际压实的任何内容,只有在安全的情况下才会删除Tombstone,它可能只是重写sstable而不能删除任何Tombstone。
-
only_purge_repaired_tombstone
(默认值: false) - 启用额外安全性的选项,以确保只有在数据已修复后才删除。
-
min_threshold
(默认值: 4) - 触发压实之前的sstables数量的下限。 不用于LeveledCompactionStrategy。
-
max_threshold
(默认值: 32) - 触发压实之前的sstables数量的上限。 不用于LeveledCompactionStrategy。
nodetool压实命令
-
nodetool实用程序提供了许多与压实相关的命令:
-
enableautocompaction
-
启用压实。
-
disableautocompaction
- 禁用压实
-
setcompactionthroughput
- 压实的最高速度 - 默认为16MB / s,但可能无法达到此吞吐量。
-
compactionstats
- 有关当前和未决定的压实的统计信息。
-
compactionhistory
- 列出有关上次压实的详细信息。
-
setcompactionthreshold
- 设置触发压实的最小/最大sstable计数,默认为4/32。
使用JMX切换压实策略和选项
可以在使用JMX的单个节点上切换压实策略及其选项,这是一种很好的方法来测试设置,而不影响整个集群。mbean是:
org.apache.cassandra.db:type=ColumnFamilies,keyspace=<keyspace_name>,columnfamily=<table_name>
如果使用jconsole或jmc,要更改的属性是CompactionParameters或CompactionParametersJson。json版本的语法与在ALTER TABLE语句中使用的语法相同,例如:
{ 'class': 'LeveledCompactionStrategy', 'sstable_size_in_mb': 123 }
该设置保持到某人执行压实策略设置重新启动节点。
更详细的压实日志记录
启用压实选项log_all将在日志目录中生成更详细的压实日志文件。
大小分层压实策略(Size Tiered Compaction Strategy)
SizeTieredCompactionStrategy(STCS)的基本思想是合并大小相同的sstables。所有sstables根据其大小放置在不同的桶中。如果sstable的大小处于当前桶中的sstables的平均大小的bucket_low和bucket_high之间,则将sstable添加到桶。这将创建几个桶并将桶进行压实。最有趣的是决定找出哪个桶的SSTables花费最多的的读取次数(原文:This will create several buckets and the most interesting of those buckets will be compacted. The most interesting one is decided by figuring out which bucket’s sstables takes the most reads.)。
高度压实
当使用STCS运行高度压实时,每个数据目录最多有两个sstables(一个用于修复数据,一个用于未修复数据)。高度压实通过一个选项(-s),将输出分割成几个sstables。sstables的大小约为总大小的50%,25%,12.5%...。
STCS 选项
-
min_sstable_size
(默认: 50MB) - 小于此值的Sstables将放在同一个桶中。
-
bucket_low
(默认: 0.5) - 在不被包括在桶中之前,sstable应当小于桶的平均大小。也就是说,如果bucket_low * avg_bucket_size <sstable_size(并且bucket_high条件成立,见下文),则sstable被添加到bucket。
-
bucket_high
(默认: 1.5) - 在不被包括在桶中之前,sstable应该比桶的平均大小大多少。也就是说,如果sstable_size <bucket_high * avg_bucket_size(并且bucket_low条件成立,见上文),则sstable被添加到bucket。
碎片整理
在读取期间创建许多sstables时进行碎片整理。 读取的结果被放入memtable,以便下一次读取不必创建尽可能多的sstables。这可能导致只读集群上的写入。
水平压实策略(Leveled Compaction Strategy)
LeveledCompactionStrategy(LCS)的想法是所有的sstables被放置在不同的级别,我们保证没有重叠的sstables在同一级别。通过重叠,单个sstable的第一个/最后一个令牌从不与其他sstables重叠。这意味着对于SELECT,我们将只需要在每个级别的单个sstable中查找分区键。每个级别是上一个级别的10倍大小,每个sstable默认为160MB。L0是sstables被流式传输/刷新的地方 - 这里做重叠保证。
当选择压实候选项时,我们必须确保压实不会在目标级别创建重叠。这是通过在下一级别中包括所有重叠的sstables来完成的。例如,如果我们在L3中选择一个sstable,我们需要保证我们选择L4中的所有重叠的sstables,并确保没有当前正在进行的压实会在我们开始压实时产生重叠。如果我们保证我们不会创建重叠,我们可以在一个级别启动许多并行压实。对于L0 - > L1压实,我们几乎总是需要包括所有L1 sstables,因为大多数L0 sstables覆盖了全范围。我们不能在一个单一的压实中压实所有L0 sstables和L1 sstables,因为那样需要使用太多的内存。
当决定压实LCS的级别时,首先检查较高级别(使用LCS,“较高”级别是具有较高数字的级别,L0是最低级别),如果级别低于该级别,则将在该级别中开始压实。
高度压实
可以使用LCS进行高度压实 - 它将从填充L1开始,然后一旦L1满了它继续L2,一次类推。在高级别层次中修改创建所有sstables是较差的选择。在引导期间,新节点还在写入数据时从远程节点传输流入的数据像其他所有写入一样刷新到L0,并且避免那些sstables阻塞远程sstables进入正确的级别,我们只对L0进行操作直到引导完成。
引导
在引导期间,sstables从其他节点传输流入。保持远程sstable的级别,以避免在完成引导之后进行压实。
L0级别上的STCS
如果LCS获得非常多的L0 sstables读数将击中所有(或大多数)L0 sstables,因为它们可能会重叠。如果有超过32个sstables,则需要更快地补救这个LCS在L0中所做的STCS压实。与LCS执行其L0 - > L1压实相比,这需要更高读取性能。 如果你在L0中获得太多的sstables,很可能LCS不是最适合你的工作负载,STCS是更好的工作方式。
Starved sstables
如果一个节点结束了一个级别的压实工作,但是其中有一些非常高级别的sstables没有被压实,可能导致使低级别的压实放弃Tombstone。例如,如果L6中有sstables,但是节点实际上只有获得足够的L4数据,则L6中的左侧sstables将变得饥饿,而不是压实。如果用户将sstable_size_in_mb从5MB更改为160MB,则可能会发生这种情况。为了避免这种LCS试图包括那些饥饿的高水平sstables在其他压实中,当当前*别的数据没有被压实时,将进行25次压实(原文:To avoid this LCS tries to includethose starved high level sstables in other compactions if there has been 25 compaction rounds where the highest levelhas not been involved)。
LCS 选项
-
sstable_size_in_mb
(默认: 160MB) - sstable压实后(如果使用压实)的目标大小 - 如果节点上有非常大的分区,sstables可能会变大。
LCS还支持cassandra.disable_stcs_in_l0启动选项(-Dcassandra.disable_stcs_in_l0 = true)来避免在L0中执行STCS。
时间窗口压实策略(Time Window CompactionStrategy)
TimeWindowCompactionStrategy(TWCS)专门针对工作负载而设计,其中通过数据的时间戳将磁盘上的数据分组是有利的,当工作负载本质上是时间序列或者所有数据都使用TTL写入时,这是一个共同的目标。在过期/ TTL工作负载中,整个SSTable的内容可能大约在同一时间到期,允许完全删除它们,并且比使用SizeTieredCompactionStrategy或LeveledCompactionStrategy时更可靠地回收空间。基本概念是TimeWindowCompactionStrategy将为给定窗口的每个文件创建1个sstable,其中窗口简单地计算为两个主要选项的组合:
-
compaction_window_unit
(默认: DAYS) - Java TimeUnit(MINUTES,HOURS或DAYS)。
-
compaction_window_size
(默认: 1) - 组成窗口的单位数。
综合起来,操作员可以指定几乎任何大小的窗口,而TimeWindowCompactionStrategy将为该窗口内的写入创建单个sstable。为了在写入期间的效率,最新的窗口将使用SizeTieredCompactionStrategy进行压实。
理想情况下,运算符应该选择一个产生约20-30个窗口的compaction_window_unit和compaction_window_size,例如,如果使用90天的TTL写入,那3天窗口将是一个合理的选择('compaction_window_unit':'DAYS','compaction_window_size':3)。
时间窗口压实策略操作问题
TWCS的主要动机是通过时间戳分隔磁盘上的数据,并允许完全过期的SSTables更有效地删除。这种最佳行为可以被颠覆的一个潜在的方式是如果数据被写入SSTables而无序,新数据和旧数据在同一SSTable中。无序数据可以以两种方式显示:
- 如果用户在传统的写入路径中混合旧数据和新数据,则数据将在memtables中合并并刷新到同一SSTable中,在那里它将保持合并。
- 如果用户对旧数据的读取请求导致将旧数据拉入当前memtable的读修复,则该数据将被合并并刷新到同一SSTable中。
虽然TWCS试图最大限度地减少数据的影响,用户应该尝试避免这种行为。具体来说,用户应避免通过CQL USING TIMESTAMP显式设置时间戳的查询。此外,用户应运行频繁的修复(以不会混合的方式传输数据),并通过将表的read_repair_chance和dclocal_read_repair_chance设置为0来禁用后台读修复。
更改时间窗口压实策略选项
希望在现有数据上启用时间窗口压实策略(TimeWindowCompactionStrategy)的操作员应首先考虑运行主要压实,将所有现有数据放入单个(旧)窗口。随后,较新的写入将按预期创建典型的SSTables。
希望更改compaction_window_unit或compaction_window_size的操作符可以这样做,但可能会在相邻窗口连接在一起时触发其他压实。如果窗口大小减小d(例如,从24小时到12小时),则现有SSTables不会被修改 - TWCS不能将现有SSTables拆分为多个窗口。