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.
3.3 KiB
3.3 KiB
已crjry表为例,主键模型并且分桶为1。现象为:当表为空的时候一开始写入很快,随着数据量增加,StreamLoad变得越来越慢,到最后超时。
问题一、主键模型写放大
在Doris中,主键模型(Unique Key Model)的实现方式是 Merge-on-Write (MoW)。理解它的工作原理,就能明白为什么您的问题会如此严重。
Merge-on-Write 工作流程(对于更新或插入操作):
- 读取 (Read): 当新数据写入时,Doris需要先根据主键找到对应的旧数据行所在的底层文件。
- 合并 (Merge): 在内存中,将旧数据行标记为删除,并与新数据行合并。
- 写入 (Write): 将合并后的新数据(包含被删除的旧行和新行)写入一个新的文件版本(Rowset)。
这个过程也叫 “读后写” 或 “写放大 (Write Amplification)”。一次逻辑上的写入,在物理层面变成了一次“读取+写入”的操作,其资源开销远大于直接追加数据的模型(Append Model)。
问题二、数据倾斜
- 底层机制 (Ingredient 1): 您使用了主键模型 (Merge-on-Write),这决定了每次写入的成本都很高。
- 写入模式 (Ingredient 2): 您的Java应用在进行高频次的Stream Load,这意味着在短时间内有大量的“读后写”请求被触发。
- 致命缺陷 (The Catalyst): 您的表存在严重的数据倾斜,导致所有高频写入请求都砸向了同一个Tablet (
tablet_id=1173992
)。
故障拆解
- 热点Tablet过载: 所有高频的、高成本的“读后写”操作全部集中在单一的Tablet上。这个Tablet所在的BE节点的CPU和磁盘I/O被瞬间打满。
- Compaction不堪重负: 每次写入都会为这个Tablet生成一个新的小文件版本(Rowset)。后台的Compaction任务拼命想把这些小文件合并起来,但它的速度远远跟不上写入生成的速度。Compaction和写入操作激烈地争抢系统资源,进一步加剧了BE节点的负载。
- 写入超时 (Doris端): 在极高的负载下,某一个写入事务的最后一步——
Publish Version
(让版本生效),无法在规定时间内完成。于是,Doris BE日志中出现了第一个关键错误:PUBLISH_TIMEOUT
。 - 版本链断裂: 这个超时的事务虽然在FE(元数据)层面可能被认为是成功的,但在BE(数据)层面,它的版本没有被成功应用。这就造成了Tablet版本链上的一个“空洞”。例如,成功了版本
325762
,但版本325763
超时失败了。 - 连锁失败 (Doris端): 后续所有发往这个Tablet的写入请求,比如想写入版本
325764
,都会发现325763
版本缺失,于是Doris拒绝写入,并抛出第二个关键错误:version not continuous
。 - 请求超时 (客户端): 与此同时,您的Java应用程序还在苦苦等待Doris返回Stream Load的成功响应。但由于Doris内部已经陷入“超时->版本不连续”的恶性循环,根本无法处理完这个请求。最终,Java客户端的HTTP
ReadTimeout
被触发,应用日志中抛出了我们看到的异常:java.net.SocketTimeoutException: Read timed out
。
结论:主键模型对数据倾斜和高频更新场景极其敏感