惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
L
Lohrmann on Cybersecurity
T
Threatpost
T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
S
Schneier on Security
Engineering at Meta
Engineering at Meta
Scott Helme
Scott Helme
博客园 - 三生石上(FineUI控件)
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Visual Studio Blog
I
Intezer
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
S
Securelist
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog RSS Feed
M
MIT News - Artificial intelligence
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Know Your Adversary
Know Your Adversary
H
Hackread – Cybersecurity News, Data Breaches, AI and More
阮一峰的网络日志
阮一峰的网络日志
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
The Hacker News
The Hacker News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Register - Security
The Register - Security
Simon Willison's Weblog
Simon Willison's Weblog
Security Latest
Security Latest
C
Cisco Blogs
量子位
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
C
CERT Recently Published Vulnerability Notes
T
Tenable Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
AWS News Blog
AWS News Blog
Project Zero
Project Zero
D
Darknet – Hacking Tools, Hacker News & Cyber Security
A
Arctic Wolf
K
Kaspersky official blog

InfoQ - 促进软件开发领域知识与创新的传播

Meta 收购 Manus 这事儿泡汤了 5.5万 Star 开源项目 Ghostty 被迫出走,GitHub 正在终结一代技术人的乌托邦 Slack 长时运行多智能体系统的上下文管理方案 从 T+1 到分钟级:金城银行基于 Apache Doris 构建高可靠、强一致的实时数据平台 谷歌云推出 Agents CLI,简化 AI 智能体开发全流程 Claude官方击穿高薪、高学历的安全防线!Anthropic点名10大高危职业,但有群人暂时稳了 亚马逊云科技终止 WorkMail 服务,并将 App Runner 转入维护模式 OPPO小布记忆:全模态碎片化内容的理解与智能整理实践|AICon上海 模力工场038周AI应用周榜:工具在消失,工作流在出现 Akamai CEO Tom Leighton:Agent 时代来临,云基础设施正从“中心化”转向“分布式边缘” 日均数百亿入库背后:从“人肉调度”到K8s弹性架构,度小满金融基于OceanBase重构入库架构实践 百度文库网盘发布GenFlow 4.0:月活用户超1亿,要把网盘变成全端AI工作台 Altman 投的 Agent 终端 Warp 开源了!斩获3.5万star 哪些客户需要拒, 敢让龙虾决定吗?_AI&大模型_InfoQ 中文站_InfoQ精选视频 从开发到生产:为什么越来越多的机器学习团队纷纷迁移到 Snowflake | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 探索多智能体工作流:LangGraph Snowflake Cortex AI | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 腾讯云分布式缓存数据库:AI Agent - 从提示词工程到 Harness 工程 | 腾讯云数据库 DBTalk_腾讯_凌敏_InfoQ精选视频 基于 Streamlit 为 CSV 数据构建分析智能体 | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 AI 智能体:告别文档缺漏 | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 构建 AI 驱动的数据管道:深度探讨 Snowflake Openflow 与非结构化数据 | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 云端太贵、本地不够聪明,英特尔押注“端云混合AI”:智能体PC会替人完成工作 不到10%的存储投入,可能拖垮90%的GPU投资!IBM把AI Agent塞进存储系统,算清企业最容易忽略的一笔账 Snowpark 上手实战 | BUILD 2025_大数据_王玮_InfoQ精选视频 ClickHouse + Langfuse,构建 Agent 可观测基石 腾讯云分布式缓存数据库:Cluster Proxy 共享连接架构深度解析 | 腾讯云数据库 DBTalk_腾讯_凌敏_InfoQ精选视频 AI 写代码太烧钱了:Copilot、Claude 一起涨价,不如把程序员请回来? 英特尔发布至强600系列工作站处理器与锐炫Pro B70 GPU,全新AI工作站来了 腾讯云分布式缓存数据库:从 Redis 到 Valkey - 开源社区如何快速创新 | 腾讯云数据库 DBTalk_腾讯_凌敏_InfoQ精选视频 印奇这次要“从0重做”智驾模型!首谈阶跃和千里双公司布局:中国AI商业闭环要靠车跑出来 从Cursor返聘归来,90后华裔女高管带Claude开启日更模式:token成本比工程师工资低多了! 从 Coding 到 Agent:QCon 北京 2026 全景复盘,优秀出品人 & 明星讲师名单揭晓 全链路支撑大模型国产化“Day 0适配”,商汤大装置构建全栈能力底座 凌晨,OpenAI 与亚马逊云科技史上最大联合发布来了 HashiCorp Vault 2.0 发布:引入新身份联邦机制,迈入 IBM 生命周期体系 Yelp 实现超 1,000 个 Cassandra 节点零停机升级 写了 17 年开源代码,我为什么认为 Coding Agents 堆功能是在瞎折腾? 基于 Apache Camel 编排智能体与多模态 AI 管道 面向智能体与人类用户的AI记忆系统:架构设计与核心场景实践|AICon上海 Anthropic 推出 Managed Agents,简化 AI 代理部署流程 阿里HappyHorse开启灰测,720P视频生成低至0.44元/秒 讯飞联合清华团队押注量子AI:不看营收、不设KPI,一群“无人区”科学家,抢夺下代AI算力入口 小米万亿模型全面开源:MIT 协议、1M 上下文,但还是打不过 DeepSeek Cortex Code 入门指南:面向数据工程师的实践路径 | 技术实践 openJiuwen社区首发Team Skills,定义Coordination Engineering新范式 用 Snowflake Cortex Agents 释放结构化数据的最大价值 | 技术实践 Grafana 利用 Kafka 对 Loki 进行了架构重构,并发布了一款命令行工具,旨在将可观测性引入编码代理 ClickHouse重构全文索引:对象存储上跑出高性能 Full-Text Search 可观测性和遥测技术如何提升软件工程实践 Dropbox 与 GitHub 合作,将单体库大小从 87GB 缩减至 20GB Agent 的下一站:基于长期记忆系统 EverOS 的自我演进|AICon上海 同一赛道,四种收费:Agent 控制层(Harness)开始分裂 Cloudflare Sandboxes 正式发布,为 AI 代理提供持久化隔离环境 Agent 的“记忆断片”困局,该怎么破?_AI&大模型_AICon 全球人工智能开发与应用大会_InfoQ精选视频 数据分析师如何快速建立在 AI 时代最值钱的能力:一份可落地的行动路线图 摩尔线程最新财报:研发占比超86%,万卡级大规模智算集群落地 当云区域失效:地缘动荡环境下的高可用重构 Slack 重构通知系统,设置参与度提升 5 倍 智能体工程的隐性技术债务 “我把所有模型都换成了DeepSeek V4”:月账单将降 90%,效果还更好 阿里云智能集团高级技术专家刘少伟已确认出席AICon上海站,并分享如何构建企业 Agent 的自动化行动架构 构建生产就绪的 tRPC API:Apollo Federation 的 TypeScript 替代方案 Anthropic推出面向Claude Code的基于智能体的代码审查功能 北京车展直击:斑马智能甩出车载Agent短剧,比亚迪率先落地,AI让智能座舱又热起来了 Snowflake 作为智能体运行时:从静态管道迈向自主数据系统 | 技术实践 Snowflake 上的本体体系:基于 Cortex Code 能力实现从架构到部署 | 技术实践 Cloudflare 公布 MCP 架构方案,应对企业面临的安全与治理风险 复杂的项目管理怎么做到「AI 友好」?飞书项目用「开放」给出答案 Snowflake Cortex Code 的规范驱动开发:将 SDLC 方法论引入 AI 辅助工作流 | 技术实践 Copilot 不让注册了:从“随便用”到“全面限”,agent 把原有订价模型顶穿了 当互联网用AI卷效率时,这家公司先问了一连串“能不能” Meta 开始记录员工每一次点击:AI 要接管工作,先监控会工作的人 Meta“Token榜”逼疯打工人,一夜烧掉公司几万刀!AI时代Token焦虑越来越离谱 智源FlagOS完成DeepSeek-V4-Flash在八款芯片Day0适配,实现三重技术突破 DeepSeek V4 重磅开源!首次打通华为Ascend,也没丢掉英伟达,百万上下文夺回国产模型话语权 李志飞的“新实验”:当超级个体撞上真实组织 GPT-5.5 登顶时刻,Anthropic 亲口承认 Claude 变笨了!网友群嘲:太敷衍 那些没空写的小需求,龙虾真能做吗?_AI&大模型_InfoQ 中文站_InfoQ精选视频 从 Pandas 到生产:使用任意 IDE 进行可扩展的 ML 数据管道与分布式处理 | BUILD 2025_AI&大模型_王玮_InfoQ精选视频 pnpm 11 候选版本发布,带来 ESM 分发、供应链默认设置以及新的存储格式 银行业PDF表格提取方案重构:基于Java的分层方案 GPT-5.5 赢了 Opus 4.7 和 Mythos?奥特曼晒黄仁勋内部信:英伟达全员用上 Codex! Cloudflare 推出 Think:一款面向 AI 代理的持久化运行时 1850亿美元天价支出、75%代码由AI生成!谷歌正式宣告:全面转向智能体工作流 xAI落后太多,马斯克“开大”重金求购Cursor,100亿美金“分手费”都敢签! Pulumi 新增对 Bun 运行时的全面支持 姚顺雨腾讯模型首秀!不卷参数只做 “听话打工人”,Hy3 preview登场 | 附实测 老板让你“忽悠”投资人,你敢发给龙虾吗?_AI&大模型_InfoQ 中文站_InfoQ精选视频 Gemini CLI 引入子代理机制,实现任务委派与并行代理工作流 清华系团队星工聚将完成数千万天使轮融资,轮式机器人拿下头部制造企业亿级大单 Pretext.js 绕过 DOM 布局重排,实现 120 FPS 的高级交互体验 靠“AI 云”爆红的 Vercel,栽在一个第三方AI工具手里!IPO前夕遭黑,200万美元赎金谈崩? 高能研讨会|端侧 AI 正在重写实时感知效率上限_AI&大模型_王玮_InfoQ精选视频 2050大会看这篇就够了|报名、交通食宿指引大全 Java 近期资讯:OpenJDK JEP、Jakarta EE 12、Spring Framework、Micrometer、Camel、JBang 金融智能的架构编排:基于 Snowflake Cortex Agents 实现结构化与非结构化数据统一分析 | 技术实践 在AK大神爆火的任务里,摸清国产AI真实水平 百灵Ling-2.6-flash 正式发布:高 Token 效率,以 1/10 消耗实现 SOTA 级 Agent 能力 当 PM 懂AI,当技术懂产品:AI 时代产品力的双向进化|PM x AI产品力领航者大会即将开幕 为 AI 智能体设计记忆机制:揭秘 LinkedIn 的认知记忆智能体 获奖名单公布|2026主题征文第一期|分享你最有价值的龙虾场景与核心 Skill_热门活动_InfoQ写作社区官方_InfoQ写作社区
ClickHouse十大最佳实践技巧
ClickHouse凌敏张琰梓 · 2026-05-26 · via InfoQ - 促进软件开发领域知识与创新的传播

