hero
深度解读 · OLAP 运维

ClickHouse 集群运维实战指南

五年 PB 级集群经验的完整拆解 —— 搭集群容易,让它持续活着难。本文专讲你会踩的坑,以及如何提前避开。

基于 Tinybird 联合创始人 Javi Santana 原文(Part I / Part II)· 融合 HN 74 条讨论与扩展资料核实
ClickHouse 没有优化器,你就是优化器
ClickHouse 没有护栏,你就是护栏
SECTION 01

原文整体框架

作者从 18.4 版本开始运维 ClickHouse 近 6 年,管理多个 PB 级集群,还给上游提交过备份引擎、JOIN 性能优化、分布式 JOIN 等关键代码。文章立场鲜明:不讲优点,专讲问题。

章节核心命题
Architecture副本+分片模型;负载均衡器是核心;写副本与读副本分离(compute-compute separation)
Storage开源版云存储支持薄弱;zero-copy 复制可用但有数据丢失风险;压缩用 ZSTD(1)
Upgrades利用复制协议向后兼容做滚动升级;四步验证体系;把升级做成 CI/CD
Config & Testing跟踪 settings 变更;必须读源码;测试要跑真集群(≥2 节点 + Keeper)
Costs成本公式拆解;32 核 ≈ 5GB/s 的经验法则;人力配置建议
Ingestion全文最重章节:parts/merges 平衡艺术;背压;防重复、防 OOM、防 too many parts
Part II负载隔离、查询四规则、MV 回填 Null 表法、system 表监控、副本下线多步流程
SECTION 02

架构:副本、分片与负载均衡

App BI 工具 ×2 cli / 分析师 写入管道 负载均衡器 负载 / 缓存亲和 一致性哈希路由 写副本(专用) compute-compute 分离 写流量直达 读副本 A(实时) 读副本 B(实时) 读副本 C(长查询) Keeper ×3 独立机器部署 复制协调 / DDL 队列 ZK 病 = 全集群病
作者推荐的集群拓扑:LB 感知后端状态做智能路由;写副本隔离并配 failover;Keeper 独立部署;读副本按负载类型分组。

实战路径

  1. 起步不分片,只加副本:垂直扩容扛大查询,加副本扛流量。分片最大的坑是 re-sharding 极其痛苦,靠聪明的 schema 设计可以长期推迟。
  2. 前置带业务逻辑的 HTTP LB:按负载、副本类型、一致性哈希(利用缓存命中)路由。基础部署不必这么复杂,但写副本隔离建议第一天就做。
  3. 按工作负载隔离副本:有 p99 要求的查询单独隔离一组副本,负载压在 40% 以下,高百分位延迟才稳。实时亚秒查询是昂贵的,别幻想 spot 实例。

成本放大的算术题

300TB 的表扛 1000 QPS,单副本扛 100 QPS → 需 10 副本 → 3000TB 存储。用 SSD 的话账单会教你做人——这是作者倾向存算分离的根本动机。

用户 solatic 质疑作者用 HTTP 而非原生 TCP:低延迟是 ClickHouse 核心目标,持久 TCP 免握手更契合。前 Tinybird 员工 eclbg 澄清:他们用 HTTP keep-alive 长连接,开销问题已解决
选哪个协议不是关键,连接复用才是。HTTP 的生态(LB、探针、限流、标准超时语义)对运维更友好。
SECTION 03

存储:存算分离的现实与妥协

热/冷存储分层示意
热/冷分层:热数据驻留本地 SSD 保延迟,冷数据下沉 S3 控成本
存储形态机制风险
本地磁盘副本间互拷(Keeper 协调)成本高,扩缩容不灵活
S3 各存一份每副本独立写 S3写操作费爆炸(CH 为本地盘设计,文件多、写频繁)
zero-copy 复制副本共享 S3 同一批 part官方明确不支持生产

zero-copy 的真相(扩展核实)

