1.场景
pod 部署的 clickhouse ,使用 local-path-storage 存储,然后 local-path-storage 配置中的目录是挂载的 cephfs 的,有次 cephfs 出问题后,挂载点坏了,重新挂载的。结果 clickhouse 启动不了了。
2.clickhouse 启动报错
登录 clickhouse pod 看报错日志
先执行 kubectl logs -f -l app=clickhouse 查看报错日志文件名字
bash
kubectl logs -f -l app=clickhouse
Processing configuration file '/etc/clickhouse-server/config.xml'.
Merging configuration file '/etc/clickhouse-server/config.d/config.xml'.
Logging trace to /var/log/clickhouse-server/clickhouse-server.log
Logging errors to /var/log/clickhouse-server/clickhouse-server.err.log
Processing configuration file '/etc/clickhouse-server/config.xml'.
Merging configuration file '/etc/clickhouse-server/config.d/config.xml'.
Saved preprocessed configuration to '/var/lib/clickhouse/preprocessed_configs/config.xml'.
Processing configuration file '/etc/clickhouse-server/users.xml'.
Merging configuration file '/etc/clickhouse-server/users.d/users.xml'.
Saved preprocessed configuration to '/var/lib/clickhouse/preprocessed_configs/users.xml'.
kubectl exec -it (kubectl get pod -l app=clickhouse\|awk '{print 1}'|grep -v NAME) -- cat /var/log/clickh
ouse-server/clickhouse-server.err.log
bash
kubectl exec -it $(kubectl get pod -l app=clickhouse|awk '{print $1}'|grep -v NAME) -- cat /var/log/clickh
ouse-server/clickhouse-server.err.log
2026.06.05 09:25:01.416635 [ 1 ] {} <Warning> Application: Calculated checksum of the binary: 38FD0E3944230CBD1E0C1028A9D68C83. There is no infor
mation about the reference checksum.
2026.06.05 09:25:08.396351 [ 120 ] {} <Error> auto DB::MergeTreeData::loadDataPartsFromDisk(DB::MergeTreeData::DataPartsVector &, DB::MergeTreeDa
ta::DataPartsVector &, ThreadPool &, size_t, std::queue<std::vector<std::pair<String, DiskPtr>>> &, bool, const DB::MergeTreeSettingsPtr &)::(ano
nymous class)::operator()(const DB::String &, const DB::DiskPtr &) const: Code: 27. DB::ParsingException: Cannot parse input: expected 'columns f
ormat version: 1\n' at end of stream. (CANNOT_PARSE_INPUT_ASSERTION_FAILED), Stack trace (when copying this message, always include the lines bel
ow):
0. DB::Exception::Exception(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, bool) @ 0xa82d07a
in /usr/bin/clickhouse
1. DB::throwAtAssertionFailed(char const*, DB::ReadBuffer&) @ 0xa8851ed in /usr/bin/clickhouse
2. DB::NamesAndTypesList::readText(DB::ReadBuffer&) @ 0x12faf20c in /usr/bin/clickhouse
3. DB::IMergeTreeDataPart::loadColumns(bool) @ 0x141ee92e in /usr/bin/clickhouse
4. DB::IMergeTreeDataPart::loadColumnsChecksumsIndexes(bool, bool) @ 0x141edcca in /usr/bin/clickhouse
5. ? @ 0x142e4f82 in /usr/bin/clickhouse
6. ThreadPoolImpl<ThreadFromGlobalPool>::worker(std::__1::__list_iterator<ThreadFromGlobalPool, void*>) @ 0xa8720aa in /usr/bin/clickhouse
7. ThreadFromGlobalPool::ThreadFromGlobalPool<void ThreadPoolImpl<ThreadFromGlobalPool>::scheduleImpl<void>(std::__1::function<void ()>, int, std
::__1::optional<unsigned long>)::'lambda0'()>(void&&, void ThreadPoolImpl<ThreadFromGlobalPool>::scheduleImpl<void>(std::__1::function<void ()>,
int, std::__1::optional<unsigned long>)::'lambda0'()&&...)::'lambda'()::operator()() @ 0xa873ec4 in /usr/bin/clickhouse
8. ThreadPoolImpl<std::__1::thread>::worker(std::__1::__list_iterator<std::__1::thread, void*>) @ 0xa86f4b7 in /usr/bin/clickhouse
9. ? @ 0xa872ebd in /usr/bin/clickhouse
10. ? @ 0x7f9b24fdf609 in ?
11. __clone @ 0x7f9b24f06293 in ?
(version 22.1.3.7 (official build))
2026.06.05 09:25:08.397284 [ 120 ] {} <Error> openbayes_serving.metrics (c661055c-e52a-4314-bc6e-106165fd0d1b): Detaching broken part /var/lib/cl
ickhouse/store/c66/c661055c-e52a-4314-bc6e-106165fd0d1b/20260605_876893917_876893917_0 (size: 0.00 B). If it happened after update, it is likely
because of backward incompability. You need to resolve this manually
2026.06.05 09:25:08.440865 [ 120 ] {} <Error> auto DB::MergeTreeData::loadDataPartsFromDisk(DB::MergeTreeData::DataPartsVector &, DB::MergeTreeDa
ta::DataPartsVector &, ThreadPool &, size_t, std::queue<std::vector<std::pair<String, DiskPtr>>> &, bool, const DB::MergeTreeSettingsPtr &)::(ano
nymous class)::operator()(const DB::String &, const DB::DiskPtr &) const: Code: 27. DB::ParsingException: Cannot parse input: expected 'columns f
ormat version: 1\n' at end of stream. (CANNOT_PARSE_INPUT_ASSERTION_FAILED), Stack trace (when copying this message, always include the lines bel
ow):
0. DB::Exception::Exception(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, bool) @ 0xa82d07a
in /usr/bin/clickhouse 我的场景是使用 local-path-storage
3.解决
看起来在实例上遇到了损坏的数据分片。出问题的是 openbayes_serving.metrics 表(UUID 为 c661055c...):数据分片 20260605_876893917_876893917_0 的大小为 0.00 B,导致 ClickHouse 无法解析列格式。
你提到你的部署场景使用了 local-path-storage (通常是 Kubernetes 中的 Rancher Local Path Provisioner),这直接解释了为什么会接连出现这种 0 字节的损坏分片。
💡 根本原因:为什么 local-path-storage 会导致这个问题?
local-path-storage 本质上是借用 Kubernetes 宿主机的本地目录(如 /opt/local-path-provisioner)通过 HostPath 挂载给 ClickHouse Pod 使用。
在 Kubernetes 环境下,出现这种 0.00 B 坏分片,通常是因为 Pod 被不优雅地强杀(Non-graceful Shutdown) 或 节点发生过驱逐/重启:
-
未等待落盘: ClickHouse 收到写入请求,刚在磁盘上创建了分片目录和空的
columns.txt,还没来得及将内存中的数据和元数据刷入(fsync)本地磁盘。 -
Pod 突然死亡: 此时 Kubernetes 触发了 Pod 重启(可能是因为 OOMKilled、Liveness 探针失败、或者节点维护调度)。
-
留下烂摊子:
local-path-storage没有 底层的分布式副本保护(不像 Ceph 或云盘有日志恢复机制),它只是纯粹的本地文件系统。Pod 重启回来后,原样读取这个空文件,立刻抛出CANNOT_PARSE_INPUT_ASSERTION_FAILED。
🛠️ 解决方案(针对 Kubernetes / local-path-storage 场景)
你可以延续之前的逻辑来修复当前报错,但在 Kubernetes 环境下有更直观的操作方式。
第一步:让 Pod 正常启动(跳过坏分片)
由于你可能无法直接进 Pod 执行 ALTER TABLE 修复(因为 Pod 此时可能在不断崩溃重启:CrashLoopBackOff),建议使用 方法 1(创建 flag 文件) 强制其启动:
-
找到该 Pod 所在的 宿主机(Node)。
-
并在宿主机上找到
local-path-storage对应的实际物理路径。路径通常长这样:/opt/local-path-provisioner/pvc-<pvc_uuid>_openbayes_clickhouse-pvc/... -
在该物理路径的
flags目录下,直接在宿主机上创建一个force_restore_data空文件。例如:Bash
sudo touch /opt/local-path-provisioner/pvc-xxxx_clickhouse/flags/force_restore_data sudo chown 101:101 /opt/local-path-provisioner/pvc-xxxx_clickhouse/flags/force_restore_data # 确保权限属于 clickhouse 用户 -
只要有了这个 flag 文件,Kubernetes 再次拉起 ClickHouse Pod 时,它就会自动把这个
0.00 B的坏分片移到detached目录,并顺利完成启动。
⚠️ 长期预防:如何防止在 local-path-storage 上再次发生此问题?
如果你的 Kubernetes 环境中经常发生这种重启,建议对 ClickHouse 的 Deployment / StatefulSet 进行以下优化:
1. 延长 Pod 的优雅终止时间 (terminationGracePeriodSeconds)
默认情况下,Kubernetes 给 Pod 的关闭缓冲时间只有 30 秒。如果 ClickHouse 正在合并大量数据,30 秒内无法安全退出,K8s 就会发送 SIGKILL 强杀进程,导致产生 0.00 B 坏文件。
在 StatefulSet 的 spec 中将其调大(例如 60 或 120 秒):
spec:
terminationGracePeriodSeconds: 120
2. 检查 Pod 退出原因(防范 OOM)
使用以下命令检查上次 Pod 死亡的原因:
kubectl describe pod <clickhouse-pod-name> -n <namespace>
如果看到 Last State: Terminated 原因是 OOMKilled,说明你给 ClickHouse 限制的内存(Limits)太小了。ClickHouse 在内存不足时被系统瞬间强杀,极易导致 local-path 写入中断。请加大其资源配额(Resources Requests/Limits)。
3. 开启 fsync 积极写入(权衡性能与安全)
可以在 ClickHouse 的 config.xml 中(通过 ConfigMap 挂载),调整 MergeTree 的设置,让其更频繁地将元数据刷入磁盘,减少断电或强杀带来的文件零字节风险。
4.验证
bash
ls -l store
/c66/c661055c-e52a-4314-bc6e-106165fd0d1b/detached/|grep 20260605_876893917_876893917_0
drwxr-x--- 2 systemd-network systemd-journal 10 Jun 5 17:36 broken-on-start_20260605_876893917_876893917_0
4.只要有了这个 flag 文件,Kubernetes 再次拉起 ClickHouse Pod 时,它就会自动把这个 0.00 B 的坏分片移到 detached 目录,并顺利完成启动。 原理是什么,为什么必须有那个文件
这是一个非常硬核且优秀的底层问题。要理解为什么 ClickHouse 不直接自动处理,而是非要塞一个 force_restore_data 标记文件,我们需要从数据库的数据完整性设计(Data Integrity)和防灾策略说起。
简单来说,这是 ClickHouse 官方为了防止"二次灾难"(即自动化盲目修复导致更严重的数据丢失)而故意设计的最高级别安全防线。
核心原理:ClickHouse 启动时的"过载保护"
ClickHouse 每次启动时,后台线程都会遍历所有磁盘上的数据表目录,对每个数据分片(Part)进行健康检查。
-
检查元数据: 它会去读取分片里的
columns.txt(列信息)、checksums.txt(校验和)等核心文件。 -
触发断言失败: 当它读取到你的
0.00 B文件时,代码预期能读到格式版本号(如columns format version: 1),结果却直接读到了文件末尾(EOF)。这就触发了源码中的断言崩溃throwAtAssertionFailed。 -
安全停机(Panic): 此时,ClickHouse 的第一反应不是"顺手帮管理员把这个坏分片删了/移走 ",而是"天哪,底层存储可能出大事了(比如文件系统损坏、RAID卡故障、磁盘丢数据),我必须立刻自我崩溃,防止继续写入导致其他好的数据也被覆盖或损坏!"
在没有任何干预的情况下,ClickHouse 会选择死给你看(拒绝启动),把决定权交给运维人员。
为什么必须要有 force_restore_data 这个文件?
这个文件本质上是管理员签发给数据库的一张"免责声明"或确认函。
1. 显式的人工确认(Explicit Acknowledgment)
数据库作为存储核心,最忌讳的就是"擅自做主"。
-
如果 ClickHouse 默认"发现坏分片就自动移到
detached目录",假设某天你的服务器磁盘出现了严重的硬件故障,导致几百个分区的数据全部变成了 0 字节。 -
如果没有这个限制,ClickHouse 启动时会自动把这几百个分区全部丢进
detached,然后假装没事一样成功启动。 -
此时,应用层继续往里写新数据,用户会误以为系统一切正常,而实际上过去几年的历史数据已经全部无声无息地丢失了。
-
这就是灾难性的"静默数据丢失"。
因此,ClickHouse 逼迫运维人员必须去宿主机上手动 创建一个叫 force_restore_data 的文件。这就意味着:"我(管理员)已经知道数据损坏了,我知道这可能会丢失数据,我同意你(ClickHouse)强行忽略它并启动。"
2. 状态机的一击确认
ClickHouse 的处理逻辑非常严密:
ClickHouse 启动
└── 扫描到损坏分片
└── 检查 flags/force_restore_data 是否存在?
├── 存在 -> 把坏分片移到 detached 目录,删除 flags/force_restore_data,继续启动。
└── 不存在 -> 打印 Error 日志,直接退出进程(CrashLoop)。
它在读取到这个文件并完成坏分片的转移后,会立刻把这个文件在磁盘上删掉。这意味着这个"免责声明"是一次性的。如果下次启动又遇到了新的坏分片,它依然会死掉,等待你再次确认。
总结
你遇到的现象,原理上可以用一句话概括:
ClickHouse 宁可选择"不服务"(崩溃无法启动),也绝不盲目选择"数据不安全"(自动丢弃它认为可能有数据的分片)。
而 force_restore_data 就是你身为管理员,跨越 Kubernetes 容器边界,向 ClickHouse 引擎下达的最高级别强行启动指令。