已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`。 **结论:主键模型对数据倾斜和高频更新场景极其敏感**