压缩概述
什么是压缩?
为什么必须运行压缩?
由于在读取操作期间会查询 SSTable,因此保持 SSTable 的数量较少非常重要。写入操作会导致 SSTable 的数量增加,因此压缩是必要的。除了墓碑问题外,数据还会因其他原因被删除,例如某些数据的生存时间 (TTL) 到期。删除、更新或过期数据都是触发压缩的有效原因。
压缩能实现什么?
压缩实现的两个重要因素是性能提升和磁盘空间回收。如果 SSTable 包含必须读取的重复数据,则读取操作会变慢。一旦墓碑和重复数据被删除,读取操作就会更快。SSTable 使用磁盘空间,通过压缩减少 SSTable 的大小可以释放磁盘空间。
压缩是如何工作的?
压缩在 SSTable 集合上进行。从这些 SSTable 中,压缩收集每个唯一行的所有版本,并使用每个行的列的最新版本(按时间戳)组装一个完整的行。合并过程是高效的,因为行在每个 SSTable 中按分区键排序,并且合并过程不使用随机 I/O。每个行的最新版本将写入一个新的 SSTable。旧版本以及任何准备删除的行将保留在旧 SSTable 中,并在完成挂起的读取后立即删除。
压缩类型
压缩的概念用于 Cassandra 中的不同类型的操作,这些操作的共同点是它们接受一个或多个 SSTable,进行合并,并输出新的 SSTable。压缩类型包括
- 小压缩
-
小压缩在 Cassandra 中会因多种操作而自动触发
-
当通过刷新将 SSTable 添加到节点时
-
当在禁用后启用自动压缩时 (
nodetool enableautocompaction
) -
当压缩添加新的 SSTable 时
-
每 5 分钟检查一次新的轻微压缩
-
- 大压缩
-
当用户在节点上的所有 SSTable 上执行压缩时,会触发大压缩。
- 用户定义的压缩
-
与大压缩类似,用户定义的压缩在用户触发对给定 SSTable 集的压缩时执行。
- 清理
-
清理会触发压缩以尝试修复任何损坏的 SSTable。如果数据已损坏,这实际上可能会删除有效数据。如果发生这种情况,您需要在节点上运行完整的修复。
- 升级 SSTable
-
当您将 SSTable 升级到最新版本时,会发生压缩。在升级到新的主要版本后运行此操作。
- 清理
-
执行压缩以删除节点不再拥有的任何范围。这种类型的压缩通常在节点引导后在相邻节点上触发,因为引导节点将从这些节点获取一些范围的所有权。
- 辅助索引重建
-
如果在节点上重建辅助索引,则会触发压缩。
- 反压缩
-
修复后,实际修复的范围将从修复开始时存在的 SSTable 中拆分出来。这种类型的压缩会重写 SSTable 来完成此任务。
- 子范围压缩
-
可以只压缩给定的子范围 - 如果您知道一个行为异常的令牌 - 正在收集许多更新或许多删除,此操作很有用。命令
nodetool compact -st x -et y
将选择包含 x 和 y 之间范围的所有 SSTable,并为这些 SSTable 发出压缩命令。对于大小分层压缩策略,这很可能包括所有 SSTable,但对于分层压缩策略,它可以为 SSTable 的子集发出压缩命令。使用 LCS,生成的 SSTable 将最终位于 L0 中。
策略
不同的压缩策略可用于针对不同的工作负载进行优化。为您的工作负载选择正确的压缩策略将确保查询和压缩本身的最佳性能。
统一压缩策略 (UCS)
-
UCS 对于大多数工作负载来说都是一个不错的选择,并且建议用于新的工作负载。这种压缩策略旨在处理各种各样的工作负载。它旨在能够处理不可变的时间序列数据和具有大量更新和删除的工作负载。它还旨在能够处理旋转磁盘和 SSD。
大小分层压缩策略 (STCS)
-
STCS 是默认的压缩策略,因为它在其他策略不适合工作负载时可以用作后备。最适合不严格的时间序列工作负载(使用旋转磁盘),或者当来自
LCS
的 I/O 太高时。 分层压缩策略 (LCS)
-
分层压缩策略 (LCS) 针对读取密集型工作负载或具有大量更新和删除的工作负载进行了优化。它不适合不可变的时间序列数据。
时间窗口压缩策略 (TWCS)
-
时间窗口压缩策略专为 TTL 的、主要是不可变的时间序列数据而设计。
墓碑
什么是墓碑?
Cassandra 删除数据的过程旨在提高性能,并与 Cassandra 的数据分布和容错内置属性一起使用。
Cassandra 将删除视为插入,并插入一个带有时间戳的删除标记,称为墓碑。墓碑会经过 Cassandra 的写入路径,并写入一个或多个节点上的 SSTable。墓碑的主要特征区别在于它有一个内置的过期日期/时间。在过期时间结束时,即宽限期结束时,墓碑会在 Cassandra 的正常压缩过程中被删除。
您也可以使用生存时间 (TTL) 值标记 Cassandra 行或列。在该时间段结束后,Cassandra 会用墓碑标记该对象,并像处理其他墓碑对象一样处理它。 |
僵尸
在多节点集群中,Cassandra 可能会在两个或多个节点上存储相同数据的副本。这有助于防止数据丢失,但会使删除过程变得复杂。如果一个节点收到对其本地存储数据的删除命令,该节点会将指定对象标记为墓碑,并尝试将墓碑传递给包含该对象副本的其他节点。但是,如果一个副本节点此时没有响应,它不会立即收到墓碑,因此它仍然包含对象在删除之前的版本。如果墓碑对象在该节点恢复之前已从集群中的其他节点删除,Cassandra 会将该节点上的对象视为新数据,并将其传播到集群中的其他节点。这种已删除但仍然存在的对象称为 僵尸。
宽限期
为了防止僵尸重新出现,Cassandra 会为每个墓碑设置一个宽限期。墓碑的宽限期由表属性 ` WITH gc_grace_seconds` 设置。其默认值为 864000 秒(十天),在此之后,墓碑会过期,并在压缩期间被删除。在宽限期结束之前,Cassandra 会在压缩事件中保留墓碑。每个表都可以为此属性设置自己的值。
宽限期的目的是为无响应节点提供时间来恢复并正常处理墓碑。如果客户端在宽限期内写入对墓碑对象的更新,Cassandra 会覆盖墓碑。如果客户端在宽限期内发送对该对象的读取请求,Cassandra 会忽略墓碑,并尽可能从其他副本中检索该对象。
当无响应节点恢复时,Cassandra 使用暗示性传递来重放该节点在停机期间错过的数据库变动。Cassandra 不会在宽限期内重放对墓碑对象的变动。但是,如果该节点在宽限期结束之后才恢复,Cassandra 可能会错过删除操作。
在墓碑的宽限期结束之后,Cassandra 会在压缩期间删除墓碑。
删除
在 gc_grace_seconds
过期后,墓碑可能会被删除(这意味着不再存在任何关于特定数据被删除的对象)。但是,删除操作的一个复杂之处在于,墓碑可能存在于一个 SSTable 中,而它标记为删除的数据可能存在于另一个 SSTable 中,因此压缩操作还必须删除这两个 SSTable。更准确地说,删除实际的墓碑
-
墓碑必须比
gc_grace_seconds
更旧。请注意,即使gc_grace_seconds
已过,墓碑也不会在压缩事件之前被删除。 -
如果分区 X 包含墓碑,则包含该分区的 SSTable 以及包含比包含 X 的墓碑更旧数据的其他所有 SSTable 必须包含在同一个压缩操作中。如果包含分区 X 的任何 SSTable 中的所有数据都比墓碑更新,则可以忽略它。
-
如果启用了选项
only_purge_repaired_tombstones
,则只有在数据也已修复的情况下才会删除墓碑。此过程在“使用墓碑进行删除”部分中进行了描述。
如果节点停机或断开连接的时间超过 gc_grace_seconds
,其已删除的数据将被修复回其他节点,并在集群中重新出现。这与“不使用墓碑进行删除”部分中的情况基本相同。
不使用墓碑进行删除
想象一个包含三个节点的集群,该集群将值 [A] 复制到每个节点。
[A], [A], [A]
如果其中一个节点发生故障,而我们的删除操作只删除现有值,我们最终可能会得到一个看起来像这样的集群
[], [], [A]
然后,修复操作会将值 [A] 替换回缺少该值的两个节点。
[A], [A], [A]
这会导致我们的数据作为僵尸复活,即使它已被删除。
使用墓碑进行删除
从包含三个节点的集群开始,该集群将值 [A] 复制到每个节点。
[A], [A], [A]
如果我们不是删除数据,而是添加一个墓碑对象,那么单个节点故障情况将看起来像这样
[A, Tombstone[A]], [A, Tombstone[A]], [A]
现在,当我们执行修复操作时,墓碑将被复制到副本,而不是被删除的数据被复活
[A, Tombstone[A]], [A, Tombstone[A]], [A, Tombstone[A]]
我们的修复操作将正确地将系统状态设置为我们期望的状态,即对象 [A] 在所有节点上都被标记为已删除。但这意味着我们最终会积累墓碑,这些墓碑会永久占用磁盘空间。为了避免永久保留墓碑,我们为 Cassandra 中的每个表设置 gc_grace_seconds
。
完全过期的 SSTable
如果 SSTable 只包含墓碑,并且可以保证该 SSTable 没有覆盖任何其他 SSTable 中的数据,则压缩操作可以删除该 SSTable。如果您看到只包含墓碑的 SSTable(请注意,一旦生存时间过期,TTL 数据就被视为墓碑),但它没有被压缩操作删除,则可能是其他 SSTable 包含更旧的数据。有一个名为 sstableexpiredblockers
的工具可以列出哪些 SSTable 可删除,以及哪些 SSTable 阻止它们被删除。使用 TimeWindowCompactionStrategy
,可以通过启用 unsafe_aggressive_sstable_expiration
来删除保证(不检查是否有覆盖数据)。
TTL
Cassandra 中的数据可以具有一个名为生存时间的附加属性 - 此属性用于在达到时间后自动删除已过期的数据。一旦 TTL 过期,数据就会被转换为墓碑,墓碑至少会保留 gc_grace_seconds
时间。请注意,如果您将 TTL 数据与没有 TTL 的数据(或 TTL 长度不同的数据)混合使用,Cassandra 将难以删除创建的墓碑,因为分区可能跨越多个 SSTable,并且不会同时压缩所有 SSTable。
完全过期的 SSTable
如果 SSTable 只包含墓碑,并且可以保证该 SSTable 没有覆盖任何其他 SSTable 中的数据,则压缩操作可以删除该 SSTable。如果您看到只包含墓碑的 SSTable(请注意,一旦生存时间过期,TTL 数据就被视为墓碑),但它没有被压缩操作删除,则可能是其他 SSTable 包含更旧的数据。有一个名为 sstableexpiredblockers
的工具可以列出哪些 SSTable 可删除,以及哪些 SSTable 阻止它们被删除。使用 TimeWindowCompactionStrategy
,可以通过启用 unsafe_aggressive_sstable_expiration
来删除保证(不检查是否有覆盖数据)。
已修复/未修复数据
使用增量修复,Cassandra 必须跟踪哪些数据已修复,哪些数据未修复。使用反压缩,已修复的数据被拆分为已修复和未修复的 SSTable。为了避免再次混合数据,对这两组数据分别运行单独的压缩策略实例,每个实例只知道已修复或未修复的 SSTable。这意味着,如果您只运行一次增量修复,然后不再运行,则已修复的 SSTable 中可能存在非常旧的数据,这些数据会阻止压缩操作删除未修复的(可能更新的)SSTable 中的墓碑。
数据目录
由于墓碑和数据可能存在于不同的 SSTable 中,因此重要的是要意识到,丢失 SSTable 可能会导致数据再次变为活动状态 - 丢失 SSTable 的最常见方式是硬盘驱动器发生故障。为了避免使数据变为活动状态,墓碑和实际数据始终位于同一个数据目录中。这样,如果磁盘丢失,则分区的全部版本都会丢失,并且不会有任何数据被取消删除。为了实现这一点,除了包含已修复/未修复数据的压缩策略实例之外,还会为每个数据目录运行一个压缩策略实例,这意味着,如果您有 4 个数据目录,则将运行 8 个压缩策略实例。这除了避免数据被取消删除之外,还有其他一些好处
-
可以并行运行更多压缩操作 - 分层压缩将具有多个完全独立的分层,每个分层都可以独立于其他分层运行压缩操作。
-
用户可以备份和还原单个数据目录。
-
但是请注意,目前所有数据目录都被视为相等,因此,如果您有一个小型磁盘和一个大型磁盘分别支持两个数据目录,则大型磁盘将受到小型磁盘的限制。解决此问题的一种方法是创建更多由大型磁盘支持的数据目录。
单个 SSTable 墓碑压缩
当写入 SSTable 时,会创建一个包含墓碑过期时间的直方图,并使用它来尝试查找包含大量墓碑的 SSTable,并在该 SSTable 上运行单个 SSTable 压缩,以期能够删除该 SSTable 中的墓碑。在开始此操作之前,还会检查删除任何墓碑的可能性以及该 SSTable 与其他 SSTable 的重叠程度。为了避免大多数这些检查,可以启用压缩选项 unchecked_tombstone_compaction
。
常见选项
所有压缩策略都有许多常见选项;
enabled
(默认值:true)-
是否应运行次要压缩。请注意,您可以将 'enabled':true 作为压缩选项,然后执行 'nodetool enableautocompaction' 来开始运行压缩操作。
tombstone_threshold
(默认值:0.2)-
SSTable 中应有多少墓碑才能让我们考虑对该 SSTable 进行单个 SSTable 压缩。
tombstone_compaction_interval
(默认值:86400 秒(1 天))-
由于在进行单次 SSTable 合并时可能无法丢弃任何墓碑,因此我们需要确保一个 SSTable 不会不断地被重新合并 - 此选项指定我们应该多久尝试一次给定的 SSTable。
log_all
(默认:false)-
新的详细合并日志记录,请参阅
below <detailed-compaction-logging>
。 unchecked_tombstone_compaction
(默认:false)-
单次 SSTable 合并对是否应该启动有非常严格的检查,此选项禁用这些检查,对于某些用例可能需要这样做。请注意,这不会改变实际合并的任何内容,只有在安全的情况下才会丢弃墓碑 - 它可能只是重写 SSTable 而无法丢弃任何墓碑。
only_purge_repaired_tombstone
(默认:false)-
启用额外安全性的选项,确保只有在数据已修复的情况下才丢弃墓碑。
min_threshold
(默认:4)-
触发合并之前 SSTable 数量的下限。不适用于
LeveledCompactionStrategy
。 max_threshold
(默认:32)-
触发合并之前 SSTable 数量的上限。不适用于
LeveledCompactionStrategy
。
此外,请参阅每个策略部分以了解特定附加选项。
合并 nodetool 命令
nodetool <nodetool>
实用程序提供了许多与合并相关的命令
enableautocompaction
-
启用合并。
disableautocompaction
-
禁用合并。
setcompactionthroughput
-
合并运行速度的上限 - 默认值为 64MiB/s。
compactionstats
-
有关当前和待处理合并的统计信息。
compactionhistory
-
列出有关最近合并的详细信息。
setcompactionthreshold
-
设置触发合并的最小/最大 SSTable 数量,默认值为 4/32。
使用 JMX 切换合并策略和选项
可以使用 JMX 在单个节点上切换合并策略及其选项,这是一种在不影响整个集群的情况下试验设置的好方法。mbean 是
org.apache.cassandra.db:type=ColumnFamilies,keyspace=<keyspace_name>,columnfamily=<table_name>
要更改的属性是CompactionParameters
或CompactionParametersJson
(如果您使用的是 jconsole 或 jmc)。例如,json 版本的语法与您在ALTER TABLE <alter-table-statement>
语句中使用的语法相同
{ 'class': 'LeveledCompactionStrategy', 'sstable_size_in_mb': 123, 'fanout_size': 10}
该设置将一直保留,直到有人执行影响合并设置的ALTER TABLE <alter-table-statement>
或重新启动节点。