背景
目前doris作为冷库,在5亿数据读写的情况下,由于数据写入HDD的性能会出现下降,所以需要compaction将小文件按照版本合并,由随机写组织成顺序写. 这需要保证compaction score消耗低于100.
Compaction的定义
Doris 的数据写入模型使用了 LSM-Tree 类似的数据结构。数据都是以追加(Append)的方式写入磁盘的。这种数据结构可以将随机写变为顺序写。这是一种面向写优化的数据结构,他能增强系统的写入吞吐,但是在读逻辑中,需要通过 Merge-on-Read 的方式,在读取时合并多次写入的数据,从而处理写入时的数据变更。
Merge-on-Read 会影响读取的效率,为了降低读取时需要合并的数据量,基于 LSM-Tree 的系统都会引入后台数据合并的逻辑,以一定策略定期的对数据进行合并。Doris 中这种机制被称为 Compaction
Compaction 使用遇到的问题
1.Compaction 速度低于数据写入速度
在高频写入场景下,短时间内会产生大量的数据版本。如果 Compaction 不及时,就会造成大量版本堆积,最终严重影响写入速度。
2. 写放大问题
Compaction 本质上是将已经写入的数据读取后重新写回的过程,这种数据重复写入被称为写放大。一个好的Compaction策略应该在保证效率的前提下,尽量降低写放大系数。过多的 Compaction 会占用大量的磁盘IO资源,影响系统整体效率。
Doris 中用于控制Compaction的参数非常多。将以下方面,介绍这些参数的含义以及如果通过调整参数来适配场景。
1.数据版本是如何产生的,哪些因素影响数据版本的产出。
2.为什么需要 Base 和 Cumulative 两种类型的 Compaction。
3.Compaction 机制是如何挑选数据分片进行 Compaction 的。
4.对于一个数据分片,Compaction 机制是如何确定哪些数据版本参与 Compaction 的。
5.在高频导入场景下,可以修改哪些参数来优化 Compaction 逻辑。
6.Compaction 相关的查看和管理命令。
数据版本的产生
首先,用户的数据表会按照分区和分桶规则,切分成若干个数据分片(Tablet)存储在不同 BE 节点上。每个 Tablet 都有多个副本(默认为3副本)。Compaction 是在每个 BE 上独立进行的,Compaction 逻辑处理的就是一个 BE 节点上所有的数据分片。
Doris的数据都是以追加的方式写入系统的。Doris目前的写入依然是以微批的方式进行的,每一批次的数据针对每个 Tablet 都会形成一个 rowset。而一个 Tablet 是由多个Rowset 组成的。每个 Rowset 都有一个对应的起始版本和终止版本。对于新增Rowset,起始版本和终止版本相同,表示为 [6-6]、[7-7] 等。多个Rowset经过 Compaction 形成一个大的 Rowset,起始版本和终止版本为多个版本的并集,如 [6-6]、[7-7]、[8-8] 合并后变成 [6-8]。
Tablet在doris的web界面可以观察到
Rowset 的数量直接影响到 Compaction 是否能够及时完成。那么一批次导入会生成多少个 Rowset 呢?这里举一个例子:
假设集群有3个 BE 节点。每个BE节点2块盘。只有一张表,2个分区,每个分区3个分桶,默认3副本。那么总分片数量是(2 * 3 * 3)18 个,如果均匀分布在所有节点上,则每个盘上3个tablet。假设一次导入涉及到其中一个分区,则一次导入总共产生9个Rowset,即平均每块盘产生1-2个 Rowset。(这里仅考虑数据完全均匀分布的情况下,实际情况中,可能多个 Tablet 集中在某一块磁盘上。)
从上面的例子可以得出,rowset的数量直接取决于表的分片数量。举个极端的例子,如果一个Doris集群只有3个BE节点,但是一个表有9000个分片。那么一次导入,每个BE节点就会新增3000个rowset,则至少要进行3000次compaction,才能处理完所有的分片。所以:合理的设置表的分区、分桶和副本数量,避免过多的分片,可以降低Compaction的开销。
建议配置
-
一个表的 Tablet 总数量等于 (Partition num * Bucket num)
-
一个 Partition 的 Bucket 数量一旦指定,不可更改。所以在确定 Bucket 数量时,需要预先考虑集群扩容的情况。比如当前只有 3 台 host,每台 host 有 1 块盘。如果 Bucket 的数量只设置为 3 或更小,那么后期即使再增加机器,也不能提高并发度
-
假设在有10台BE,每台BE一块磁盘的情况下。如果一个表总大小为 500MB,则可以考虑4-8个分片。5GB:8-16个分片。50GB:32个分片。500GB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片。5TB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片
集群环境
集群中的Tablet数量 = 分区数 * 分桶数 * 副本数
分桶数过多会造成FE元数据信息负载过高,从而影响导入和查询性能。一般发生在Apache Doris上线运行一段时间之后,随着越来越多数据的接入,数量的增长,集群运行一段时间后,读写就变得越来越慢,直到无法正常进行读写
-
分桶数太少
在数据量持续增长预期的情况下,可考虑以下分桶数:
Base 和 Cumulative 两种类型的 Compaction意义和解决思路
以下我们简称Base Compaction(BC) 和 Cumulative Compaction(CC)
1.Compaction 机制是如何挑选数据分片进行 Compaction 的。
- 对于一个数据分片,Compaction 机制是如何确定哪些数据版本参与 Compaction 的。
Compaction 分数
每次都选择数据版本最多的数据分片进行 Compaction。这个策略也是 Doris 的默认策略。这个策略在大部分场景下都能很好的工作。但是考虑到一种情况,就是版本多的分片,可能并不是最频繁访问的分片。而 Compaction 的目的就是优化读性能。那么有可能某一张 “写多读少” 表一直在 Compaction,而另一张 “读多写少” 的表不能及时的 Compaction,导致读性能变差。
因此,Doris 在选择数据分片时还引入了 “读取频率” 的因素。“读取频率” 和 “版本数量” 会根据各自的权重,综合计算出一个 Compaction 分数,分数越高的分片,优先做 Compaction
这两个因素的权重由以下 BE 参数控制(取值越大,权重越高):