ClickHouse 是一款开源的列式数据库管理系统,专为对海量数据集进行实时分析查询而设计。它擅长在数毫秒内聚合数十亿行数据,使其成为分析平台、可观测性系统、实时仪表盘和数据仓库的流行选择。ClickHouse 通过其列式存储格式、高效压缩和向量化查询执行来实现这一目标,但要获得最佳性能,需要理解如何与其架构协同工作。

尽管 ClickHouse 具备卓越的开箱即用性能,但设计不佳的 Schema、低效的查询或次优的配置都可能浪费大量的性能潜力。一个原本能在数毫秒内返回结果的表,可能需要数秒。能实现 50 倍压缩的存储,可能最终只能达到 10 倍。这种性能差距往往源于未能理解 ClickHouse 如何存储、压缩和查询数据,并应用正确的技术来使您的用例与其优势相匹配。

无论您是每天插入数十亿事件、运行复杂的分析查询,还是努力降低存储成本,正确的优化都能显著提升性能和效率。对数据类型、表引擎或排序键的微小调整,都可能带来数量级的改进。

在本文中,我将分享 10 项最佳实践,这些是我作为 ClickHouse 解决方案架构师,在日常与客户紧密合作中发现能够带来最大影响的。它们并非纸上谈兵的理论建议,而是我在各种规模的部署中反复运用并取得成效的模式,涵盖了从 Schema 设计、数据建模到查询优化和监控等多个主题。