Zero-copy replication is a poorly designed feature; it barely works. We don't expect it will be ready for production usage. It will be left in an experimental stage for a long time, maybe several years.—— ClickHouse 创始人 Alexey Milovidov,GitHub issue #49383,2023-05

2026 年的 HN 讨论证实这一态势延续至今:社区认为官方在开源版刻意"看门" zero-copy,因为它是 ClickHouse Cloud 商业模式的核心;也有人为之辩护——这正是"别人出钱让你免费用其余全部功能"的合理对价。

作者的分层建议

solatic 的反向观点值得认真回答:ClickHouse 的低延迟优势恰恰来自 NVMe 本地盘。如果数据都要放对象存储、接受多跳网络延迟,为什么不直接用 Snowflake 这类天生存算分离的 OLAP?——这是选型层面必须先想清楚的问题。
SECTION 04

升级:从 3 小时战战兢兢到 CI/CD 全自动

作者团队第一次升级:2 周准备 + 3 小时操作。四年后做到无停机、无性能降级、纳入 CI/CD——"据我所知没有第二家公司做到"。前提是复制协议向后兼容

滚动升级 SOP(可直接照抄)

  1. 用新版本加一个副本进集群
  2. 盯日志——ClickHouse 爱打看似 critical 实则无害的日志,头几次会吓出心脏病
  3. 升级期间避免 DDL(加列、跳数索引等结构变更)
  4. 全集群升完前,不用新版本任何新特性
  5. 先导一部分流量,看延迟 / 内存 / 资源
  6. 再测
  7. 切真实流量;不放心就观察几小时
  8. 逐个升级其余副本

你会遇到的四类问题

问题频率对策
存储格式不兼容2 年 2-3 次(多租户全数据类型才易撞上)发布后至少等 1 个月再升;出事就钻文件系统或恢复备份
SQL 行为变化常见(多由 bugfix 引起)system.query_log 回放真实查询做回归
性能回退偶发升级前跑性能基准,备好回滚
Settings 变更每次都有版本间 settings diff 自动化;CI 指向 master 构建

验证体系(进阶目标)

升级能力靠肌肉记忆 —— through repetition, suffering, and recovery.—— 原文
SECTION 05

配置与测试

跟踪 settings 变更

在源码里盯默认值与 flag 翻转,做自动化 diff。changelog 是入口,但自己的 diff 系统才可靠。

📖

必须读源码

作者管过几百个 Postgres 集群都不需要读源码,ClickHouse 不行。头部用户几乎都有上游 patch。

3

CI 三版本策略

生产版本 + master + 目标升级版本,全量跑你的分析查询。Because you are testing your analytics queries, right?

绝不测单实例

至少 2 节点 + Keeper。单实例测不出建表失败、复制延迟等集群特有问题。

SECTION 06

成本与人

成本清单 = 副本机器费 + ≥3 个独立部署的 Keeper 节点 + 存储(本地盘不能缩容;S3 要算操作费)+ HA 负载均衡器一对 + 备份存储。

Keeper 必须独立部署:与 DB 同机时机器过载 → Keeper 变慢 → 表进入只读(最好情况)。原文:"A ZK problem means you are fucked."

经验法则

针对"需要全职盯查询",zbentley 指出这其实是旧时代 DBA 文化的价值——不是写查询,而是做查询与 schema 的守门人/限流器。cogman10 给出平衡方案:DBA 不做 gatekeeper 做 guardian——DB 出问题的第一道防线,简单问题直接修,复杂问题与开发结对,查询仍由开发写但有专家兜底。
不必复刻专职 DBA,但必须有人对查询质量和表设计有否决权——否则 ClickHouse 会以 OOM 的方式行使否决权。
SECTION 07 · 全文精华

写入(Ingestion)

