clickhouse 数据损坏无法启动问题修复

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)节点发生过驱逐/重启

  1. 未等待落盘: ClickHouse 收到写入请求,刚在磁盘上创建了分片目录和空的 columns.txt,还没来得及将内存中的数据和元数据刷入(fsync)本地磁盘。

  2. Pod 突然死亡: 此时 Kubernetes 触发了 Pod 重启(可能是因为 OOMKilled、Liveness 探针失败、或者节点维护调度)。

  3. 留下烂摊子: local-path-storage 没有 底层的分布式副本保护(不像 Ceph 或云盘有日志恢复机制),它只是纯粹的本地文件系统。Pod 重启回来后,原样读取这个空文件,立刻抛出 CANNOT_PARSE_INPUT_ASSERTION_FAILED

🛠️ 解决方案(针对 Kubernetes / local-path-storage 场景)

你可以延续之前的逻辑来修复当前报错,但在 Kubernetes 环境下有更直观的操作方式。

第一步:让 Pod 正常启动(跳过坏分片)

由于你可能无法直接进 Pod 执行 ALTER TABLE 修复(因为 Pod 此时可能在不断崩溃重启:CrashLoopBackOff),建议使用 方法 1(创建 flag 文件) 强制其启动:

  1. 找到该 Pod 所在的 宿主机(Node)

  2. 并在宿主机上找到 local-path-storage 对应的实际物理路径。路径通常长这样:

    /opt/local-path-provisioner/pvc-<pvc_uuid>_openbayes_clickhouse-pvc/...

  3. 在该物理路径的 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 用户
  4. 只要有了这个 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)进行健康检查。

  1. 检查元数据: 它会去读取分片里的 columns.txt(列信息)、checksums.txt(校验和)等核心文件。

  2. 触发断言失败: 当它读取到你的 0.00 B 文件时,代码预期能读到格式版本号(如 columns format version: 1),结果却直接读到了文件末尾(EOF)。这就触发了源码中的断言崩溃 throwAtAssertionFailed

  3. 安全停机(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 引擎下达的最高级别强行启动指令

相关推荐
阿演1 天前
我把这个桌面数据库工具又升级了一轮:现在支持 ClickHouse,还能可视化建表和改表了
数据库·clickhouse·ai编程·数据库连接工具
海南java第二人2 天前
ClickHouse 稀疏索引深度解析:为什么 OLAP 数据库不用 B-Tree?
数据库·clickhouse
海南java第二人2 天前
ClickHouse 主键索引详解:不是唯一标识,而是排序规则
clickhouse
海南java第二人3 天前
ClickHouse 列式存储深度解析:优点、缺点与选型实战
数据库·clickhouse
努力攻坚操作系统4 天前
ClickHouse虚拟列
clickhouse
海南java第二人4 天前
ClickHouse 备份与恢复完全指南:从物理拷贝到内置备份的实战选择
clickhouse·备份与恢复
海南java第二人4 天前
ClickHouse Sharding 分片与 Partitioning 分区:区别、联系与生产实践
clickhouse·分区·分片
狼与自由6 天前
mysql到clickhouse
数据库·mysql·clickhouse
云天AI实战派6 天前
跨境出海全流程实战:用 Medusa + Hyperswitch + ClickHouse 搭建落地页、支付订阅、客服工单与多语言 SEO 闭环
大数据·人工智能·clickhouse·独立开发·跨境出海·medusa