如何影响tablet的compaction优先度呢?
tablet_score = k1 * scan_frequency + k2 * compaction_score
k1和k2分别可以通过参数compaction_tablet_scan_frequency_factor(默认值为0)和参数compaction_tablet_compaction_score_factor(默认值为1)动态配置。scan_frequency 表示tablet当前一段时间的scan频率(可以在监控面板上看到)。
compaction_score分数可以表征compaction任务从任务池每次可以拿到的permit令牌大小,每个tablet的permit越大,说明排队执行的任务越多
要降低routine load阶段的compaction压力,可以采取以下BE参数调整方法和优化策略:
BE参数调整方法
- 增加Compaction线程数:
- max_base_compaction_threads:默认是4,可以适当增加以加快Base Compaction的速度。
- max_cumu_compaction_threads:默认是每个盘1个,可以适当增加以加快Cumulative Compaction的速度。
- 调整Compaction任务数:
- compaction_task_num_per_disk:默认是4,可以适当增加以允许每个磁盘上执行更多的Compaction任务。
- compaction_task_num_per_fast_disk:默认是8,可以适当增加以允许每个快速磁盘上执行更多的Compaction任务。
- 调整Compaction任务配额:
- total_permits_for_compaction_score:默认是10000,可以适当增加以允许更多的Compaction任务同时进行。
- 调整Compaction任务的内存使用:
- max_cumulative_compaction_num_singleton_deltas:默认是1000,可以适当减少以限制每个Cumulative Compaction任务合并的版本数,从而减少内存使用。
优化策略
- max_cumulative_compaction_num_singleton_deltas:默认是1000,可以适当减少以限制每个Cumulative Compaction任务合并的版本数,从而减少内存使用。
- 优化数据导入频率和批量大小:
- 增大单次数据导入的量,降低导入频率,从而减少compaction的压力。
- 合理设置表的分区和分桶:
- 通过合理的分区和分桶策略,避免生成过多的数据分片,从而减少compaction的复杂性。
- 避免频繁的删除操作:
- 尽量减少使用DELETE操作,因为DELETE操作会在底层生成Delete版本,增加compaction的负担。
- 监控和报警:
- 通过监控Compaction的压力和相关指标,及时发现和处理问题,避免compaction压力过大。
具体操作步骤
- 通过监控Compaction的压力和相关指标,及时发现和处理问题,避免compaction压力过大。
- 调整BE参数:
-
修改be.conf文件,增加Compaction线程数和任务数:

