定位:通用原理 + 工程实践。项目内的定量评估(50 万日活各链路 part 速率、阈值推演)见《CK写入TooManyParts风险评估与监控方案》,本文不重复,侧重"为什么"与"怎么写才对"。
摘要
| 项 | 内容 |
|---|---|
| 适用对象 | 使用 ClickHouse(MergeTree 家族引擎)的服务端写入链路 |
| 核心结论 | part 的计数单位是 INSERT 语句条数 (按分区),不是行数;风险信号是 单表单分区 active parts 存量 ,不是"写得快不快";控制手段是攒批而非"少写数据" |
| 关键阈值 | 随版本变化 :23.5 及以前为 150/300,23.6+ 上调为 1000/3000(详见 §3.1)。务必以 system.merge_tree_settings 实测为准,勿套用旧认知 |
| 一句话原则 | 让多条数据走同一条 INSERT 语句成为常态,而不是循环里逐条插入 |
1 Part 是什么
1.1 定义
Part 是 MergeTree 家族引擎在磁盘上的数据分片文件 ,是数据落盘的最小物理单元。每次数据写入,CK 不是把行直接塞进一张大表,而是新写一批文件(一个 part),再交给后台线程逐步合并成更大的 part。
1.2 Part 与 Partition 的区别(易混)
| 概念 | 含义 | 关系 |
|---|---|---|
| Partition(分区) | 按 PARTITION BY 表达式切分的数据子集(如按天 toYYYYMMDD(date)) |
逻辑划分,决定数据放哪个目录 |
| Part(数据块) | 每个分区内部,每次写入产生的一个物理数据块 | 物理划分,一个分区内可以有多个 part |
阈值(旧版 150/300,新版 1000/3000,见 §3.1)是单表单分区内的 active part 计数------既不是全表,也不是全库加总。
1.3 引擎家族
MergeTree 家族(MergeTree / ReplacingMergeTree / SummingMergeTree / AggregatingMergeTree 等)都用同一套 part 机制。差异只在后台 merge 时如何合并同主键数据(去重/求和/聚合),part 的产生与计数规则一致。
2 Part 是怎么产生的
2.1 判据:语句条数 × 触及分区数
一条 INSERT 语句 → 对每个"被数据触及的分区"各生成 1 个 active part
关键点:
- 计数单位是语句次数,不是行数 (最核心的一条)
- 一条 INSERT 写 1 万行 = 1 个 part
- 循环 1 万次、每次 INSERT 1 行 = 1 万个 part ← 爆错的经典姿势
- 按分区拆分:一条 INSERT 若跨了 3 个分区,生成 3 个 part(每分区各 1 个)。单值分区表达式(如按月/天分区键)下通常一条语句只落一个分区 → 1 个 part。
2.2 一条 INSERT 是不是就等于一次网络往返?
不是。 二者是三件不同的事,务必区分:
| 概念 | 是什么 |
|---|---|
| 网络往返 | 客户端与 CK 之间的一次 TCP 交互 |
| INSERT 语句 | CK 服务端解析执行的一条插入命令 |
| Part | 服务端为该语句在每个触及分区生成的物理数据块 |
一次网络往返里可以携带多条语句(multi-statement),一条语句也可以被拆成多次网络传输。决定 part 数的是"服务端收到了几条 INSERT 语句",与网络往返不直接挂钩。
2.3 Merge:part 如何被消化
- CK 后台线程池持续挑选"同分区、大小相近"的多个 part,合并成更大的 part,合并后旧 part 标记为非 active
- 合并是异步、自动、持续的,写入语句提交后立即返回,合并发生在后台
- 越小越快:小 part 合并是亚秒到秒级;合并到几百 MB 的大 part 后再合并会更慢
- 这说明不能靠"无限攒批写超大 part"来解决------那是另一个极端(大批写入的内存/超时问题)
2.4 判据是"积压",不是"速率"
速率之所以重要,是因为它决定积压上限:
稳态积压 ≈ part 生成速率 × merge 消化周期
只要生成速率低于 merge 吞吐,parts 存量稳定在低位不动;速率一旦失控(循环单条插入),merge 再快也追不上,积压持续上涨,最终撞阈值。
3 阈值与限流行为
3.1 两个阈值(默认值随版本变化)
阈值按版本不同,部署前必须确认实际版本,并以实测为准:
| 版本 | parts_to_delay_insert |
parts_to_throw_insert |
说明 |
|---|---|---|---|
| ≤ 23.5(含 22.5) | 150 | 300 | 本项目当前 22.5.1.2079 属此档 |
| ≥ 23.6 | 1000 | 3000 | 官方大幅上调(parts_to_throw_insert "Prior to 23.6 this setting was set to 300") |
| 参数 | 23.6+ 默认值 | 触发行为 |
|---|---|---|
parts_to_delay_insert |
1000(旧版 150) | 开始限流 :INSERT 被人为拖慢(DelayedInserts),但不丢数据 |
parts_to_throw_insert |
3000(旧版 300) | 拒绝写入 :抛 Too many parts 错误,数据真的写不进去 |
告警线怎么定:建议取"当前版本实际阈值的 50%"。旧版(150/300)约 100;新版(1000/3000)约 500。切忌照搬旧版的 100,否则在新版上会产生大量无效告警。
实测当前阈值(避免套用记忆中的数字):
sql
SELECT name, value FROM system.merge_tree_settings
WHERE name IN ('parts_to_delay_insert', 'parts_to_throw_insert',
'inactive_parts_to_delay_insert', 'inactive_parts_to_throw_insert');
另有两个相关参数 :
inactive_parts_to_delay_insert/inactive_parts_to_throw_insert(默认 0 = 不限制 )------它们约束的是非活跃 part (被 merge 替代、待清理的 part)的积压,报错信息为Too many inactive parts。当服务器清理 part 不够快时会用到;同样仅较新版本提供。
3.2 触线是一个渐进过程
低位 ──→ 逼近限流线 ──→ 越线(限流变慢,无丢失) ──→ 越过拒写线(报错)
重要认知:越过限流线不等于故障------它是 CK 主动的自我保护(拖慢写入等待 merge 追上)。真正的故障线是拒写线。告警应设在两者之间的低位(约限流线的一半或更低),留出反应窗口。
3.3 分区是天然的"清零阀"
parts 计数按分区独立,新分区从零开始:
- 按天分区:每天新分区归零,昨天的积压不累积到今天
- 按月分区:月初归零
所以高风险窗口通常只在当天高峰时段,隔日自然缓解。
4 使用建议(写入实践)
4.1 核心原则:攒批
让多条数据走同一条 INSERT 语句,而不是循环里逐条插入。
| 反模式 | 正模式 |
|---|---|
for (row : rows) mapper.insert(row);(每条独立提交) |
一个批次内逐行 addBatch,最后一次提交 |
XML <foreach> 拼接多行 VALUES(见 4.4 驱动的坑) |
单行 VALUES + JDBC addBatch |
| 来一条写一条 | 攒 N 条 / 攒 T 毫秒后写一批 |
批量效率不受影响:一批 500 行走一次发送,和逐行 500 次发送相比,CK 端生成的都是 1 个 part,吞吐反而更高(少了 499 次解析与提交开销)。
4.2 攒批的两种驱动方式
| 方式 | 做法 | 适用 |
|---|---|---|
| 显式批次 | 业务侧天然成批(如一次 poll 到一批、一轮调度处理一批) | 推荐,实现简单、语义清晰 |
| 缓冲窗口 | 内存队列攒 1~2s 或攒够 N 行再刷 | 数据到达零散时用;需评估宕机窗口丢数据(可用幂等重放兜底) |
4.3 攒批的边界
- 批大小要设上限 :防止单批过大导致内存峰值、写超时(项目内
BATCH_LIMIT即此用) - 重试不放大 part 数 :一批重试多次,只有最终成功的那一次提交才产生 1 个 part,失败的尝试 CK 未收到数据、不落 part
- 积压重放不放大 part 数 :下轮把积压数据重新攒批提交,仍是"每批 1 part",积压只让 part 的行数 变大,不增加 part 个数
4.4 驱动层注意(本项目踩过的坑)
housepower clickhouse-native-jdbc 2.7.1 的 prepared INSERT 仅支持单行 VALUES ------多行 VALUES 在语句构造期只登记第一行参数位,第二行首个参数即数组越界 ArrayIndexOutOfBoundsException。
因此攒批必须走「单行 VALUES + JDBC addBatch」:
BATCH 会话内逐行 addBatch → commit() 时一次 executeBatch 网络发送 = 一条 INSERT 语句 = 1 个 part
这也说明:"用了批量接口" 不等于 "只产生 1 个 part",要看最终是否合并成一条 INSERT 语句发送。
注:多行 VALUES 在 CK 服务端和 SQL 语义上是合法的;本节限制仅针对 housepower clickhouse-native-jdbc 2.7.1 的 prepared INSERT 实现。换驱动或改用 HTTP 接口后不受此限。
5 优化建议
5.1 写入侧(优先级最高,成本最低)
| 手段 | 说明 | 收益 |
|---|---|---|
| 统一攒批入口 | 所有 CK 写入收敛到一个 batch 执行器,禁止裸单条 insert | 从源头杜绝 part 爆炸 |
| 跨批次聚合缓冲 | 落库前按表聚合 1~2s 窗口内的多来源行,合并提交 | part 速率可降一个数量级;代价是内存队列 + 宕机窗口权衡 |
| 异步/削峰 | 用队列把写入从请求路径剥离,按稳定速率消费 | 削平写入尖峰 |
5.2 表设计侧
| 手段 | 说明 |
|---|---|
| 分区键选高频写入维度 | 高频表用按天分区,让 parts 隔日清零 |
| 分区基数控制在 1000 以下(社区共识) | ⚠️ 高基数分区键(如按用户 ID、秒级时间戳)会:产生大量微小分区、merge 无法跨分区合并导致小 part 永不消化 、system.parts 行数膨胀拖慢监控查询。按天/按月(toDate/toStartOfMonth)是安全档 |
| TTL 与分区键对齐 | TTL 列若等于分区键,过期即整分区 DROP,零 merge 开销 |
| TTL 不对齐需评估开销 | 若 TTL 列无法与分区键对齐,过期触发的是part 级重写(非分区 DROP),在大量小 part 场景下会显著加重 merge 负担 |
| 主键/排序键 | 与高频查询对齐;ReplacingMergeTree 用业务幂等键(如 signal_id)以便重试/重放无副作用 |
5.3 引擎与服务端
| 手段 | 说明 | 可用性 |
|---|---|---|
| Buffer 表引擎 | 写 Buffer 表由 CK 定时刷盘合并到底表 | 22.5 可用;需 DBA 评估 flush 参数与宕机丢数据特性 |
| async_insert | 服务端异步聚合插入,客户端不必严格攒批 | 22.6+ 才提供(详见 §5.4);低版本勿尝试开启 |
| 硬件/分片扩容 | merge 吞吐随 CPU/IO 增长 | 数据量与 merge 负载同步上升时整体评估 |
| 调阈值 | 放宽 parts_to_delay/throw_insert |
⚠️ 治标有害:只是把故障线推后,积压本身未解决,不推荐作为首选 |
5.4 async_insert(服务端异步插入,写入侧最重要的服务端优化之一)
⚠️ 可用性前提 :
async_insert自 22.6+ 提供。本项目当前 22.5.1.2079 不可用------本节面向升级后的版本。
核心机制 :启用后,CK 把多个并发/连续的小 INSERT 先写入内存缓冲区 ,达到阈值后合并刷盘------由服务端承担攒批职责,客户端不必再严格攒批。
核心配置组合(官方强推):
sql
-- 推荐配置
SET async_insert = 1, wait_for_async_insert = 1;
| 参数 | 取值 | 含义与风险 |
|---|---|---|
async_insert |
1 | 开启服务端缓冲聚合 |
wait_for_async_insert |
1(默认,推荐) | 数据落盘后才返回确认------强持久性,flush 出错会返回给客户端 |
wait_for_async_insert |
0 | "fire-and-forget":进缓冲即返回 ------延迟最低、吞吐最高,但宕机可能丢数据、错误只在 flush 时暴露、无死信队列,仅在可容忍丢数时使用 |
三个 flush 触发条件(任一满足即刷):
| 参数 | 默认值 | 作用 |
|---|---|---|
async_insert_max_data_size |
10 MiB(自建默认,10485760 字节);Cloud 为 100 MiB | 缓冲达到该字节数即刷盘 |
async_insert_busy_timeout_ms |
async_insert_busy_timeout_max_ms 的别名;自建默认 200 ms,Cloud 1000 ms |
缓冲转储前的最长等待时间;24.2+ 自适应模式下实际在 min_ms(50) ~ max_ms(200) 间浮动 |
async_insert_max_query_number |
450 | 缓冲累积的查询数超此值即刷盘 |
自建与 Cloud 默认值可能不同,上线前以
system.settings实测为准。
重要 :自 24.2 起默认采用自适应超时 (async_insert_use_adaptive_busy_timeout=1,在 min 50ms ~ max 200ms 间动态调整),不再是固定间隔。官方默认见上表;5~10MB / 1000~2000ms 是按业务延迟要求人为收紧 的值,不是官方推荐默认。
与客户端攒批的关系(关键前提):
- 两者可叠加;启用 async_insert 后,客户端单条小 INSERT 不再直接对应一个 part------服务端会先聚合到缓冲区,再以合并后的批次写盘
- 因此本文 §2「循环单条 insert = part 数 = 行数」的论断,在启用 async_insert 后不再直接成立 ,代价是引入一层服务端缓冲的延迟与不确定性(数据 flush 前不可查询)
- 注意 :异步插入默认禁用自动去重 。去重开关随版本演进:
- 旧版(≤25.x,含本项目 22.5):由
insert_deduplicate(同步,默认 1)与async_insert_deduplicate(异步,默认 0)分别控制 - 26.2+ :由新设置
deduplicate_insert(默认enable)统一接管并覆盖 上述两者;此时仅设insert_deduplicate=0已无法关闭去重 - 需要异步插入幂等时,须显式开启
async_insert_deduplicate(旧版)或确认deduplicate_insert取值(26.2+)。较新版本已改善异步插入与 MV/复制的去重协同。
- 旧版(≤25.x,含本项目 22.5):由
调试入口 :async_insert 开启后行为对客户端"不可见"(缓冲、flush 时机),排障可查 system.asynchronous_insert_log(需在服务端 <asynchronous_insert_log> 配置中开启),能看到每次 flush 的 query_id、bytes、rows、status。
5.5 反模式清单(禁止)
- 循环体内逐条调用单行 insert(每条独立提交)------part 数 = 行数
- XML
<foreach>拼多行 VALUES------parts 语义外还踩驱动硬限制(housepower 2.7.1 prepared INSERT 仅支持单行,详见 §4.4;CK 服务端本身支持多行 VALUES) - 靠调高阈值"解决" too many parts------掩盖问题
- 无限攒批写超大 part------引入内存/超时问题(另一个极端)
5.6 Merge 调优参数(parts 积压时,优先于"调高阈值")
当 parts 积压但写入端已尽力攒批时,可从 merge 侧调优。这些参数修改多为服务端配置,需 DBA 评估并(部分)重启。
| 参数 | 作用 | 默认/建议 | 备注 |
|---|---|---|---|
background_pool_size |
后台 merge/mutation 线程数 | 默认 16(自建);Cloud/部分发行版可能按规格调整 | 若 system.merges 长期占满,可上调至 32 左右或按核数评估;只能运行时增大,调小需重启 |
background_merges_mutations_concurrency_ratio |
线程数与可并发 merge 数之比 | 默认 2 | 有效最大并发 = background_pool_size × 该比值(如 16×2=32);只能增大 |
merge_selector_enable_heuristic_to_lower_max_parts_to_merge_at_once |
降低单次 merge 可选 part 上限 → 增加并发 merge 数,利于缓解 parts 积压;代价是写放大增加 | 25.12 新增(默认 0),26.7 起默认启用 | ⚠️ 版本敏感,低版本此设置不存在 |
min_age_to_force_merge_seconds |
范围内所有 part 都超过该年龄时强制合并,防"冷门"分区小 part 长期滞留 | 默认 0(禁用),可设 3600 | 另有 min_age_to_force_merge_on_partition_only 控制作用范围 |
应急手段
OPTIMIZE TABLE ... FINAL:某分区 parts 逼近阈值、merge 来不及时可手动触发强制合并。但它是重操作 ------会吞大量 IO 并阻塞该分区写入,仅限应急,不可作为常规手段。
⚠️ 注意 :建议中出现的merge_selector_heuristic是错误名称 ,官方无此设置;正确设置名为merge_selector_enable_heuristic_to_lower_max_parts_to_merge_at_once(见上表)。
6 监控方案
监控分层原则(领先---同步---滞后):三者组合才形成完整可观测性闭环------
- 领先信号 :
DelayedInserts/RejectedInserts事件计数(限流/拒写是否已发生,最早感知)- 同步信号 :
system.merges积压深度(merge 是否跟得上,与积压同步)- 滞后信号 :
system.partsactive parts 存量(涨到阈值时问题已发生)
6.1 核心指标:单表单分区 active parts(滞后信号)
sql
SELECT table, partition, count() AS active_parts
FROM system.parts
WHERE database = '<db>' AND active
GROUP BY table, partition
ORDER BY active_parts DESC
LIMIT 20;
| 阈值 | 动作 |
|---|---|
| active parts > 当前版本限流线的 ~50% | 告警:排查写入端是否出现高频小批/单条插入(旧版约 100,新版约 500,见 §3.1) |
| active parts > 拒写线 | CK 已拒写,立即处置 |
健康态预期:个位数到几十。
接入 Prometheus 时注意:按天分区的 partition label 基数每天 +1 ,建议 label 只保留 database + table,或仅对 top 分区暴露,避免指标基数膨胀。
6.2 merge 积压深度(同步信号)
仅看 active parts 是滞后 的------它涨到阈值时问题已发生。system.merges 能反映 merge 是否"跟得上":若该表行数持续增长且 elapsed 不断累加,说明 merge 线程池已饱和,parts 即将堆积。
sql
SELECT table, elapsed, progress, num_parts, memory_usage
FROM system.merges
WHERE database = '<db>';
6.3 限流/拒写事件计数(领先信号)
system.events 中的累计计数直接反映限流与拒写是否已发生 ,比看 parts 存量更早感知。建议作为 Prometheus counter 采集,并设速率告警:
sql
SELECT event, value FROM system.events
WHERE event IN ('DelayedInserts', 'RejectedInserts');
告警表达式示例:rate(DelayedInserts[5m]) > 0 即触发预警。
sql
-- 佐证:全库 active parts 总量
SELECT metric, value FROM system.metrics WHERE metric = 'PartsActive';
6.4 Prometheus 集成实践
- CK 自带 Prometheus endpoint(默认 9363 端口,通过服务端
<prometheus>配置开启),可直接暴露system.metrics/system.events中的关键指标,无需自写 exporter system.parts这类需要 SQL 聚合的指标,可通过clickhouse-exporter或自定义 view 暴露,label 只保留database+table两级
6.5 巡检
sql
-- 看 merge 是否跟上
SELECT table, elapsed, progress, num_parts FROM system.merges WHERE database = '<db>';
-- 建表口径核对
SELECT name, engine, partition_key, sorting_key, ttl FROM system.tables WHERE database = '<db>';
7 常见误解澄清
| 误解 | 实际情况 |
|---|---|
| "写得快就会 too many parts" | 判据是单表单分区 active parts 存量,不是速率本身;攒批写出高频流量也不会堆 part |
| "一批多少行就产生多少 part" | part 数 = 语句条数 × 触及分区数,与行数无关 |
| "一次网络往返 = 一个 part" | 三者不同:网络往返 / INSERT 语句 / part;决定 part 数的是"服务端收到几条 INSERT 语句" |
| "越过 150 就是故障" | 150/300 是**旧版(≤23.5)**阈值;限流线(变慢,不丢数据)与拒写线(报错)是两个不同概念。新版阈值为 1000/3000,务必按实际版本判断(§3.1) |
| "重试会成倍产生 part" | 只有成功那次提交才落 part,失败尝试不落 |
| "积压到下一轮会让 part 翻倍" | 下轮重新攒批仍是"每批 1 part",积压只增大 part 行数 |
| "架构封顶 N part/s 就是每秒恒定产出 N" | 封顶是理论上界 (任何情况都不会超过),不是恒定值。空批、批处理异常/退避、积压防御等只会让实际值低于上限------对 parts 更安全,代价是时效 |
| "某分片某轮异常/超时少产出 part,会突破或影响封顶" | 每分片每轮最多贡献 1 part,异常只是把"最多 1"变成"0";实际速率 = 有产出的分片数 ÷ 周期 ≤ 上限,永远不会超过 |
| "merge 是固定周期定时执行的" | merge 是异步、持续、按需触发(后台线程挑同分区、大小相近的 part 合并),不是定时任务;单次耗时取决于 part 大小与资源,小 part 亚秒到秒级,大批更慢 |
| "调高阈值能解决问题" | 只是把故障线推后,积压未解决,治标有害 |
| "多副本会各写一份放大 part" | 副本(ReplicatedMergeTree)是数据复制,part 计数按表逻辑计,不因副本数翻倍。阈值按单个副本视角判断 ;若把多副本的 system.parts 结果加总看会随副本数放大,那是统计口径问题,非 part 实际膨胀 |
8 团队约定(防止退化)
- CK 写入必须经统一攒批入口 ------禁止循环内逐条单行 insert,禁止 XML
<foreach>多行 VALUES(和驱动有关系);代码评审检查 - 新增写入链路时评估 part 速率 :写入频率 × 触及分区数,对照
稳态积压 ≈ 速率 × merge 周期 - 分区设计:高频写入表用按天分区(分区基数 < 1000);TTL 尽量与分区键对齐
- 监控前置 :新表上线前先接入
system.parts监控与告警
9 故障处置决策树(收到 too many parts 告警 / 报错时)
① 收到告警
│
├─ 先确认当前版本实际阈值(勿套用 150/300 旧认知)
│ SELECT name, value FROM system.merge_tree_settings
│ WHERE name LIKE 'parts_to_%_insert' OR name LIKE 'inactive_parts_%';
│
② 定位谁在堆 part
│ SELECT database, table, partition, count() AS parts
│ FROM system.parts WHERE active
│ GROUP BY database, table, partition ORDER BY parts DESC LIMIT 20;
│
③ 判断:是「写入端问题」还是「merge 端问题」?
│
├─ 写入端看写放大:是否存在高频小批/单条 INSERT
│ SELECT avg(written_rows) FROM system.query_log
│ WHERE query_kind='Insert' AND type='QueryFinish' AND event_time > now()-3600;
│ └─ 平均行数远低于 1000 → 【写入端问题】
│
└─ merge 端看消化能力:
SELECT table, elapsed, progress, num_parts FROM system.merges WHERE database='<db>';
SELECT event, value FROM system.events WHERE event IN ('DelayedInserts','RejectedInserts');
└─ merges 长期占满、elapsed 持续累加 → 【merge 端问题】
│
④ 分派处置
├─ 写入端问题 → 根本解法:
│ · 客户端攒批(模板 §4.1)------首选
│ · 升级后可评估 async_insert=1,wait_for_async_insert=1(§5.4)
│
├─ merge 端问题 → 调优(§5.6,优先于调高阈值):
│ · background_pool_size / background_merges_mutations_concurrency_ratio ↑
│ · min_age_to_force_merge_seconds 治冷门分区小 part
│
└─ 紧急止血(仅争取时间):
· OPTIMIZE TABLE <t> PARTITION '<p>' FINAL(重操作,阻塞该分区写入)
· 临时调高 parts_to_throw_insert(治标,须配合上面根治)
判断原则 :绝大多数 too many parts 的根因是写入端高频小批 ,而非 merge 太慢。先查
avg(written_rows),再动服务端参数。注:
system.query_log保留时长由服务端<query_log>配置决定(默认 7 天,生产常调短),若查不到数据先确认保留窗口。
10 版本兼容性速查表
| 特性 / 设置 | 引入或变更版本 | ≤22.5(本项目) | 说明 |
|---|---|---|---|
parts_to_delay_insert / parts_to_throw_insert = 150/300 |
≤ 23.5 | ✅ 适用 | 本项目当前档 |
| 阈值上调为 1000/3000 | 23.6+ | ❌ | 版本切换后务必同步调整告警线 |
inactive_parts_to_delay/throw_insert(默认 0) |
较新版本 | ❌ | 约束非活跃 part 积压 |
async_insert(服务端异步插入) |
22.6+ | ❌ | §5.4;本项目当前不可用 |
wait_for_async_insert(默认 1,推荐) |
随 async_insert | ❌ | =0 有丢数据风险 |
| 自适应 async 刷盘超时 | 24.2+ | ❌ | async_insert_use_adaptive_busy_timeout 默认开 |
async_insert 默认启用 |
26.2(以官方发布说明为准) | ❌ | 升级后即使客户端攒批,行为也会变,需显式 async_insert=0 保持同步语义 |
async_insert 与物化视图去重协同 |
26.1 | ❌ | 异步插入+MV 的去重改善 |
insert_deduplicate(同步去重开关,默认 1)/ async_insert_deduplicate(异步,默认 0) |
旧设置 | ✅ 可用 | 本项目 22.5 的去重控制项 |
deduplicate_insert(统一去重开关,默认 enable) |
26.2+ | ❌ | 覆盖 insert_deduplicate / async_insert_deduplicate;升级后仅设 insert_deduplicate=0 已无法关闭去重(见 §5.4) |
input_format_max_block_wait_ms(按时长刷块) |
26.2 | ❌ | 无界插入场景的批控制 |
merge_selector_enable_heuristic_to_lower_max_parts_to_merge_at_once |
25.12 新增,26.7 默认启用 | ❌ | §5.6 |
background_merges_mutations_concurrency_ratio |
20.4+ | ✅ 可用 | 有效并发 = pool × ratio(默认 2) |
min_age_to_force_merge_seconds |
20.3+ | ✅ 可用 | 默认 0(禁用),可设 3600 |
⚠️ 使用本表的前提 :版本号以官方发布说明为准;上线前请用 §3.1 的 SQL 实测当前实例的真实阈值与可用设置,勿仅凭本表推断。
11 版本升级检查清单
ClickHouse 版本升级后,写入行为可能静默改变(尤其默认值翻转)。升级前后按下表逐项检查。
11.1 升级前
| # | 检查项 | 动作 |
|---|---|---|
| 1 | 阈值随版本切换 | 从 22.5 跨到 23.6+ 时,parts_to_delay/throw_insert 由 150/300 变为 1000/3000;监控告警线须同步从 ~100 调到 ~500 ,否则产生大量无效告警。注意中间版本 :若目标版本为 23.0~23.5,阈值仍为 150/300(切换点是 23.6,非 23.0),无需调整告警线 |
| 2 | 确认目标版本是否跨过默认值翻转点 | 重点:async_insert 默认启用(26.2 起)、deduplicate_insert 接管去重(26.2 起)。跨过即需在升级方案中显式声明行为保持措施 |
| 3 | 记录当前 system.merge_tree_settings 快照 |
升级前存一份,便于升级后逐项 diff,定位"哪些默认值变了" |
| 4 | 确认去重依赖 | 若业务依赖 insert_deduplicate/async_insert_deduplicate,升级到 26.2+ 后须改由 deduplicate_insert 控制(详见 §5.4) |
11.2 升级后
| # | 检查项 | 动作 |
|---|---|---|
| 1 | 跑 §3.1 实测 SQL | 确认实例真实阈值(parts_to_* 与 inactive_parts_* 一次查全),不凭文档推断 |
| 2 | 核对告警线 | 按实测阈值重设(经验值:实际限流线的 ~50%) |
| 3 | 验证 async_insert 行为 | 若升到 26.2+,async_insert 默认变 on;即使客户端已攒批,也要显式设 async_insert=0 才能保持原有"每条 INSERT 对应 part"的同步语义 |
| 4 | 验证去重行为 | 26.2+ 下确认 deduplicate_insert 取值符合预期(默认 enable);仅设 insert_deduplicate=0 已无法关闭去重 |
| 5 | 复核监控分层 | 确认 DelayedInserts/RejectedInserts(领先)、system.merges(同步)、system.parts(滞后)三类指标在新版本上仍可取到(见 §6) |
11.3 新表上线前(常规)
- 跑 §3.1 实测 SQL 确认实例真实阈值,不要凭文档或记忆推断
- 评估 part 速率:写入频率 × 触及分区数,对照 §2.4 积压公式
- 接入监控 :
system.parts单表单分区 active parts(>100 或按实测阈值一半告警) - §5.4 的 async_insert 仅在升级到 22.6+ 后再评估启用;当前 22.5 不可用,保持客户端攒批即可
⚠️ 本清单的使用前提 :文中所有版本号以官方发布说明为准;关键阈值与设置可用性,一律以实例实测(§3.1 SQL)为准,勿凭记忆或第三方转述推断。