Every single company handling ClickHouse struggles with ingestion. They lose data (most of the time without knowing it), duplicate data, take the database down…—— 原文。HN 上 PostHog 前员工 yakkomajuri:"读到 too many parts 我 PTSD 都犯了";同事 fuziontech:"Slack 上天天都是 CH 需要更多磁盘来追 merge。"
parts merge 示意
insert → 小 part → 后台 merge → 大 part:读要快就要少而大的 part,但大 part 的 merge 吃 CPU/内存/时间

写入是五件事的平衡艺术:merges、inserts、reads、mutations、表设计

典型事故链

大查询吃光 CPU/IO insert 堆积 内存上涨 OOM 丢数据(且不自知)
ClickHouse 运维的标准事故链——每一环都有监控点可切断

可操作清单

  1. 攒批 insert,每次目标只产生 1 个 part;按分区攒批、不同分区不同 flush 节奏(别每秒刷新上月分区)。
  2. 只有真正需要的表才高频写。
  3. 用 S3 务必开 Compact parts——省大量写操作费,避免撞 S3 限流;不用 S3 也建议开。
  4. 配一块"小热盘":写入和首轮 merge 在本地盘完成再下沉 S3,省大量操作费。
  5. 调 max part size 减少多余 merge,但别让 part 过大。
  6. 分区键设计教育:一天一分区却一次灌 3 年数据 = 一次 insert 产生上千 part = too many parts。
  7. MV 要管控:MV 在 insert 时执行,每个都拖慢写入;写得差的 MV 直接 OOM 服务器(Tinybird 有自动熔断系统)。
  8. merge 追不上 insert → 加大 merge 线程池 / 写入背压 / 调大队列上限。

背压与防重复

solatic:列存数据库的本质权衡就是"写入慢/异步换查询快",Kafka 缓冲天然契合;若业务要求写入立即可见,那是选型问题而非运维问题。背压不是补丁,是架构的固有部分。
SECTION 08

读负载与查询设计

If you see any benchmark that only gives read performance, it may look nice in the benchmark, but that's not true in real life.—— 原文:读的快慢取决于 part 数量,part 数量取决于写入。纯读 benchmark 都是骗人的。

四类流量与隔离

实时查询(喂 dashboard)/ 长查询(临时分析)/ 回填 / 其他非实时。核心矛盾:长查询不能拖垮实时查询的 p95+。方案:按负载分副本(贵)、应用层 + LB 智能路由(Tinybird:感知副本状态选路,必要时直接拒绝查询保服务器)、spot 副本跑长查询。查询入口至少 4 个:app、cli、BI、以及 BI(故意的——BI 生成的查询烂到要数两遍)。

关键 settings

参数建议
max_threads实时查询设 1;高 QPS 下 >2 会吃掉大量硬件。瓶颈通常是扫描速度
max_memory大 GROUP BY / JOIN / 窗口函数要给高
max_bytes_before_external_group_by要速度就给高,避免中间结果落盘毁性能
max_concurrent_queries千万别关——服务器过载时的救命稻草

查询设计四规则(背下来就是 p95 用户)

① sorting key 列过滤烂设计 = 慢 10-100 倍 ② PREWHERE 小列高选择性 · 非 String/Array ③ IN 等排除操作把数据滤出去 ④ JOIN / GROUP BY重操作永远放最后
作者提示:把这四条规则 + 表 schema 喂给任意 LLM,它就能写出不错的查询
OOM 后重启加载可能要几分钟——HA 设计必须把这个恢复时间算进去。
SECTION 09

回填:MV 回填的唯一正确姿势

场景:表在持续写入,表上有 MV,你加了一个新 MV 要回填历史数据。

历史数据 SELECT大 block 批量 Null 表不存数据 · 只触发 MV 新物化视图与在线写入同一条链路 目标表不丢不重
Null 表回填法:回填数据与在线写入走同一条 MV 链路,天然规避不一致
SECTION 10

监控与日常运维

必监控指标(常规 CPU/内存/IO 之外)

指标为什么
运行中查询数负载基线
Keeper 延迟Keeper 病则全集群病
S3 错误用了就要盯
复制延迟加副本、追数据时关键
DDL 队列长度卡住会阻塞全集群 DDL
Merge 队列暴涨 = 写入/merge 失衡前兆
Merge 内存 / MutationsOOM 预警;容易卡死且 kill 不总是有效

关键错误告警:表只读("撞到这个基本就完蛋了")、Keeper 错误、max_simultaneous_queries 触顶、segfault(必须定位到具体查询,否则集群被同一条查询反复打挂,你一直 on-call)。

system 表:救火时的圣经

副本下线多步流程 & ON CLUSTER 陷阱

正确顺序:先摘流量 → 等写入和查询完成 → 必要时 kill → 再关机。有副本宕机时 ON CLUSTER 操作会拖很久,应用超时没设好会连环出错——"trust me, they're not"。超低延迟场景(<20ms)要预热缓存,盯 mark_cache 内存。

作者强烈推荐 Altinity 知识库:自建 ClickHouse 最好的资料库,没有之一。
SECTION 11

杂项但致命

🛡

删表保护

默认 >50GB 的表禁止 DROP——别手贱调低。世界上只有两种数据工程师:误删过表的,和将要误删的。

MV 是杀手级特性也是杀手

难管理、内存炸弹、产生大量 part,每个新 MV 都拖慢写入。

高危列类型

高基数列(>2 亿)上的 uniqExactState,merge 时能杀掉集群。聚合状态列都要小心。

128

index_granularity ≥ 128

低值只对点查有用,否则会杀掉服务器。

选型横向定位(来自 HN a34729t)

SECTION 12

一页纸行动清单

🚀 上线前

  • 写副本隔离 + failover 方案
  • Keeper ≥3 节点独立部署
  • 背压队列(Kafka 或自研)——没有它别上生产
  • 表分区键评审 + sorting key 设计评审
  • CI 跑 ≥2 节点 + Keeper 集群测试,覆盖生产版 / master / 目标版

✍️ 写入侧

  • 攒批 insert,每 insert ≈ 1 part,按分区攒批
  • 用 S3 必开 Compact parts;考虑热盘缓冲
  • MV 数量与内存预算管控,异常 MV 自动熔断
  • 幂等重试设计,防重复数据

🔍 查询侧

  • 实时查询 max_threads=1,负载压 40% 以下保 p99
  • max_concurrent_queries 必须设置
  • 四规则查询评审(sorting key → PREWHERE → IN → 重算子)

⬆️ 升级与变更

  • 滚动升级 SOP:新版本加副本 → 读 → 写 → 切流 → 逐个升
  • 新版本发布等 ≥1 个月;升级期冻结 DDL
  • 用 system.query_log 回放真实查询做回归
  • settings diff 自动化

📡 监控

  • merge 队列、复制延迟、DDL 队列、Keeper 延迟、只读表、segfault 告警
  • 团队熟读 system.query_log / processes / part_log
  • 副本下线走多步流程(摘流 → 等 → kill → 关机)

🆘 保命

  • 保留 50GB 删表保护
  • 备份 + 恢复演练(备份没恢复过等于没有)
  • index_granularity ≥ 128;警惕高基数聚合状态列

附:原文没有但值得补的三点

  1. ClickHouse Keeper 替代 ZooKeeper:新部署直接用 Keeper(C++ 实现、与 CH 同栈、运维负担更小),监控口径一致。
  2. async inserts:作者评价"pretty limited",但写入端没有队列时至少打开它(async_insert=1 + wait_for_async_insert 权衡),是最简单的服务端攒批缓冲。
  3. 官方态度风险:zero-copy 被创始人定性"数年内不会生产可用",社区认为官方在保护 Cloud 商业模式。架构押在 zero-copy 上要有 Plan B(热/冷分层随时可退)。
文中成本数字(300TB×10 副本、500MB/s 扫描等)是作者 2025 年的经验值,硬件与版本演进后建议当数量级参考,而非精确值。