-
保存文件并重启BE节点以使配置生效。
-
- 优化数据导入:
- 增大单次数据导入的量,降低导入频率。例如,将sink.batch.size设置为10000,sink.batch.interval设置为10秒。
- 合理设置表的分区和分桶:
- 根据数据量和查询需求,合理设置表的分区和分桶。例如,对于大数据量的表,可以增加分区数和分桶数。
- 监控和报警:
-
使用Grafana和Prometheus监控Compaction的压力和相关指标,设置报警阈值,及时发现和处理问题。
通过以上调整和优化策略,可以有效降低routine load阶段的compaction压力,提高系统的稳定性和查询性能。
tablet不健康排查方案

对此在存算分离模式下进行小文件合并的观测验证.
-
- 首先排查compaction score高的原因
- 找出score最高的若干个tablet, 一般是用户比较高频导入的表
- 分析score最高的tablet
- compaction 持续失败导致的compaction高
需要先定位出哪个tablet频繁进行compaction行为
grep "permits" log/be.INFO | tail -n 1000
为了调节BE节点compaction的内存使用量,Doris增加了对compaction任务提交的permission机制。系统维持一定数量的compaction permits(通过参数total_permits_for_compaction_score配置)
- 查看tablet在做什么compaction操作
curl http://xxxxxxxx:8040/api/compaction/show?tablet\\\\_id=192775
{
"cumulative policy type": "SIZE_BASED",
"cumulative point": 18438,
"last cumulative failure time": "1970-01-01 08:00:00.000",
"last base failure time": "1970-01-01 08:00:00.000",
"last cumulative success time": "2021-05-05 17:18:48.904",
"last base success time": "2021-05-05 16:14:49.786",
"rowsets": [
"[0-17444] 13 DATA NONOVERLAPPING 0200000000b1fb8d344f83103113563dd81740036795499d 2.86 GB",
"[17445-17751] 1 DATA NONOVERLAPPING 0200000000b25183344f83103113563dd81740036795499d 68.61 MB",
"[17752-18089] 1 DATA NONOVERLAPPING 0200000000b2b9a2344f83103113563dd81740036795499d 74.52 MB",
"[18090-18437] 1 DATA NONOVERLAPPING 0200000000b32686344f83103113563dd81740036795499d 76.41 MB",
"[18438-18678] 1 DATA NONOVERLAPPING 0200000000b37084344f83103113563dd81740036795499d 53.07 MB",
"[18679-18679] 1 DATA NONOVERLAPPING 0200000000b36d87344f83103113563dd81740036795499d 3.11 KB",
"[18680-18680] 1 DATA NONOVERLAPPING 0200000000b36d70344f83103113563dd81740036795499d 258.40 KB",
"[18681-18681] 1 DATA NONOVERLAPPING 0200000000b36da0344f83103113563dd81740036795499d 266.98 KB",
],
"stale_rowsets": [],
"stale version path": []
}
这里我们看到一个 tablet 的 Cumulative Point,最近一次成功、失败的 BC/CC 任务时间,以及每个 rowset 的版本信息。如上面这个示例,可以得出以下结论:
1.基线数据量大约在2-3GB,增量rowset增长到几十MB后就会晋升到BC任务区。
2. 新增rowset数据量很小,且版本增长较快,说明这是一个高频小批量的导入场景