You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

已crjry表为例主键模型并且分桶为1。现象为当表为空的时候一开始写入很快随着数据量增加StreamLoad变得越来越慢到最后超时。

问题一、主键模型写放大

在Doris中主键模型Unique Key Model的实现方式是 Merge-on-Write (MoW)。理解它的工作原理,就能明白为什么您的问题会如此严重。

Merge-on-Write 工作流程(对于更新或插入操作)

  1. 读取 (Read): 当新数据写入时Doris需要先根据主键找到对应的旧数据行所在的底层文件。
  2. 合并 (Merge): 在内存中,将旧数据行标记为删除,并与新数据行合并。
  3. 写入 (Write): 将合并后的新数据包含被删除的旧行和新行写入一个新的文件版本Rowset

这个过程也叫 “读后写”“写放大 (Write Amplification)”。一次逻辑上的写入,在物理层面变成了一次“读取+写入”的操作其资源开销远大于直接追加数据的模型Append Model

问题二、数据倾斜

  1. 底层机制 (Ingredient 1): 您使用了主键模型 (Merge-on-Write),这决定了每次写入的成本都很高。
  2. 写入模式 (Ingredient 2): 您的Java应用在进行高频次的Stream Load,这意味着在短时间内有大量的“读后写”请求被触发。
  3. 致命缺陷 (The Catalyst): 您的表存在严重的数据倾斜,导致所有高频写入请求都砸向了同一个Tablet (tablet_id=1173992)。

故障拆解

  1. 热点Tablet过载: 所有高频的、高成本的“读后写”操作全部集中在单一的Tablet上。这个Tablet所在的BE节点的CPU和磁盘I/O被瞬间打满。
  2. Compaction不堪重负: 每次写入都会为这个Tablet生成一个新的小文件版本Rowset。后台的Compaction任务拼命想把这些小文件合并起来但它的速度远远跟不上写入生成的速度。Compaction和写入操作激烈地争抢系统资源进一步加剧了BE节点的负载。
  3. 写入超时 (Doris端): 在极高的负载下,某一个写入事务的最后一步——Publish Version让版本生效无法在规定时间内完成。于是Doris BE日志中出现了第一个关键错误PUBLISH_TIMEOUT
  4. 版本链断裂: 这个超时的事务虽然在FE元数据层面可能被认为是成功的但在BE数据层面它的版本没有被成功应用。这就造成了Tablet版本链上的一个“空洞”。例如成功了版本325762,但版本325763超时失败了。
  5. 连锁失败 (Doris端): 后续所有发往这个Tablet的写入请求比如想写入版本325764,都会发现325763版本缺失于是Doris拒绝写入并抛出第二个关键错误version not continuous
  6. 请求超时 (客户端): 与此同时您的Java应用程序还在苦苦等待Doris返回Stream Load的成功响应。但由于Doris内部已经陷入“超时->版本不连续”的恶性循环根本无法处理完这个请求。最终Java客户端的HTTP ReadTimeout被触发,应用日志中抛出了我们看到的异常:java.net.SocketTimeoutException: Read timed out

结论:主键模型对数据倾斜和高频更新场景极其敏感

解决办法

在无法优化导入的前提下:

  1. streamload 发起端增加 group_commit 参数
# 导入时在 header 中增加"group_commit:async_mode"配置  
  
curl --location-trusted -u {user}:{passwd} -T data.csv -H "group_commit:async_mode" -H "column_separator:," http://{fe_host}:{http_port}/api/db/dt/_stream_load  
{  
"TxnId": 7009,  
"Label": "group_commit_c84d2099208436ab_96e33fda01eddba8",  
"Comment": "",  
"GroupCommit": true,  
"Status": "Success",  
"Message": "OK",  
"NumberTotalRows": 2,  
"NumberLoadedRows": 2,  
"NumberFilteredRows": 0,  
"NumberUnselectedRows": 0,  
"LoadBytes": 19,  
"LoadTimeMs": 35,  
"StreamLoadPutTimeMs": 5,  
"ReadDataTimeMs": 0,  
"WriteDataTimeMs": 26  
}  
  
# 返回的 GroupCommit 为 true说明进入了 group commit 的流程  
# 返回的 Label 是 group_commit 开头的,是真正消费数据的导入关联的 label

group_commit 可以实现在服务端赞批的功能当size达到表设置的 group_commit_data_bytes 大小时提交一次可以节省事务和compaction开销

Group Commit 的默认提交数据量为 64 MB用户可以通过修改表的配置调整

# 修改提交数据量为 128MB  
ALTER TABLE dt SET ("group_commit_data_bytes" = "134217728");

如果设置太大,数据会缓存到内存中,导致内存占用率高,这里需要注意

如果设置了group_commit还是出现磁盘占用高的问题可以尝试临时关闭表压缩功能

ALTER TABLE dt SET ("disable_auto_compaction" = "true");

设置完成后这张表的tablet不会压缩但是会占用大量磁盘空间非紧急情况不要使用。