20260603.记一次 Doris FE "幽灵卡死"引发的惨案:从表象到真凶的追凶实录
作为大数据团队的打工人,最怕的不是系统报错,而是系统"不报错,直接死给你看"。最近,我们负责的 Apache Doris 集群就患上了一种让人抓狂的"月经病"------每隔一两个月,Doris FE(Frontend)就会在深夜毫无征兆地彻底卡死,指令下发不了,集群形同瘫痪。
本以为这是个隐藏极深的底层 Bug,没想到一番抽丝剥茧后,却牵扯出了我们对 CBO(基于成本的优化器)和后台任务调度的深刻教训。
第一幕:案发现场的迷雾
案发当晚 23:55,报警群炸锅。Doris 集群再次陷入"假死"状态。
- • 现象描述: 所有的 DDL(如建表、修改表)和查询指令被堵在门外,BE(Backend)仿佛失联,系统完全停止响应。
- • 初步怀疑:GC(垃圾回收)灾难。
这通常是第一反应。毕竟 Java 写的系统,遇到这种大面积的 STW(Stop-The-World),大概率是堆内存被打爆了。于是,我立刻捞出了当时的fe.gc.log。
然而,日志给了我结结实实的一巴掌:
text
[2026-06-03T23:00:34.052+0800] GC(23091) Pause Young (Mixed) (G1 Evacuation Pause) 2123M->1663M(12288M) 119.895ms
堆内存健康得不能再健康了!在 12GB 的总内存中,回收后常态只占用 1.6GB,完全没有 OOM 的迹象,停顿时间也不过是一百多毫秒。GC 灾难的嫌疑被彻底洗清。
- • 再次怀疑:NFS 磁盘 I/O 阻塞。
因为我们的doris-meta挂载在阿里云的 NFS 上。根据经验,NFS 偶尔的网络抖动会导致fsync刷盘极慢,一旦主线程刷盘被卡,所有的心跳和元数据操作都会排队,这也能造成"假死"。
我满怀信心地去 grepfe.warn.log寻找fsync相关的超时报错,结果------干干净净,没有一条相关日志。
这也不对,那也不对,究竟是谁捂住了 Doris 的嘴?
第二幕:剥开伪装,直指真凶
既然底层资源没问题,那就一定是在业务层面出了幺蛾子。我决定硬啃 fe.warn.log 中杂乱的报错信息,终于,几条被淹没的关键日志浮出水面。
线索一:RPC 队列被打爆
text
2026-06-03 23:26:43,742 WARN (thrift-server-pool-7|10404) [ReportHandler.putToQueue():228] the report queue size exceeds the limit: 100. current: 101
铁证如山!FE 用来接收 BE 心跳和报告的 thrift-server-pool 队列(默认大小 100)被完全打满了。
一旦这个队列溢出,BE 发送的心跳 FE 收不到,FE 自然认为 BE 失联,进而引发大面积瘫痪。
线索二:漫长的"毒瘤"查询被强杀
text
[ConnectContext.checkTimeout():995] kill query timeout, remote: ... query timeout: 900000...
大量的查询跑了整整 15 分钟(900000 毫秒)才被系统强杀。这说明在卡死前,BE 正在负重前行,处理着极其沉重的烂 SQL。
但问题来了,我去 BE 看了一圈,CPU 和内存波澜不惊,更离谱的是,fe.audit.log 里根本查不到这几条 15 分钟超时的 SQL!
线索三:疯狂的后台 Analyze 任务
text
[BaseAnalysisTask.runQuery():313] Failed to execute sql SELECT CONCAT... SUM(t1.count) * COUNT(t1.column_key)... FROM amzn_cleaned_report_reserved_inventory_bak...
就在系统濒临崩溃的 23:55,Doris 居然在后台疯狂生成几十组重度的 SELECT ... GROUP BY 聚合查询,对我们的千万级核心表执行全表扫描!
破案了!这是一场由并发风暴和元数据锁争用引发的惨案。
原来,那些 15 分钟超时的查询,根本连物理执行计划都没下发到 BE,它们死在了 FE 的"娘胎"里 。由于瞬间爆发的几十个 Auto Analyze 任务疯狂抢占 FE 的元数据锁,导致正常的业务查询连"读锁"都拿不到,只能在 FE 内部傻等。与此同时,锁竞争导致处理 BE 心跳的线程也被阻塞,最终 report queue 被塞满,系统彻底失联。
第三幕:自救与根除
找到原因后,我们要解决的就是"如何消除并发风暴"。
起初,为了缓解队列打爆的问题,我打算修改 fe.conf:
properties
report_queue_size = 300thrift_server_max_worker_threads = 8192
但这只是"治标",扩大等位区确实能让系统在遭遇并发尖刺时喘口气,不至于立刻死掉,但治不了产生尖刺的根。
真正的根源在于 Auto Analyze(自动统计信息收集)。
为了避免 Analyze 影响业务,我第一反应是限定它的执行时间,比如只在凌晨 3 点到下午 2 点跑:
sql
SET GLOBAL auto_analyze_start_time = "03:00:00"; SET GLOBAL auto_analyze_end_time = "14:00:00";
险些酿成大祸!
仔细复盘了我们的调度系统后,我惊出了一身冷汗:凌晨 1 点到 5 点,正是我们第三方海外仓链路(Scan -> ETL -> 清洗)和 ERP 补写数据最疯狂的时刻。
Doris 的 Auto Analyze 是由"表数据变更量超过阈值"触发的。如果允许在这段期间自动收集,写入大军刚越过阈值,系统就会立刻拉起成百上千个 Analyze 任务,直接和写入任务"撞车",那系统绝对会从"一个月死一次"变成"天天死"!
最终的治本方案,是加上并发限制的"紧箍咒":
sql
-- 强制限制后台统计分析的并发数SET GLOBAL auto_analyze_task_num = 2;
这一招"釜底抽薪",直接斩断了并发风暴的可能。无论后台积压了多少表需要统计,同一时刻只允许 2 个任务在跑,FE 门前变成了有序的排队叫号。
第四幕:反思与总结,为无知买单
复盘这次惊魂事件,最大的教训不是参数配错了,而是我们对现代数据库 CBO(Cost-Based Optimizer)优化器及其配套机制的理解过于粗浅。
在传统的认知里,数据库只要建好索引、配好内存就能跑。但现代的 OLAP 引擎(如 Doris),由于要在几百亿数据中快速选择是用 Hash Join 还是 Broadcast Join,极度依赖于底层的统计信息(NDV、Min/Max、Null Count)。
这也就是 Auto Analyze 存在的意义------它是不知疲倦的"清道夫",默默地为优化器收集情报。
我们犯了三个致命错误:
所幸,惊吓过后,集群重归宁静。通过这次追凶实录,不仅彻底治好了 Doris FE 的"幽灵假死",也让我明白:驾驭复杂系统,不仅要会看 GC 和 I/O,更要理解隐藏在背后的调度机制和优化哲学。敬畏每一行默认配置,才能少背一点锅。