ClickHouse性能优化年度实践清单:从参数调优到架构设计的20条经验
ClickHouse性能优化年度实践清单从参数调优到架构设计的20条经验过去一年团队管理的ClickHouse集群从最初的3个节点扩展到现在的24个节点日写入量从200GB增长到3TB查询QPS从个位数增长到200。在持续的性能优化过程中积累了20条经过生产验证的经验按优先级从高到低排列。一、当3TB日写入让集群雪崩一个性能退化事故的完整复盘今年4月数据量翻倍后集群出现了严重的性能退化写入延迟从100ms飙升到3秒查询超时率从0.1%上升到15%。排查发现是三个因素叠加导致的第一分区键设计不合理导致单分区数据量超过500GB第二max_partitions_per_insert_block使用默认值未调整导致写入大量小分区第三MergeTree的合并速度跟不上写入速度积压了超过5000个待合并的part。单个调整任何一个参数都无法解决问题——这是一个典型的系统性性能瓶颈。最终通过重新设计分区键从天改为小时、调整合并参数和增加节点三管齐下才将性能恢复到可接受水平。二、ClickHouse查询和写入的性能链路ClickHouse的核心性能模型依赖于两点写入时的数据预排序和查询时的分区裁剪。理解了这个模型就理解了90%的优化策略。写入阶段数据按ORDER BY键排序后写入磁盘查询阶段利用主键索引稀疏索引和分区信息快速定位需要扫描的数据范围。三、20条核心优化经验的实践代码#!/usr/bin/env python3 ClickHouse Performance Optimization Toolkit import clickhouse_connect import time from typing import Dict, List, Optional from dataclasses import dataclass from contextlib import contextmanager dataclass class MergeStatus: table: str active_parts: int outdated_parts: int bytes_to_merge: int future_parts: int is_healthy: bool class ClickHouseOptimizer: def __init__(self, host: str localhost, port: int 8123, user: str default, password: str ): self.client clickhouse_connect.get_client( hosthost, portport, usernameuser, passwordpassword ) def check_merge_health(self, database: str default) - List[MergeStatus]: 检查MergeTree表的合并健康状况经验1: 监控合并积压 query SELECT database, table, active_parts, outdated_parts, formatReadableSize(bytes_to_merge) as merge_size, future_parts FROM system.merges WHERE database {database:String} ORDER BY bytes_to_merge DESC try: result self.client.query(query, parameters{database: database}) status_list [] for row in result.result_rows: status MergeStatus( tablerow[1], active_partsrow[2] or 0, outdated_partsrow[3] or 0, bytes_to_mergerow[4] or 0, future_partsrow[5] or 0, is_healthy(row[3] or 0) 100 # outdated_parts 100 视为健康 ) status_list.append(status) return status_list except Exception as e: print(f[ERROR] Merge health check failed: {e}) return [] def optimize_partition_key(self, table: str, database: str default): 经验2-5: 分区键优化分析 queries { 分区数量检查: f SELECT countDistinct(partition) as partition_count, max(rows) as max_partition_rows, formatReadableSize(max(bytes_on_disk)) as max_partition_size FROM system.parts WHERE database {database} AND table {table} AND active , 分区倾斜度分析: f SELECT partition, count() as parts, sum(rows) as total_rows, formatReadableSize(sum(bytes_on_disk)) as size FROM system.parts WHERE database {database} AND table {table} AND active GROUP BY partition ORDER BY total_rows DESC LIMIT 10 , 小分区检测经验3: 避免过多小分区: f SELECT partition, count() as part_count, sum(rows) as rows, formatReadableSize(sum(bytes_on_disk)) as size FROM system.parts WHERE database {database} AND table {table} AND active GROUP BY partition HAVING sum(rows) 100000 ORDER BY rows LIMIT 20 } for name, query in queries.items(): try: print(f\n {name} ) result self.client.query(query) print(result.result_rows[:5] if result.result_rows else 无数据) except Exception as e: print(f[ERROR] {name} failed: {e}) def get_query_profile(self, query_id: str): 经验6-8: 查询性能分析利用query_log profile_query f SELECT query_duration_ms, read_rows, read_bytes, formatReadableSize(read_bytes) as read_size, memory_usage, result_rows, result_bytes FROM system.query_log WHERE query_id {query_id} AND type QueryFinish ORDER BY event_time DESC LIMIT 1 try: result self.client.query(profile_query) if result.result_rows: row result.result_rows[0] print(f查询耗时: {row[0]}ms) print(f扫描行数: {row[1]}) print(f扫描数据: {row[3]}) print(f内存使用: {format_bytes(row[4])}) print(f结果行数: {row[5]}) except Exception as e: print(f[ERROR] Profile query failed: {e}) def set_optimize_settings(self): 经验9-12: 会话级查询优化设置 settings [ (max_threads, 8), (max_memory_usage, str(20 * 1024 * 1024 * 1024)), # 20GB (max_bytes_before_external_group_by, str(10 * 1024 * 1024 * 1024)), (distributed_aggregation_memory_efficient, 1), (prefer_localhost_replica, 1), (optimize_skip_unused_shards, 1), (distributed_product_mode, local), ] try: for setting, value in settings: self.client.command(fSET {setting} {value}) print(查询优化设置已应用) except Exception as e: print(f[ERROR] Setting application failed: {e}) def analyze_table_structure(self, table: str, database: str default): 经验13-15: 表结构分析 try: # 检查排序键 create_query self.client.query( fSHOW CREATE TABLE {database}.{table} ) if create_query.result_rows: ddl create_query.result_rows[0][0] print(f\n 表结构 ) # 检查是否使用LowCardinality经验16 if LowCardinality not in ddl: print([建议] 字符串列建议使用LowCardinality优化) # 检查是否使用CODEC经验17 if CODEC not in ddl: print([建议] 建议添加ZSTD压缩CODEC) # 检查列压缩率 column_query f SELECT name, type, compression_codec, formatReadableSize(data_compressed_bytes) as compressed, formatReadableSize(data_uncompressed_bytes) as uncompressed FROM system.columns WHERE database {database} AND table {table} ORDER BY data_uncompressed_bytes DESC LIMIT 10 result self.client.query(column_query) if result.result_rows: print(\n 列压缩统计 ) for row in result.result_rows: print(f {row[0]} ({row[1]}): {row[3]} - {row[2]} f(CODEC: {row[2]})) except Exception as e: print(f[ERROR] Table analysis failed: {e}) def format_bytes(bytes_val): if bytes_val is None: return N/A for unit in [B, KB, MB, GB, TB]: if bytes_val 1024: return f{bytes_val:.1f}{unit} bytes_val / 1024 return f{bytes_val:.1f}PB if __name__ __main__: optimizer ClickHouseOptimizer() # 健康检查 health optimizer.check_merge_health() for h in health: status HEALTHY if h.is_healthy else WARNING print(f[{status}] {h.table}: {h.active_parts} active, f{h.outdated_parts} outdated) # 分区优化分析 optimizer.optimize_partition_key(events) # 表结构分析 optimizer.analyze_table_structure(events)四、20条优化经验精要速查按投入产出比排序的20条核心经验监控合并积压system.merges中future_parts 100时立即告警分区键选天/小时级别避免按分钟分区导致的海量小文件ORDER BY键查询WHERE条件主键索引的有效性80%取决于此避免Nullable列滥用每个Nullable额外增加1字节开销字符串列用LowCardinality基数100万时压缩率可达10倍使用ZSTD(3)作为默认压缩压缩比和速度的最佳平衡点optimize_skip_unused_shards1分布式查询性能提升显著物化列替代函数计算高频聚合的字段预先物化max_bytes_before_external_group_by大聚合防OOM的关键参数分布式表优先本地查询prefer_localhost_replica1控制单表列数50列数过多导致内存膨胀使用INSERT INTO ... SELECT代替逐条INSERT后台合并线程数≥CPU核数/2background_pool_sizeMergeTree设置ttl_only_drop_parts1TTL删除时避免重写使用Projection替代物化视图自动路由到最优数据子集max_insert_threads与CPU核数对齐写入并行度控制异步插入用于高频小批量写入async_insert1max_partitions_per_insert_block0避免默认限制SSD做热数据HDD做冷数据分层storage_policy定期执行OPTIMIZE TABLE FINAL合并碎片化数据五、总结ClickHouse的性能优化是一个系统性工程最有效的优化往往不是调整某个参数而是重新审视分区策略、排序键设计和查询模式是否匹配。过去一年最深刻的教训是在理解业务查询模式之前不要盲目调参。下半年计划在物化列自动化推荐和冷热数据生命周期管理两个方向继续深入。