将 ClickHouse 与 AI 代理一同使用? 如果您正通过 AI 代理或大型语言模型 (LLM) 应用程序查询 ClickHouse,请查阅 ClickHouse best practices for AI agents,获取针对该用例的专属指南(https://clickhouse.com/blog/introducing-clickhouse-agent-skills)。

1. 选择合适的主键和排序键

在 ClickHouse 中,表定义中的 ORDER BY 子句是您做出的最关键决策之一。它决定了数据在存储中的物理排序方式,这直接影响查询通过主索引剪枝 (primary index pruning) 跳过无关数据的效率。同时,它还会影响压缩效率,因为排序后的数据中相邻行通常共享相似值,从而能实现更高的压缩比。

ClickHouse 写入数据时,会根据您指定的 ORDER BY 列对行进行排序,并在内存中存储每个数据颗粒(granule,默认为 8,192 行)的首个值。在查询时,对这些列应用的过滤器可让 ClickHouse 跳过那些无法包含匹配数据的整个数据颗粒。

关键在于使您的 ORDER BY 顺序与最常见的查询模式保持一致。优先放置像 tenant_idregioncategory 这样的低基数(low-cardinality)列,随后是基于时间的列。应避免以 UUIDs 或 timestamp 等高基数(high-cardinality)字段开头,因为它们几乎无法提供裁剪优化效果。

让我们以包含逾 1.5 亿行数据的 Amazon reviews dataset 为例。假设一个表默认按 (marketplace, customer_id, review_date) 排序,执行以下查询:

SELECT product_category,       toStartOfMonth(review_date) AS month,       count()                     AS review_count,       avg(star_rating)            AS avg_ratingFROM   amazon_reviewsWHERE  product_category = 'Electronics'       AND toYear(review_date) = 1999GROUP  BY product_category,          monthORDER  BY month;

复制代码

它会执行一次全表扫描,遍历全部 1.5 亿行数据以查找极小部分数据。如果我们更改表的 ORDER BY 顺序为 (product_category, review_date),我们的查询将基于这些列进行过滤,从而使相同的查询运行速度 提升 3 倍,同时所需扫描的数据量 减少 347 倍。对于相同的查询和数据集,如果 ORDER BY 顺序与查询模式匹配,便能带来显著的不同。

ClickHouse Blog Banner-Tips-1.jpg

2. 使用高效数据类型

ClickHouse 中的数据类型不仅仅关乎正确性,它们直接影响存储大小、压缩比和查询速度。选择适合数据的最小类型,除非 NULL 值确实具有实际意义否则避免使用 Nullable (可为空) 类型,对于低基数文本列使用 LowCardinality(String) (低基数字符串) 类型,以及对于固定值集合优先选择 Enum (枚举) 类型而非自由文本字符串,可以显著提升性能和存储效率。同样的逻辑也适用于整数类型,当数据范围允许时,使用 UInt8UInt32 代替 UInt64 意味着每次查询需要读取、解压缩和处理的数据量更少。 被标记为 Nullable (可为空) 的列要求 ClickHouse 额外存储一个 UInt8 列来追踪 NULL 值,这会增加存储和查询执行的双重开销。因此,除非 NULL 值确实具有实际意义,否则最好避免使用 Nullable 类型。在大多数情况下,一个合理的默认值可以作为有效的替代方案:文本字段使用空字符串、数值计数使用 0、或者对于 0 是有效条目的 ID 字段使用像 -1 这样的哨兵值。对于值集有限的字符串列,LowCardinality(String) (低基数字符串) 类型在底层使用字典编码,这使得它在列中不同值少于约 10,000 个时效率更高。

让我们继续以拥有 1.5 亿行数据的 Amazon 评论数据集为例。一个设计不佳的表,其中许多列是 Nullable (可为空) 类型、数值字段过大、低基数文本列使用普通 String 类型,会占用 30.16 GB 存储空间。通过优化,即通过删除 Nullable (可为空) 类型、调整数值列大小、并在适当位置应用 LowCardinality(String) (低基数字符串) 类型,将其切换到更合适的数据类型后,存储空间可降至 26.8 GB。但其价值不仅仅体现在存储方面,它对性能也有显著提升,如下例所示,查询速度提高了 2 倍

3. 考虑分区策略,或避免使用分区

ClickHouse 中的分区 (partitioning) 是最容易被误解的特性之一,最常见的错误是将其用作性能优化手段。ClickHouse 中的分区主要是一种数据管理特性,而非通用的性能加速器。ClickHouse 通过主索引剪枝 (primary index pruning) 在数据跳过方面已经极其快速。在此基础上再进行分区,很少有帮助,反而常常适得其反。原因是 ClickHouse 需要大的数据块 (parts)(通常高达 150GB,常含数十亿行数据)才能高效地进行压缩和查询,并且数据块 (parts) 永远不会跨分区边界合并。过度分区,例如按天或按高基数 (high-cardinality) 列(如 tenant_id)进行,常常导致大量小数据块 (parts)、合并速度变慢、内存占用增加以及查询性能下降。一个好的经验法则:如果您创建了超过几十个分区,您很可能正在过度分区。

那么何时应该进行分区呢?在两种情况下,对数据进行分区是很有价值的。第一种情况是基于 Time-to-Live (TTL) 的数据过期:按月或按年分区可以高效地删除整个旧数据分区,而无需触发变异 (mutation) 或合并 (merge) 操作,这对于大型数据集来说比行级 TTL 效率高得多。第二种情况是与 ReplacingMergeTree、CollapsingMergeTree 或 AggregatingMergeTree 等合并型表引擎 (merge-oriented table engines) 配合使用时,通过为历史分区保留一个数据块 (part),我们可以在带有 FINAL 修饰符的查询中获得显著的性能提升。

除了这两种情况,在添加 PARTITION BY (分区子句) 之前请务必谨慎考虑。默认的不分区,或者简单的按月或按年分区,通常是正确的选择。

为了说明不必要分区的成本,我们在两个结构相同的表上,使用了包含 1.5 亿行 Amazon 评论的同一份数据集进行测试。其中一个表按 review_date 的月份进行分区,另一个则未分区。数据摄入时间大致相同(294 秒 对 314 秒),但分区表在加载过程中消耗了多达 55% 的内存(4.71 GB 对 3.03 GB)。真正的性能损耗体现在查询阶段。一个针对所有 product_category 值进行简单聚合的查询,在未分区表上仅需 0.4 秒,而在分区表上则耗时 20 秒。尽管扫描了完全相同的行数,但性能下降了 46 倍。一个按 helpful_votes 进行 Top-100 排序的查询也展现了类似但程度稍轻的性能差异:未分区表耗时 40 秒,分区表则耗时 92 秒。同样的数据,同样的查询,速度却慢了一倍以上。由于这两个查询均未对review_date 进行过滤,分区并未带来任何剪枝 (pruning) 优化。相反,数据碎片化反而增加了每次扫描时的合并与调度开销。

ClickHouse Blog Banner Tips-3.jpg

4. 使用跳过索引 (Skipping Indexes) 优化数据扫描

ClickHouse 的主索引是基于 ORDER BY 字段构建的稀疏索引,是实现快速数据访问最强大的工具。但在实际应用中,查询并非总能通过主键列进行过滤。在这种情况下,跳过索引 (Skipping Indexes) 能够将相同的数据粒度剪枝 (granule-pruning) 能力扩展到数据模型中的任意其他列。跳过索引是与数据一同存储的辅助索引,它不会改变数据的物理存储和排序方式。

跳过索引有多种类型,我们可以将它们大致分为两类:轻量级索引 (Lightweight Indexes) 和重量级索引 (Heavyweight Indexes)。轻量级索引对写入性能和存储的影响微乎其微,因此可以在任何有助于提升查询效率的地方自由添加。而重量级索引则会带来更高的存储开销和写入放大 (write amplification) 成本。因此,只有当查询加速效果显著且足以抵消这些额外开销时,才值得考虑使用。

轻量级索引 (Lightweight Indexes):

  • minmax - 针对每个数据粒度 (granule) 存储其最小值和最大值。最适用于数值或日期列,对字符串列也可能有所帮助。其构建与维护成本极低,存储开销几乎可以忽略不计;

  • set - 针对每个数据粒度存储一小组独特的(distinct)值。最适合经常用于过滤条件但未包含在 ORDER BY 子句中的低基数 (low-cardinality) 列。可以通过 set(0) 来存储所有唯一值,或者使用 set(N) 来设定上限,当超出此上限时,查询将回退到全表扫描。

重量级索引 (Heavyweight Indexes):

  • bloom_filter - 一种概率型数据结构,用于判断“某个值是否确定不在当前数据块中”。最适用于高基数字符串列,例如 ID 或 URL。它允许存在误报,但绝无漏报。由于会带来显著的存储和写入开销,因此仅当其带来的扫描优化能够抵消其成本时才应使用;

  • ngrambf_v1 / tokenbf_v1 - 是 bloom_filter 的变体,专门针对自由文本列上的 LIKE 或 hasToken 查询进行了优化。虽然在子字符串和词元(token)搜索方面表现强大,但其构建和存储成本较高,因此仅应在经常进行搜索的列上使用;

  • Text - 一种全新的(自 26.2 版本起正式发布,即 GA)文本搜索专用倒排索引,其功能类似于 Lucene/Elasticsearch 等系统中的倒排索引。它支持高精度的精确词条(term)、前缀和子字符串匹配。它是文本搜索场景中最强大的选择,但同时在存储和写入放大方面也是开销最大的。当 Bloom_filter 无法满足您的性能需求时,可选用 Text索引。

通过 Amazon 评论数据集可以很好地说明跳过索引(skipping index)的优势。当执行一个 total_votes > 1000 的过滤查询时,如果没有跳过索引,系统将对全部 1.5 亿行数据执行全表扫描。若在 total_votes 列上添加一个 minmax 索引(这是开销最低的索引之一),扫描的行数将降至 2900 万行,实现高达 80% 的数据扫描量减少,且几乎不带来额外开销。

5. 利用 JSON 数据类型处理半结构化数据

ClickHouse 的原生 JSON 数据类型是处理半结构化数据的强大工具,特别适用于那些键值不可预测、随时间变化或包含多种类型值的场景。它能够在插入数据时自动推断类型,并将每个被发现的路径存储为独立的子列(受限于 max_dynamic_paths 的定义),从而为动态数据提供列式存储的性能优势。

然而,这种灵活性并非没有代价。JSON 类型在每次数据插入时都会执行类型推断,这相比静态 Schema 会带来额外的开销。此外,当同一路径下包含多种类型的值时,它还会占用更多的存储空间。对于结构已知且一致的数据,即使数据以 JSON 格式传输,采用带有显式列类型的静态 Schema 也将始终比 JSON 类型表现出更优的性能。

在使用 JSON (JavaScript Object Notation) 数据时,一个关键参数是 max_dynamic_paths,它控制 ClickHouse 将多少个不同的 JSON 路径作为单独的子列进行存储。默认情况下,一旦超出此限制,其他路径将被存储在一个共享结构中,这会降低查询效率。默认值为 1024,但对于路径集有限且结构明确的有效载荷,将其设置得更低,可以使数据结构更紧凑、更可预测。当您知道某些路径始终存在且类型固定时,可以使用 JSON 提示 (hints) 明确声明这些路径。例如:

CREATE TABLE events (  id UInt64,   payload JSON(\`timestamp\` DateTime, \`level\` LowCardinality(String))) ENGINE = MergeTree ORDER BY id;

复制代码

提示 (Hints) 为 ClickHouse 提供了有关这些路径的更多信息,这些路径像常规列一样被存储和压缩,而有效载荷的其余部分则保持完全动态。在将 Amazon Reviews 数据集转换为文档型数据集时,相比不使用提示的 JSON,使用提示可将存储空间减少 38%;同时,此示例查询的速度在使用提示时比不使用提示时提高了 26%。

SELECT count(*),       review_data.product_category PCFROM   amazon_reviews_jsonGROUP  BY pc

复制代码

然而,这不仅是为了优化存储和提升性能,提示路径也是跳过索引 (skipping indexes) 的可靠目标,而完全动态的路径在不同数据粒度 (granules) 之间可能存在不一致,从而导致索引效率较低。需要指出的是,虽然可以在任何 JSON 路径上添加跳过索引,但这需要进行类型转换 (casting)。

总而言之:如果数据结构扁平且可预测,应使用显式列 (explicit columns)。如果数据存在可预测的核心结构,但伴随动态变化,则考虑对已知部分使用静态列 (static columns),其余部分使用单个 JSON 列。只有当数据模式完全不可预测时,才应考虑使用完全动态的 JSON 列。

6. 正确地将数据导入 ClickHouse

如何高效地将数据插入 ClickHouse 是一个值得深入探讨的重要议题。数据摄取 (ingestion) 模式通常有四种,每种模式都有其推荐的方法和最佳实践。

对象存储 (Object Storage)(如 Amazon S3、GCS、Azure Blob)是批量加载最常见的数据源之一。在有格式选择时,建议优先选择 Parquet 或 ORC 等列式格式,而不是 JSON 或 Avro 等行式格式;ClickHouse 可以仅从 Parquet 和 ORC 格式中读取所需列,完全跳过其余部分,而 JSON 则需要解析每一行的每个字段。即使在加载所有列时,列式格式也更快,因为数据到达时已按照 ClickHouse 内部存储的方式组织,从而减少了摄取过程中的转换开销。加载 Amazon reviews 数据集清楚地说明了这一点:Parquet 和 ORC 格式加载耗时 79 秒,Avro 格式耗时 94 秒,JSON 格式耗时 105 秒

对于从对象存储进行托管式持续数据摄取,ClickPipes 直接支持 S3 和 GCS 数据源。

数据库的 CDC (Change Data Capture)(如 Postgres、MySQL、MongoDB 等)和 事件流(如 Kafka、Kinesis 等)最好通过 ClickHouse Cloud 的原生托管式摄取服务 ClickPipes 来处理。ClickPipes 开箱即用地支持模式映射、偏移量管理、错误处理和背压。针对 CDC,它采用基于日志的方法,以最小的源数据库负载捕获每一行级别的变更。如果您的数据库位于私有 VPC (Virtual Private Cloud) 中,ClickPipes 支持反向 PrivateLink,从而实现安全连接,而无需将数据库暴露给公共互联网。

后端应用程序 直接写入 ClickHouse 是企业非常普遍的做法,但这确实需要考虑一些事项。ClickHouse 针对大型、不频繁的批次进行了优化,而非应用程序代码中常见的小型、频繁的插入。每次插入都会在存储层中创建至少一个部分 (part),而过多的小部分会导致合并压力 (merge pressure)、内存使用量升高,并最终导致插入限流 (insert throttling)。两种解决方案是:客户端批量处理(累积行并在每隔几秒或几千行时刷新),或者启用 异步插入 (async inserts),这让 ClickHouse 自动缓冲传入的插入并以批次形式刷新它们:

SET async_insert = 1;SET wait_for_async_insert = 1;

复制代码

wait_for_async_insert = 1 时,客户端会等待数据写入分片(part)的确认,这为您提供了小批量写入的便利,并具备适当的确认机制和可靠的错误处理能力。您可以通过 system.asynchronous_insert_log 监控异步插入行为,从而针对您的工作负载调整刷新间隔和缓冲区大小。

无论采用何种数据导入方式:请避免一次只插入一行数据;在可能的情况下,优先选择原生二进制格式而非 JSON;并通过监控 system.parts 中的分片(part)数量,尽早发现数据导入问题。

7. 写入时计算,借助物化视图(Materialized View)和投影(Projection)实现更快的读取

物化视图和投影都遵循相同的核心思想:在数据插入时执行计算,从而使读取操作更快,并减少计算资源消耗。与在查询时进行扫描和聚合不同,您可以选择在数据到达时预先计算并存储结果。两者的权衡取舍是一致的:更快的读取是以增加存储空间和额外的写入开销为代价的。

投影是存储在同一表内部的替代排序方式或预聚合数据。当 ClickHouse 执行查询时,如果查询的过滤和排序模式与某个投影匹配,它会自动选择最佳投影,因此无需对查询进行任何修改。这使得它们对应用程序透明,易于采用。缺点在于,每次插入操作都必须为每个投影写入和排序数据,这会增加插入延迟和存储占用。在围绕投影制定查询优化策略之前,有必要验证它们是否真正在查询时被选中。最简单的方法是启用以下设置:

SET force_optimize_projection = 1;

复制代码

启用此设置后,如果您的查询没有找到合适的投影,ClickHouse 将抛出错误,从而立即明确您的投影是正在被使用,抑或是被默默忽略。

关于投影,需要强调的一点是,它们常常被“以防万一”地添加,这会影响存储和写入成本。首先,应该使用设计良好的主键,并识别出实际运行缓慢的查询,然后只在真正需要的地方添加投影。让实际使用数据来指导投影的定义。

物化视图 (Materialized views) 有两种类型。

可刷新物化视图 (Refreshable materialized views) 的工作方式与传统数据仓库中的预期一致,它们会按计划重新计算结果,因此适用于复杂的转换。但这种方式通常需要管理已处理数据的“书签”,以区分已处理和未处理的数据;在遇到延迟数据或历史数据回填时,需要进行重新处理;同时强烈建议在处理过程中考虑幂等性。

增量物化视图 (Incremental materialized views) 是 ClickHouse 的独特之处,它们更灵活,但也需要更精心设计。它们充当插入触发器,对每个传入批次运行 SELECT 查询并将结果写入目标表。这使得它们在数据到达时,能够极其高效地持续维护聚合、汇总或扇出数据管道。一个重要的限制是,它们仅在插入操作时触发:对源表的删除和更新操作不会传播,因此它们最适合仅追加或不可变的数据模式。

增量物化视图中的连接操作值得特别关注,因为只有连接中的左表会触发视图更新。如果右侧表发生变化,物化视图将不会更新。此外,物化视图的良好可组合性也值得了解:单个源表可以“扇出”到多个物化视图(MV),每个 MV 维护不同的聚合或转换;同时,来自不同源表的多个 MV 也可以汇聚到同一个目标表。这使得它们成为构建更复杂数据管道的强大基石。

在 ClickHouse 中,一种常见模式是利用物化视图来维护用于仪表盘和高频查询的预聚合汇总表,同时保留原始表以进行即席探索。

8. 了解你的系统表

ClickHouse 的系统表是其最强大的内置功能之一。集群中发生的一切,包括查询、合并、后台活动和错误,都被捕获并可以通过标准 SQL 进行查询,从而使用标准 SQL 为你提供深入的可观测性。

在多副本服务中,查询系统表仅会显示当前查询所在副本的日志。若要获取所有副本的完整视图,需使用clusterAllReplicas。此外,由于许多系统表会轮转,历史数据可能不会直接显示,除非你显式地通过 merge 表函数对它们进行合并。以下是如何查询 system.query_log 以确保获取表中所有服务日志的示例:

SELECT event_time, query_id, query, typeFROM   clusterAllReplicas('default', merge('system', '^query_log*'))WHERE  event_time > Now() - toIntervalMinute(5);

复制代码

system.query_logsystem.parts 是两个最值得熟悉的系统表。

system.query_log 是理解查询行为的主要工具。每个查询会根据事件(QueryStartQueryFinishExceptionBeforeStart ExceptionWhileProcessing)生成一行记录,从而提供服务上所有查询的完整生命周期视图。每行数据会捕获查询耗时 (query_duration_ms)、资源使用情况 (read_rows、read_bytes、memory_usage)、查询文本本身,以及所涉及的数据库、表、列和投影。在错误排查时,还可以利用 exception_codeexceptionstack_traceProfileEvents 列则提供了更深入的洞察,它是一个低级执行计数器的映射,能够精确揭示时间开销分布,从 CPU 周期到 I/O 读取再到缓存命中。当查询速度低于预期时,ProfileEvents 常常能帮助我们判断瓶颈是在 I/O、CPU 还是网络。

system.parts 表详细展示了您的存储中所有 MergeTree 系列表(MergeTree-family tables)的每个物理数据部分的信息。每行对应一个数据部分,使其成为监控存储、诊断合并行为和理解表健康状况的理想选择。其中最关键的列包括:active 指示一个数据部分是当前活跃的,还是已完成合并后留下的旧部分;因此,通过 active = 1 进行过滤,可确保查询只关注活跃的相关部分。partitionpartition_id 显示了每个数据部分所属的分区,而 rowsbytes_on_diskdata_compressed_bytesdata_uncompressed_bytes 则清晰地展示了数据部分的大小和压缩效率。part_type 区分 WideCompact 两种数据部分,它们决定了列的存储方式。在 Wide 格式中,每个列都存储在各自独立的文件中,这是较大数据部分的标准格式,并在读取时实现了高效的列裁剪(column pruning)。Compact格式将所有列存储在单个文件中(默认小于 10MB),这减少了文件句柄(file handle)的数量,对于行数较少的小型数据部分而言,效率更高。

值得我们随时取用的两个查询是:

各表的数据部分数量和大小:

SELECT table, count() AS parts, sum(rows) AS total_rows,       formatReadableSize(sum(bytes_on_disk)) AS size_on_diskFROM   system.partsWHERE  activeGROUP  BY tableORDER  BY parts DESC;

复制代码

过度分区表:

SELECT table, partition, count() AS partsFROM   system.partsWHERE  activeGROUP  BY table, partitionHAVING parts > 10ORDER  BY parts DESC;

复制代码

9. 精通 ReplacingMergeTree

ReplacingMergeTree 是 ClickHouse 中备受欢迎的表引擎之一,它用于支持需要去重(deduplication)或更新插入(upserts)的场景。此表引擎根据指定列(例如版本/时间戳)保留每行的最新版本。去重操作依据 ORDER BY 列的唯一性进行。旧的重复数据会在后台合并(background merges)过程中被舍弃。需要注意的是,这些合并是异步进行的,这意味着在查询时,表中可能仍然存在重复行。若要获取正确的结果,需要使用 FINALargMax 模式,并深入理解它们之间的权衡。

FINAL 是最简单的方式,只需将其添加到查询中,ClickHouse 就会透明地处理去重操作。然而,代价是 FINAL 在返回结果之前必须协调(reconcile)所有的数据部分,其性能与查询时存在的数据部分数量直接相关。在一个合并良好、分区中只有一个数据部分的表上,使用 FINAL 与否,性能差异不大。而在一个处于数据摄取(ingestion)中期、存在大量数据部分的表上,它可能带来显著的性能开销。

SELECT star_ratingFROM   mytests.amazon_reviews_rmt FINALWHERE  review_id = ''

复制代码

argMax 模式是一种替代方案,它将去重逻辑整合到聚合操作本身中,从具有最高版本的行中选取值:

SELECT argMax(star_rating, review_date)FROM   mytests.amazon_reviews_rmtWHERE  review_id = ''

复制代码

在包含 1.52 亿行(1.5 亿原始行 + 200 万重复行)的 Amazon reviews 数据集上,这两种方法的性能差异与表的状态密切相关。在存在 9 个未合并的数据分块时,上述使用 FINAL 的查询耗时 1.5 秒,而 argMax 耗时 1.0 秒。为了展示数据分块更少时的差异,我们强制将这些数据分块合并为一个单一分块,此时两种方法的性能均下降到大致相同的水平:0.48 秒对比 0.40 秒。尽管具体结果可能因查询形态、基数和数据分块数量而异,但这一规律依然成立:无论合并状态如何,argMax 都表现出更高的稳定性,而 FINAL 则随着数据分块的合并,性能显著提升。

实际上,对于日常查询或当表数据合并良好时,FINAL 是一个更简单的选择。而当处理有活跃写入的表,并且需要可预测的延迟时,argMax 则更值得选用。

在生产环境中,减少 FINAL 性能不稳定性的一种方法是配置后台合并,使其对旧数据处理得更积极。默认情况下,ClickHouse 会根据内部启发式算法合并数据分块,该算法会考虑数据分块的大小、数量和创建时间,但并没有强制将一个分区的数据合并成一个单一分块的机制。这意味着一个表可以无限期地保持每个分区拥有多个数据分块,从而导致 FINAL 持续产生额外开销。min_age_to_force_merge_seconds 设置改变了这一默认行为,它会强制 ClickHouse 不断合并早于指定阈值的数据分块,直到每个分区只剩下一个数据分块:

min_age_to_force_merge_seconds = 600, min_age_to_force_merge_on_partition_only = 1;

复制代码

请注意,这样做可能会增加后台合并的负载,因为 ClickHouse 会持续合并数据分块直到每个分区只剩下一个,从而占用更多本可用于查询或写入的 CPU 和 I/O 资源。

min_age_to_force_merge_on_partition_only = 1 标志确保此操作仅在所有数据块(parts)都已足够旧的分区上触发,从而避免干扰仍在活跃写入的分区。值得注意的是,为了使此设置在实践中有效,表应该进行分区。如果没有分区,所有数据将存储在单个分区中,可能积累过多的数据。由于 ClickHouse 默认不会合并会使数据块大小超过 150GB 的分块,因此将所有数据整合为单个数据块变得不切实际。通过按月或按年分区,每个分区都将保持在可控的尺寸范围内,从而能够合并为单个数据块,这正是 FINAL 操作性能最佳的状态。

10. 优化你的 JOIN

过去,ClickHouse 中的 JOIN 曾是建议用户谨慎使用的一个特性,普遍的建议是尽可能通过反范式化(denormalization)、字典(dictionaries)或物化视图(materialized views)来避免使用 JOIN。这一建议在当时是合理的,但显著的引擎级改进使 JOIN 在高并发生产工作负载中变得越来越可行。作为默认查询执行层引入的 Analyzer(查询优化器)为 JOIN 规划带来了重大改进:ClickHouse 24.4 引入了更好的谓词下推(predicate pushdown)功能,通过将过滤条件推送到 JOIN 的两侧,可将查询性能提升 10 倍;版本 24.12 获得了自动重新排序双表 JOIN 的能力,将较小的表放在右侧;25.9 则将此功能扩展到连接三个或更多表的查询。结合多种 JOIN 算法可供选择以平衡不同的内存和性能考量,如今 ClickHouse 中的 JOIN 其能力显著增强,也更容易正确使用,甚至比一年前有了巨大的提升。

尽管如此,JOIN 在分析型数据库中仍然伴随着开销,因此有几项原则值得遵循。对于对毫秒级延迟有严格要求的实时工作负载,目标是每个查询最多包含 3 到 4 个 JOIN。此外,反范式化、字典或预聚合物化视图是值得考虑用于进一步提升查询性能的工具。

对于静态或变化缓慢的查找,推荐使用字典。当需要用不常变动的小型参考表数据来丰富大型表时,字典的性能将优于常规连接。字典会完全加载到内存中,并通过 dictGet 进行访问,从而彻底绕过哈希连接过程。以客户元数据丰富后的 Amazon reviews 数据集为例,性能差异显著:在 1.5 亿行数据上执行常规 JOIN 操作耗时 2.3 秒,与字典表进行连接耗时 1.36 秒,而 dictGet 操作仅需 0.86 秒;这比基准连接快了近 3 倍,且底层数据无需修改。

ClickHouse Blog Banner Tips-6.jpg

总结

ClickHouse 开箱即用即表现出极快的速度,但要充分发挥其潜力,就必须深入理解其数据存储、合并和查询机制。本文介绍的最佳实践并非孤立存在,而是相辅相成。精心选择的 ORDER BY 子句通常能使跳跃索引 (skipping indexes) 更为高效。恰当的数据类型能够减轻物化视图和投影的工作负担。明智的分区策略能让 ReplacingMergeTree (ReplacingMergeTree) 和基于 TTL 的过期策略 (TTL-based expiration) 有序运行。优化数据摄取过程能保持健康的数据分片数量,进而确保 FINAL 操作的执行效率。

本文中贯穿使用的 Amazon reviews 数据集基准测试表明,这些并非微不足道的提升:选择正确的主键能将扫描数据量减少 347 倍,正确的数据类型能将存储空间削减 12% 并将查询时间缩短 50%,不必要的分区可能导致查询速度降低 46 倍,而字典查找的性能可比常规连接快 3 倍。这些都是纯粹源于设计决策而非硬件升级所带来的数量级差异。

如果你是 ClickHouse 的初学者,应重点关注前两点:主键设计和数据类型。它们的影响最为广泛,适用于你创建的每个表。在此基础上,在查询需要时添加跳跃索引,仅在有明确理由时进行分区,并根据具体用例需求采用物化视图和 ReplacingMergeTree。

ClickHouse 会回馈那些深入理解其架构的用户。你的 schema 和查询与 ClickHouse 数据管理方式越是契合,你的系统就将越快、越高效。在理想情况下,这意味着你能够摄取数十亿行数据,并以毫秒级的速度查询它们。

/END/

征稿启示

面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出 &图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com。