# Compaction

> 介绍 GreptimeDB 中的压缩策略，包括时间窗口压缩策略（TWCS）和严格窗口压缩策略（SWCS），以及它们的概念、参数和使用示例。

# Compaction

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

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

## 概念

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

### SST 文件

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

在 GreptimeDB 中，SST 文件中的数据行按[tag 列](/user-guide/concepts/data-model.md)和时间戳组织，如下所示。每个 SST 文件覆盖特定的时间范围。当查询指定一个时间范围时，GreptimeDB 只检索可能包含该范围内数据的相关 SST 文件，而不是加载所有已持久化的文件。

![SST layout](/compaction-sst-file-layout.jpg)

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

### 时间窗口

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

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

GreptimeDB 预设了一组窗口大小，它们是：
- 1 小时
- 2 小时
- 12 小时
- 1 天
- 1 周

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

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

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

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

![num-of-sorted-runs](/compaction-num-sorted-runs.jpg)

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

### 层级

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

## 压缩策略

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

## 时间窗口压缩策略（TWCS）

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

TWCS 将要压缩的文件分配到不同的时间窗口。对于每个窗口，TWCS 会识别有序组。如当前出现了多于一个有序组，TWCS 会基于合并开销来计算一个文件合并策略，从而将有序组的数量减少到 1。如果有序组的数量没有超过 1（也就是任意两个文件的时间范围都不重叠），TWCS 会检查是否存在过多的文件碎片，并在必要时合并这些碎片文件，因为 SST 文件数量也会影响查询性能。

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

TWCS 提供了 5 个参数供调整：
- `trigger_file_num`: 单一时间窗口中触发 compaction 的文件数量（默认为 4）
- `max_output_file_size`: compaction 产生文件的最大大小（默认无限制）

以下图表显示了当 `trigger_file_num`为 3 时，窗口中的文件如何被压缩：
- 在 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 中的样子。
- 在 E 中，一个新文件 `[11, 12]` 被写入。尽管仍然只有一个有序组，但文件数量达到了 `trigger_file_num`（3），因此将会把 `[5, 6, 9，10]` 与 `[11，12]` 合并形成 `[5，6，9，10，11，12 ]`.

![compaction-trigger-file-num.png](/compaction-trigger-file-num.png)

### 指定 TWCS 参数

TWCS 参数可以在两个级别指定：

1. **表级别**：在创建或修改表时明确设置
2. **数据库级别**：为数据库中的所有表设置默认值

有效的压缩设置在压缩调度时动态解析，优先级如下：
- 表级别设置（如果指定）
- 数据库级别设置（如果指定且没有表级别覆盖）
- 内置默认值

#### 表级别压缩设置

用户可以在创建表时指定 TWCS 参数：

```sql
CREATE TABLE monitor (
  host STRING,
  ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP() TIME INDEX,
  cpu FLOAT64 DEFAULT 0,
  memory FLOAT64,
  PRIMARY KEY(host))
WITH (
    'compaction.type'='twcs', 
    'compaction.twcs.trigger_file_num'='8', 
    'compaction.twcs.max_output_file_size'='500MB'
    );
```

#### 数据库级别压缩设置

您也可以为数据库中的所有表设置默认压缩参数。这些设置将被没有明确设置压缩选项的表继承。参见 [ALTER DATABASE](/reference/sql/alter.md#修改数据库的压缩选项) 获取示例。

:::note
与存储在表元数据中的表级别设置不同，数据库级别的压缩设置在运行时动态解析。更改数据库级别的压缩选项会立即影响数据库中所有没有明确设置压缩选项的表。此行为类似于[数据库级别的 TTL](/reference/sql/create.md#create-database) 的工作方式。
:::

## 严格窗口压缩策略（SWCS）和手动压缩

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

```sql
ADMIN COMPACT_TABLE(
    <table_name>, 
    <strategy_name>, 
    [<strategy_parameters>]
);
```

`<strategy_name>` 参数可以是 `twcs` 或 `swcs`（大小写不敏感），分别指定时间窗口压缩策略和严格窗口压缩策略。
对于 `swcs` 策略， `<strategy_parameters>` 可以指定：
- 用于拆分 SST 文件的窗口大小（以秒为单位）
- `parallelism` 参数用于控制压缩的并行度（默认为 1）

例如，触发使用 1 小时窗口的压缩：

```sql
ADMIN COMPACT_TABLE(
    "monitor", 
    "swcs", 
    "3600"
);

+--------------------------------------------------------------------+
| ADMIN compact_table(Utf8("monitor"),Utf8("swcs"),Utf8("3600")) |
+--------------------------------------------------------------------+
|                                                                  0 |
+--------------------------------------------------------------------+
1 row in set (0.01 sec)
```

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

你还可以指定 `parallelism` 参数来通过并发处理多个文件以加速压缩：

```sql
-- 使用默认时间窗口和并行度为 2 的 SWCS 压缩
ADMIN COMPACT_TABLE("monitor", "swcs", "parallelism=2");

-- 使用自定义时间窗口和并行度的 SWCS 压缩
ADMIN COMPACT_TABLE("monitor", "swcs", "window=1800,parallelism=2");
```

`parallelism` 参数同样适用于常规压缩：

```sql
-- 并行度为 2 的常规压缩
ADMIN COMPACT_TABLE("monitor", "regular", "parallelism=2");
```

下图展示了一次 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](/compaction-strict-window.jpg)
