Compaction

对于基于 LSM 树的数据库,压缩是极其关键的。它将重叠的碎片化 SST 文件合并成一个有序的文件,丢弃已删除的数据,同时显著提高查询性能。

从 v0.9.1 版本开始,GreptimeDB 提供了控制 SST 文件如何压缩的策略:时间窗口压缩策略(TWCS)和严格窗口压缩策略(SWCS)。

概念

让我们从 GreptimeDB 中压缩的核心概念开始介绍。

SST 文件

当内存表刷新到持久存储(如磁盘和对象存储)时,会生成排序的 SST 文件。

在 GreptimeDB 中,SST 文件中的数据行按tag 列和时间戳组织,如下所示。每个 SST 文件覆盖特定的时间范围。当查询指定一个时间范围时,GreptimeDB 只检索可能包含该范围内数据的相关 SST 文件,而不是加载所有已持久化的文件。

SST layout

通常,在实时写入工作负载中,SST 文件的时间范围不会重叠。然而,由于数据删除和乱序写入等因素,SST 文件可能会有重叠的时间范围,这会影响查询性能。

时间窗口

时间序列工作负载呈现出显著的“窗口”特征,即最近插入的行更有可能被读取。因此,GreptimeDB 将时间轴逻辑上划分为不同的时间窗口,我们更关注压缩那些落在同一时间窗口内的 SST 文件。

特定表的时间窗口参数通常是从最新 flush 到存储的 SST 文件推断出来的,或者如果选择了 TWCS,您可以在建表时手动指定时间窗口。

GreptimeDB 预设了一组窗口大小,它们是:

  • 1 小时
  • 2 小时
  • 12 小时
  • 1 天
  • 1 周
  • 1 年
  • 10 年

如果未指定时间窗口大小,GreptimeDB 将在第一次压缩时推断窗口,通过从上述集合中选择能够覆盖所有要压缩文件的整个时间跨度的最小的时间窗口作为时间窗口大小。

例如,在第一次压缩期间,所有输入 SST 文件的时间跨度为 4 小时,那么 GreptimeDB 将选择 12 小时作为该表的时间窗口,并将此参数持久化以便后续的压缩中使用。

GreptimeDB 将包含最近插入时间戳的窗口视为活跃窗口(active window),而将之前的那些窗口视为非活跃窗口(inactive window)

有序组

有序组(sorted runs)是一个包含已排序且时间范围不重叠的 SST 文件的集合。

例如,一个表包含 5 个 SST,时间范围如下(全部包括在内):[0, 10], [12, 23], [22, 25],[24, 30],[26,33],我们可以找到 2 个有序组:

num-of-sorted-runs

有序组的数量往往能够反映 SST 文件的有序性。更多的有序组通常会导致查询性能变差,因为特定时间范围的查询往往会命中多个重叠的文件。压缩的主要目标是减少有序组的数量。

层级

基于 LSM 树的数据库常常有多个层级,数据的键(key)会逐层进行合并。GreptimeDB 只有两个层级,分别是 0(未压缩)和 1(已压缩)。

压缩策略

GreptimeDB 提供了上述两种压缩策略,但在创建表时只能选择时间窗口压缩策略(TWCS)。严格窗口(SWCS)仅在执行手动压缩时可用。

时间窗口压缩策略(TWCS)

TWCS 主要旨在减少压缩过程中的读 / 写放大。

它将要压缩的文件分配到不同的时间窗口。对于每个窗口,TWCS 会识别有序组。如有序组的数量超过了允许的最大值,TWCS 会找到一个解决方案,在考虑合并惩罚的同时将其减少到阈值以下。如果有序组的数量没有超过阈值,TWCS 会检查是否存在过多的文件碎片,并在必要时合并这些碎片文件,因为 SST 文件数量也会影响查询性能。

对于窗口分配,SST 文件可能跨越多个时间窗口。为了确保不受陈旧数据影响,TWCS 根据 SST 的最大时间戳来进行分配。在时间序列工作负载中,无序写入很少发生,即使发生了,最近数据的查询性能也比陈旧数据更为重要。

TWCS 提供了 4 个参数供调整:

  • max_active_window_runs: 活跃窗口中最大允许存在的有序组数量(默认为 4)
  • max_active_window_files: 活跃窗口中最大允许存在的文件数量(默认为 4)
  • max_inactive_window_runs: 非活跃窗口中最大允许存在的有序组数量(默认为 1)
  • max_inactive_window_files: 非活跃窗口中最大允许存在的文件数量(默认为 1)

