16. Doris 系列第16篇:深挖 MOW 致命坑|Unique Key 写入炸裂、版本爆炸根源、链路剖析+应急根治方案

张开发
2026/4/5 12:43:37 15 分钟阅读

分享文章

16. Doris 系列第16篇:深挖 MOW 致命坑|Unique Key 写入炸裂、版本爆炸根源、链路剖析+应急根治方案
承接第14篇Compaction、15篇MOW基础原理破除「MOW版本少」误区直击生产高发MOW比MOR更容易触发500版本写入拒绝拆解底层病根、还原三大爆炸场景、给全架构优化配置脚本双模型混合落地策略一、颠覆固有认知MOW 真的能杜绝版本爆炸1.1 全网通用误区 生产真相✅ 大众固有理解MOR 读时合并、堆多版本 → 极易版本爆炸MOW 写入时合并、清旧数据 → 版本天然可控❌ 生产真实现状高频更新/热点单行改/大小文件混写场景下MOW 版本堆积速度MOR更快撞 500 硬性阈值、直接锁死写入核心诱因DeleteBitmap 机制拉高合并开销、形成「写入加速→合并拖慢→版本雪崩」死循环1.2 底层关键定性不管MOR还是MOW一个 Rowset 一个版本MOW 只是用位图标记旧数据作废不会自动回收版本计数位图依赖还会卡死Compaction收敛效率二、MOW 版本爆炸底层核心原理深扒2.1 MOW 最小存储单元结构版本计数关键Rowset N ├── Segment 实体数据文件 └── DeleteBitmap 作废标记位图核心MOR单纯数据RowsetMOW数据位图绑定Rowset但照样一条Rowset占一个版本名额2.2 三大核心病根版本爆炸元凶病根1强写入放大更新必增新版本MOR1次更新追加1条Rowset无额外标记开销MOW1次更新新增1条Rowset 旧Rowset打位图标记 更新主键索引 同等更新量版本新增数量一致但MOW后期回收极慢举例10万行全行更新MOR初始1 更新10万 100001版本MOW初始1 更新10万 100001版本版本基数完全持平差距全在合并回收效率病根2MOW Compaction 效率腰斩致命短板MOR合并纯数据拼接、简单归并IOCPU轻量MOW合并必须同步解析/重写/归档DeleteBitmap还要校验主键依赖 实测同等数据量MOR合并耗时30s → MOW合并耗时90s2~3倍开销直接导致写入产生版本速度 Compaction回收版本速度病根3位图依赖链强制串行合并、无法跳级连续更新同一主键100次V1→V2→V3→…→V100 每一代都依赖上一代Bitmap标记硬性限制不能跳过老版本直接合并中间版本必须从最旧版本开始串行消化一旦出现超大Base Rowset整个合并队列直接卡死结果新版本疯狂堆、老版本啃不动版本数直线冲500三、三大经典生产爆炸场景百分百踩中场景1热点单行高频更新库存/余额/秒杀业务千个热门SKU每秒千次库存扣减CREATETABLEinventory_mow(sku_idBIGINT,stockINT,update_timeDATETIME)UNIQUEKEY(sku_id)DISTRIBUTEDBYHASH(sku_id)BUCKETS10PROPERTIES(enable_unique_key_merge_on_writetrue);-- 循环单行扣减UPDATEinventory_mowSETstockstock-1WHEREsku_id10001;爆炸全过程每次扣减生成1条仅1行的极小Rowset海量微型Rowset堆叠MOW合并啃不动海量小位图版本线性暴涨 → 几秒直达500 → 写入拒绝实测每秒新增800个版本几分钟直接封表场景2大基数全量导入 零星小更新业务日亿级订单全量落库 实时改状态Rowset1亿级大文件10GBBase底Rowset2~N每次几十行微小更新卡死逻辑Compaction不敢动超大Base底重写成本爆炸零散小更新Rowset持续堆积大小文件无法混并版本只增不减场景3多MOW表并发合并资源打满多张MOW高频更新表共用mow_compaction_threads专用线程池耗尽后所有表合并集体积压弱优先级表直接版本放飞四、底层技术深挖为什么MOW回收永远跟不上4.1 DeleteBitmap 三重写入放大每次更新隐性开销读取旧Bitmap修改标记、落地新Bitmap同步刷新主键索引对比MOW一次更新 MOR三次写入开销4.2 小文件黑洞MOR小更新可被合并收敛MOW单次单行更新必生成独立Rowset独立Bitmap小文件越堆越多IO寻址爆炸4.3 串行依赖锁死合并吞吐量链式依赖→无法并行跳合并→老底不消化新版堆成山五、全套根治方案架构配置写入监控应急方案1业务层打散热点 强制攒批最有效① 热点主键打散-- 错误单热点key扎堆一个BucketUNIQUEKEY(sku_id)-- 正确加分片字段拆分热点UNIQUEKEY(sku_id,bucket_no)DISTRIBUTEDBYHASH(bucket_no)BUCKETS100;② 杜绝单行UPDATE统一攒批Python伪代码标准落地update_buffer[]# 攒1000~5000条再一次性StreamLoadiflen(update_buffer)2000:batch_stream_load(update_buffer)update_buffer.clear()核心把万次单行更新→几十次批量导入直接砍爆版本生成速度方案2BE内核配置激进调优加速MOW合并# 拉高MOW专属合并线程隔离通用Compactionmow_compaction_threads20# 全局合并线程扩容max_compaction_threads32# 降低累加合并触发门槛早合并、勤合并cumulative_compaction_num_cumulative_rowsets2# 限制单次合并文件大小避免大文件卡死队列cumulative_compaction_max_output_size2147483648方案3表级MOW激进收敛ALTERTABLEhot_mowSET(enable_mow_compactiontrue,mow_compaction_threshold2,-- 累计2轮更新就触发合并compaction_policysize_based);方案4前置缓冲架构彻底隔离高频写入标准链路业务→Kafka→Flink窗口攒批→Doris批量落库Flink开1~5分钟滚动窗口只保留每条主键最新状态往Doris灌大批次杜绝零碎小Rowset方案5精细化分区拆分版本压力按小时/按天分区分割热点更新只在当日分区历史分区低峰期统一Full Compaction单个分区版本爆炸不影响全表方案6全链路监控 自动熔断自救核心排查SQL-- 看MOW表版本暴涨SELECTtable_name,tablet_id,version_countFROMinformation_schema.tabletsWHEREenable_mowtrueANDversion_count400;-- 监控Bitmap膨胀SELECTtable_name,SUM(delete_bitmap_size)/1024/1024asbitmap_mbFROMinformation_schema.tabletsWHEREenable_mowtrueGROUPBYtable_nameHAVINGbitmap_mb1024;自动应急脚本阈值触发自动下发COMPACT450版本紧急干预、400提前兜底六、MOR vs MOW 风险对照表选型直接抄业务场景MOR风险MOW风险最优选择批量导入少量更新低低按需选均匀高频更新中高MOR热点单行扣减/秒杀极高爆炸级坚决MOR攒批大底零散小改中高MOR兜底多表并发更新中高冷热分离读多写少、强实时中低MOW七、企业级混合架构终极落地热数据高频改→ MOR表抗写入 冷数据查为主→ MOW表保性能 定时离线同步MOR冷数据归档至MOW既扛住写入不炸版本又保住查询极致性能八、上线Checklist规避99%版本爆炸✅ 建表分区打散热点合理分桶✅ 写入禁用单行UPDATE全量攒批StreamLoad✅ 配置拉高MOW专属线程、降低合并触发阈值✅ 监控版本400告警、Bitmap1G告警✅ 运维低峰期定时Full Compaction❌ 严禁热点主键直更、零碎小更新裸跑MOW

更多文章