相关新闻

计算机毕业设计之基于springboot的家政服务平台

计算机毕业设计之基于springboot的家政服务平台

本论文借助 Java 编程语言,运用VUE 前端架构及SpringBoot 后端架构,以 MySQL 数据库为依托,对一套家政服务平台进行了分析和设计。此系统包括家政服务、服务预约等功能,并划分为用户、家政人员和管理员三个角色,各角色…

2026/10/18 4:21:39 阅读更多 →
BQ40Z50-R4电源管理与电量计量:从原理到实战的BMS设计指南

BQ40Z50-R4电源管理与电量计量:从原理到实战的BMS设计指南

1. 项目概述:深入BMS的“心脏”与“大脑”在任何一个依赖锂离子电池供电的嵌入式系统中,比如你的笔记本电脑、电动工具或者无人机,电池管理系统(BMS)都扮演着至关重要的角色。你可以把它想象成电池组的“智能管家”和“…

2026/10/20 0:46:33 阅读更多 →
信息素养竞赛“必然事件”真题解析:集合递推与动态规划实战

信息素养竞赛“必然事件”真题解析:集合递推与动态规划实战

如果你正在准备信息素养大赛、CSP-J/S或任何C算法竞赛,一定会遇到这类题目: “必然事件” 。听起来像数学概念,但出现在编程题里,往往意味着你需要从逻辑和概率的交叉点找到突破口。 很多选手第一次看到这种题会懵——这到底是…

