ClickHouse 千万级 TPS 实时写入下的 Part 治理:彻底告别 `Too many parts` 写入挂起
2026/9/15 4:34:00 网站建设 项目流程

ClickHouse 千万级 TPS 实时写入下的 Part 治理:彻底告别Too many parts写入挂起

在大促实时监控大盘、千亿级 APM 全链路日志与流式风控场景中,ClickHouse 凭借其强悍的列存写入吞吐成为了事实上的标准选型。

然而,几乎每一个初次将 ClickHouse 推上千万级实时写入高压的架构师,都会在某天夜里被同一个臭名昭著的崩溃异常所击溃:

DB::Exception: Too many parts in all data parts in table t_order_log (300). Merges are processing significantly slower than inserts, parts are being generated at a rate of 120.00 parts per second. (version 24.8.3)

伴随着这个异常,ClickHouse 会无情地启动写入反压与挂起(Write Stall)——直接拒绝任何新的INSERT请求,导致上游 Flink 流处理任务瞬间发生背压雪崩,Kafka 消息大量积压。

为什么 ClickHouse 如此惧怕“频繁小写入”?在千万级 TPS 的持续高压下,我们如何从物理微架构出发,彻底根治 Part 碎片膨胀,实现丝滑平稳的极限实时写入?

[ClickHouse 频繁小写入引发 Too Many Parts 写入挂起机制] 上游应用高频单行 / 小批写入 (例如每秒 1000 次, 每次 50 行) │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 每次 INSERT 都在磁盘生成一个独立的物理 Part 目录! │ │ (每秒产生 1000 个小 Part, 单表累积 Part 数量直奔 300+!) │ └──────────────────────────────┬──────────────────────────────┘ │ 远超后台 Compaction 合并算力极限! ▼ ┌─────────────────────────────────────────────────────────────┐ │ 触碰 parts_to_throw_insert = 300 熔断红线! │ │ ──▶ 【系统强制拒绝所有新写入! 抛出 Too many parts 异常!】 │ └─────────────────────────────────────────────────────────────┘

内核机制:为什么每一次INSERT都会生成一个物理 Part?

ClickHouse 的底层存储引擎是基于LSM-Tree 变体——MergeTree设计的。

ClickHouse 内核中有一个绝对的物理铁律:客户端发起的每一次INSERT INTO ...事务,无论你写入的是 100 万行还是仅仅 1 行,ClickHouse 都会在磁盘上为你创建一个全新的、独立的 Part 目录(Part Folder)!

如果上游应用像对待传统 MySQL 一样,每个线程单条写入数据:

  • 每秒 1000 次写入,就会在磁盘上生成1000 个独立的物理小 Part 目录
  • 每个 Part 都包含所有列的压缩文件(.bin)和稀疏索引标记(.mrk3);
  • 后台后台合并工作池(BackgroundProcessingPool)哪怕拼尽全力进行多路归并,也无法在物理 IO 上追赶如此恐怖的 Part 产生速率;
  • 当单表活跃 Part 数量超过parts_to_delay_insert = 150时,ClickHouse 开始人为增加写入延迟;超过parts_to_throw_insert = 300时,直接抛出异常拒绝写入!
[工业级千万 TPS 写入三阶缓冲治理流水线] 千万级实时事件流 (Kafka 流水) │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 第一阶段: Flink / 消费端大微批攒批 (Micro-Batch Buffering) │ │ - 严格约束: 单批次 >= 50,000 行 或 等待时间 >= 2.0 秒 │ │ - 将写入频率从每秒数万次骤降至每秒 10~20 次大块落盘 │ └──────────────────────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 第二阶段: ClickHouse 内存 Buffer 引擎前置平滑缓冲 │ │ - 写入先落内存 Buffer Table,每隔 5 秒异步大块 Flush 实体表│ └──────────────────────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 第三阶段: MergeTree 后台异步合并算力深度调优 │ │ - 增大 background_pool_size = 32 (释放多核合并算力) │ │ - 调优 max_bytes_to_merge 避免大 Part 阻塞小 Part 快速合并│ └─────────────────────────────────────────────────────────────┘

生产级根治方案一:上游必须执行“大微批攒批(Micro-Batching)”

解决 Part 膨胀的绝对第一法则是在客户端或流计算引擎(Flink / Spark Streaming)中完成数据攒批

// Flink ClickHouse Sink 标准生产攒批配置范式 ClickHouseSink sink = ClickHouseSink.builder() .setBatchSize(50000) // 核心参数: 单批次累积达到 50,000 行才执行一次写入 .setFlushInterval(2000) // 核心参数: 最长等待时间 2000 毫秒 .setMaxRetries(3) .build();
  • 物理效果:原本每秒 5 万次零散写入,被规整为每秒仅发起 1 次包含 50,000 行的大 Block 写入
  • 磁盘每秒仅产生 1 个高质量的大 Part,后台合并线程可以在不到 0.05 秒内轻松完成异步 Compaction,活跃 Part 数量永远稳定在10 到 20 个的极健康水位!

生产级根治方案二:内核合并调度与 Buffer 引擎调优

在 ClickHouse 配置文件(config.xmlusers.xml)中,必须针对高并发写入实例进行内核调优:

<yandex> <!-- 1. 扩大后台数据合并线程池 (释放多核 CPU 算力) --> <background_pool_size>32</background_pool_size> <background_merges_mutations_concurrency_ratio>2</background_merges_mutations_concurrency_ratio> <profiles> <default> <!-- 2. 适当放宽大促写入熔断阈值,避免瞬时毛刺直接抛异常 --> <parts_to_delay_insert>300</parts_to_delay_insert> <parts_to_throw_insert>600</parts_to_throw_insert> <max_delay_to_insert>1</max_delay_to_insert> </default> </profiles> </yandex>

生产实操收益

通过将全站上游写入全面重构为大微批模式并调优后台合并线程池:

  • 在大促全链路压测中,ClickHouse 集群成功抗住了单机每秒 85 万行、全集群超 1500 万 TPS的极限实时写入洪峰;
  • 单表活跃 Part 数量长期稳定在18 个左右,Too many parts异常彻底归零;
  • 实现了大促指挥大盘“千万写入不卡顿、秒级查询毫秒出”的卓越架构表现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询