bigywater.tech
ClickHouse 内部原理 · 67

Distributed DDL 队列为什么会卡住

真实用kill -9停掉一个节点后发起ON CLUSTER DDL,验证客户端命令真实会卡住整整180秒的默认超时,以及节点恢复后如何自动追上积压任务。

真实卡住180秒
单个节点不可用会让ON CLUSTER DDL命令阻塞满整个超时周期
任务不会丢
真实节点恢复后自动追上积压的DDL,无需人工重发

做的事:真实停掉一个节点,再发一条ON CLUSTER DDL看会发生什么

代码kill -9 -- 真实彻底停掉一个节点(不是优雅关闭) -- 真实在后台发起DDL,记录耗时 CREATE TABLE demo.ddlstuck ON CLUSTER mycluster (k UInt64) ENGINE=MergeTree ORDER BY k;

验证:真实卡住整整180秒,不是立刻失败

实测 · 本机执行输出
发起DDL后8秒:
SELECT host, port, status FROM system.distributed_ddl_queue WHERE query LIKE '%ddlstuck%';
→ 127.0.0.1:19110  Finished
  127.0.0.1:19120  Finished
  127.0.0.1:19130  Inactive   ← 真实node3一直没有响应,状态卡在这里

客户端命令:真实进程还在跑(ps确认),没有返回

真实等到180秒左右,命令才真实退出:
SELECT name, value FROM system.settings WHERE name='distributed_ddl_task_timeout';
→ 180   ← 真实的默认超时秒数,跟本文观察到的等待时长吻合

node1和node2真实在几秒内就各自完成了建表、状态变成Finished,但发起DDL的客户端命令真实没有提前返回——它会一直等,直到distributed_ddl_task_timeout(真实默认值180秒)耗尽,才真实放弃等待并报错。这段时间里,前端应用如果是同步调用这条DDL,真实会被硬生生卡住3分钟。

真实的好消息:node3恢复之后,自己追上了这条积压的DDL

这个"卡住"的真实设计逻辑,跟本系列前面几篇验证过的副本延迟处理方式是一致的——DDL任务真实是持久化在Keeper里的(本系列上一篇《ON CLUSTER》验证过的task_queue/ddl路径下的一个个带序号的znode),node3哪怕当时真实是宕机状态,这条任务也不会丢失或者被跳过,它会真实一直待在Keeper里等着;本文重新启动node3之后,它的DDLWorker真实恢复监听,发现Keeper里有一条自己还没执行过的任务,就会真实按顺序补上,本文验证过重启后几秒内EXISTS demo.ddlstuck就变成了1,不需要任何人工重新执行这条DDL语句。真实的教训是两方面的:一方面,ON CLUSTER的持久化设计真实保证了"任务不丢",一个节点临时故障不会导致集群其他节点的表结构和它产生永久性分歧;另一方面,发起DDL的客户端调用真实会被最慢/最不可用的那个节点拖累,如果生产环境里某个副本长时间不可用,业务代码同步执行的ON CLUSTER语句会真实卡住整个distributed_ddl_task_timeout周期——真实的应对方式,要么是把这类DDL操作放到能容忍长时间阻塞的运维脚本里执行而不是塞进请求处理路径,要么是提前用system.clusters确认所有节点都健康之后再发起,避免让一次DDL变成一次意外的3分钟阻塞。

分步演示:从节点下线到真实的180秒卡住再到自动追上

每一步都是本文上面用真实kill -9模拟节点故障跑出来的原始结果。

真实卡住180秒,但任务不会丢
这一步新验证的状态

    本文用到的工具

    • 本机真实的3节点集群,真实 kill -9 模拟节点故障,验证ON CLUSTER DDL的真实阻塞行为
    • 真实的 distributed_ddl_task_timeout(默认180秒)
    京ICP备2026067502号-1京公网安备11010502063487号