您可以为活跃窗口和非活跃窗口设置不同的阈值。这很重要,因为乱序写入通常发生在活跃窗口中。通过允许更多重叠文件存在于活跃窗口,TWCS 在数据摄取过程中减少了写放大,并在活跃窗口变为非活跃时合并所有这些文件。

以下图表显示了当最大活跃窗口允许的有序组数量为 1 时,活跃窗口中的文件如何被压缩:

  • 在 A 中,有两个 SST 文件[0, 3][5, 6, 9],但由于这两个文件的时间范围不重叠,因此只有一个有序组。
  • 在 B 中,一个新的 SST 文件[1, 4]被刷新,因此形成了两个有序组。然后将 [0, 3][1, 4] 合并为 [0, 1, 3, 4].
  • 在 C 中,一个新的 SST 文件 [9, 10 ] 被刷新,并且它将与 [5, 6, 10 ] 合并以创建 [5, 6, 9, 10].
  • D 是最终状态,两个压缩后的文件中形成一个有序组。

compaction-twcs-active.jpg

指定 TWCS 参数

用户可以在创建表时指定前述的 TWCS 参数,例如:

  1. CREATE TABLE monitor (
  2. host STRING,
  3. ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP() TIME INDEX,
  4. cpu FLOAT64 DEFAULT 0,
  5. memory FLOAT64,
  6. PRIMARY KEY(host))
  7. WITH (
  8. 'compaction.type'='twcs',
  9. 'compaction.twcs.max_active_window_runs'='4',
  10. 'compaction.twcs.max_active_window_files'='8',
  11. 'compaction.twcs.max_inactive_window_runs'='1',
  12. 'compaction.twcs.max_inactive_window_files'='2'
  13. );

严格窗口压缩策略(SWCS)和手动压缩

与 TWCS 根据 SST 文件的最大时间戳为每个窗口分配一个 SST 文件不同的是,严格窗口策略(SWCS)将 SST 文件分配给所有与此文件的时间范围重叠的窗口,正如其名称所示。因此,一个 SST 文件可能会包含在多个压缩输出中。由于其在压缩期间的高读取放大率,SWCS 并不是默认的压缩策略。然而,当用户需要手动触发压缩以重新组织 SST 文件布局时,它是有用的,特别是当单个 SST 文件跨越较大的时间范围而显著减慢查询速度时。GreptimeDB 提供了一个简单的 SQL 函数来触发手动压缩:

  1. ADMIN COMPACT_TABLE(
  2. <table_name>,
  3. <strategy_name>,
  4. [<strategy_parameters>]
  5. );

<strategy_name> 参数可以是 twcsswcs(大小写不敏感),分别指定时间窗口压缩策略和严格窗口压缩策略。 对于 swcs 策略, <strategy_parameters> 指定用于拆分 SST 文件的窗口大小(以秒为单位)。例如:

  1. ADMIN COMPACT_TABLE(
  2. "monitor",
  3. "swcs",
  4. "3600"
  5. );
  6. +--------------------------------------------------------------------+
  7. | ADMIN compact_table(Utf8("monitor"),Utf8("swcs"),Utf8("3600")) |
  8. +--------------------------------------------------------------------+
  9. | 0 |
  10. +--------------------------------------------------------------------+
  11. 1 row in set (0.01 sec)

执行此语句时,GreptimeDB 会将每个 SST 文件按 1 小时(3600 秒)的时间跨度拆分成多个分块,并将这些分块合并为一个输出文件,确保没有重叠的文件。

下图展示了一次 SWCS 压缩的过程:

在图 A 中,有 3 个重叠的 SST 文件,分别是 [0, 3](也就是包含 0、1、2、3 的时间戳)、[3, 8][8, 10]。 严格窗口压缩策略会将覆盖了窗口 0、4、8 的文件 [3, 8] 分别分配给 3 个窗口,从而分别和 [0, 3] 以及 [8, 10] 合并。 图 B 给出了最终的压缩结果,分别有 3 个文件: [0, 3][4, 7][8, 10],它们彼此互相不重叠。

compaction-strict-window.jpg