2026/10/18 18:44:14 阅读更多 →

最新新闻

程序员转大模型应用开发:6个月从RAG到Agent的实战路线图

程序员转大模型应用开发:6个月从RAG到Agent的实战路线图

前两年我还在纠结“大模型这么火,跟我一个写业务接口的程序员有什么关系”,后来真跳进去做了几个落地项目才发现:大模型领域现在最缺的,恰恰不是能推导Attention公式的研究员,而是能把模型接进业务、能把检索链路调通、…

2026/10/20 2:38:36 阅读更多 →
MAX9286+96705四路AHD车载摄像头方案实战指南

MAX9286+96705四路AHD车载摄像头方案实战指南

简介:本资源是一套面向嵌入式Linux视频驱动开发者的AHD多路视频采集方案,聚焦于将四路模拟高清(AHD)摄像头信号经MAX9286解码器与MAX96705串行器协同处理,统一转换为MIPI CSI-2数字接口输出,适用于车载DVR、…

2026/10/20 2:38:36 阅读更多 →
SPEI干旱指数工程化计算工具包:支持多PET算法与时空尺度聚合

SPEI干旱指数工程化计算工具包:支持多PET算法与时空尺度聚合

简介:本资源是一套面向气象水文、生态遥感及干旱研究领域的Python计算工具包,专为解决现有开源代码仅支持月尺度SPEI而缺乏日/周尺度计算能力的痛点设计。代码完整实现多时间尺度(日、周、月)、多分辨率(支持逐日输入&…

2026/10/20 2:38:36 阅读更多 →
AiPy实战:用Python本地搭建AI自动化流程,低成本替代昂贵AI助手

AiPy实战:用Python本地搭建AI自动化流程,低成本替代昂贵AI助手

1. 先说清楚:这玩意儿到底是个啥如果你这段时间在关注AI圈,多少应该听过几个名字——某个老外做的自动化AI助手,能自己拆任务、调工具、跑流程,动辄几千块一个月的订阅费,还得折腾各种付费接口。很多开发者、中小团队被…

2026/10/20 2:38:36 阅读更多 →
换两行代码就换引擎:laya-mlx 接口兼容迁移的设计野心

换两行代码就换引擎:laya-mlx 接口兼容迁移的设计野心

换两行代码就换引擎:laya-mlx 接口兼容迁移的设计野心 【免费下载链接】laya-mlx Native MLX runtime for Laya typed decision models — 7–14 ms short decisions on M3 Max. No text generation, PyTorch, or cloud API. 项目地址: https://gitcode.com/gh_mi…

2026/10/20 2:38:36 阅读更多 →
博途1500PLC字符串包含判断:FOR+MID滑动窗口实现详解

博途1500PLC字符串包含判断:FOR+MID滑动窗口实现详解

用博途1500PLC判断源字符串中是否含有子字符串,这个需求听起来简单,我第一次在现场做的时候也以为一条指令就完事了。那是条包装线的扫码工位,读码器通过Profinet把条码送进PLC,我需要在ABC240315001XYZ这样的字符串里判断有没有2…

2026/10/20 2:37:36 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →