50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

性能飞跃!ClickHouse 处理复杂 JSON,速度提升 58 倍,内存减少 3300 倍

  • 2025-10-23
    北京
  • 本文字数:3008 字

    阅读完需:约 10 分钟

大小:1.46M时长:08:28
性能飞跃!ClickHouse 处理复杂 JSON,速度提升58倍,内存减少3300倍

2024 年 8 月,ClickHouse v24.8 引入了功能强大的 JSON 数据类型。从那时起,我们持续通过引入新特性和优化手段不断改进它。


本文将介绍我们如何在 ClickHouse v25.8 中再次实现重大性能提升。


ClickHouse 一直是分析性能方面的行业标杆,而 v25.8 的最新改进也使 ClickHouse 成为处理 JSON 数据分析的领先方案。

v24.8 中 JSON 类型的工作原理 


我们先快速回顾一下,在 ClickHouse v24.8 中,JSON 数据在 MergeTree 分区中的存储方式(https://clickhouse.com/blog/a-new-powerful-json-data-type-for-clickhouse)。


如下图所示,我们有一组包含 K 个唯一路径的 JSON 数据,每个路径对应一个字符串值。前 N 个路径作为“动态路径”被存储为子列。其余 K – N 个路径则被存储在一个共享的数据结构中,即一个 Map(String, String) 类型的列,包含路径和值的映射。



例如,当我们查询路径 key1 的数据时,ClickHouse 首先会通过元数据判断 key1 是属于动态路径还是共享数据。在本例中,key1 属于动态路径,因此其值被存储在独立的数据文件中,能够被高效、直接地读取。



但如果我们查询的是 key_n+1,元数据会显示该路径不在动态路径中,而是存储在共享数据中。此时,ClickHouse 需要读取整个 Map(String, String) 列并在内存中进行过滤处理,效率显著下降。



默认情况下,动态路径的数量限制为 1024。当路径数量超过此限制时,尤其是在使用 S3 等远程存储时,贸然提高该限制并不可取,因为每个分区可能生成成千上万的文件,这会在数据合并过程中显著增加内存使用量,并加剧读取复杂度。


因此,对于包含成千上万乃至数万个唯一 JSON 路径的工作负载,系统性能会明显下降。

这正是我们希望解决的难题。


v25.8 引入的新共享数据序列化格式


ClickHouse v25.8 推出了两种新的共享数据序列化格式,显著提升了读取特定路径时的效率。

分桶式共享数据


第一种新格式通过将共享数据划分为 N 个桶进行组织。每个桶包含一个各自独立的 Map(String, String) 列,并通过固定的规则将路径分配到不同的桶中。


当查询如 key_m1 这类路径时,ClickHouse 可直接判断该路径位于哪个桶中,仅读取对应的桶,其余桶则完全跳过。


相较于读取整个共享数据,这种方式大幅减少了扫描的数据量,从而提高了查询性能。但需要注意的是,若桶的数量设置过多,文件数量也会随之增加,容易带来文件系统的额外负担。

高级共享数据


第二种方式采用了更为强大的高级序列化格式。


在该格式中,每个桶包含以下三个文件:


  • .structure:每个 granule(数据块)的元信息,包括行数、包含的路径列表,以及在 .paths_marks 文件中的偏移位置;

  • .data:实际路径数据,按列式格式按 granule 存储;

  • .paths_marks:记录每个路径在 .data 文件中起始位置的偏移指针。



当查询路径 key_m1 时,ClickHouse 首先读取 .structure 文件,判断当前 granule 是否包含该路径;如果不包含,则跳过该 granule;如果包含,则通过 .paths_marks 文件找到其偏移位置,并直接跳转到 .data 文件中对应的路径数据进行读取。


这种方式避免了无关路径数据被加载进内存,从而极大提升了查询效率。

支持嵌套路径


许多 JSON 文档包含嵌套结构,例如对象数组。通过上述方法提取这类嵌套路径时,仍需读取整个数组,对于处理大型数据负载而言效率较低。


为了解决这一问题,我们对高级序列化格式进行了扩展,引入了用于子列处理的额外文件。


在该扩展格式中,每个桶包含以下 6 个文件:


  • .structure:每个 granule 的元信息,包括行数、路径列表,以及在 .paths_marks 和 .substreams_metadata 文件中的偏移位置;

  • .data:实际路径数据,以列式格式按 granule 存储,并被划分为多个子流(substreams)。同一路径可能对应多个子流。该结构允许 ClickHouse 仅读取重建目标子列所需的子流,而无需扫描整个路径值;

  • .paths_marks:指向 .data 文件中每个路径数据起始位置的偏移;

  • .substreams_marks:指向 .data 文件中每个子流数据起始位置的偏移;

  • .substreams:记录每个 granule 中各路径对应的子流列表。不同 granule 中可能存在差异,例如某些数组对象可能包含不同的嵌套字段;

  • .substreams_metadata:为每个路径记录其在 .substreams 和 .substreams_marks 文件中的偏移,建立路径与其子列及数据位置之间的映射关系。



当查询路径 key_m1 的子列时,ClickHouse 首先读取 .structure 文件,判断当前 granule 是否包含该路径。如果不包含,则直接跳过该 granule;如果包含,则根据 .structure 中记录的偏移,读取 .substreams_metadata 文件中对应项,获取 key_m1 的偏移信息。


接着,ClickHouse 根据第一个偏移,从 .substreams 文件中读取该路径在当前 granule 中的子流列表。如果列表中不包含所需的子流,该 granule 会被跳过;若包含所需子流,则 ClickHouse 会根据第二个偏移,从 .substreams_marks 中读取子流位置,并仅从 .data 文件中读取这些子流的数据。


在成功重建目标子列后,ClickHouse 会继续处理下一个 granule。



这种方式避免了加载不相关路径及其无关子流的数据,从而显著提升了嵌套子列的读取性能。

平衡效率与兼容性


高级序列化格式在支持选择性读取方面具备显著优势,但也带来了一定的性能开销:当需要读取整个 JSON 列或执行合并操作时,由于共享数据在内存中的表示方式(Map(String, String))与其存储布局差异较大,会引发较重的数据结构转换,导致性能下降。


为此,ClickHouse v25.8 采取了一种折中的方案:在保存高级格式的同时,附加存储一份原始格式的数据副本。虽然这会使存储空间需求翻倍,但可以在保留高级格式选择性读取优势的同时,避免整体读取和合并操作中的性能损失。


如下图所示,原始格式副本被存储在三个额外的文件中:


  • .copy.offsets:原始 Map(String, String) 列中各项的偏移信息;

  • .copy.indexes:跨桶路径的索引(避免重复存储所有路径);

  • .copy.values:原始 Map(String, String) 列中的实际值。



这种副本机制确保了读取完整 JSON 列和执行合并操作的性能不会下降,同时依然保留了高级格式在选择性读取中的优化效果。

性能评估


我们对包含 10、100、1000 和 10,000 条唯一路径的 JSON 数据,使用新的序列化格式进行了性能基准测试。


测试结果显示,高级共享数据序列化在查询速度和内存使用方面的表现,与将所有路径作为动态子列存储的方式相当,同时具备良好的扩展性,可应对数万路径的复杂 JSON 数据。


以下为 10,000 路径测试的一些关键结果,展示了 v25.8 中新序列化机制带来的显著优化。

性能测试 1(选择性读取)


在本项测试中,我们从包含 20 万行数据的表中读取单个 JSON 键。每行均包含一个含 1 万条路径的 JSON 文档,采用宽格式存储。


测试结果表明,使用高级序列化格式后,相较原始 JSON 序列化方式,读取时间提升约 58 倍,内存使用下降约 3,300 倍。


Data type

Time (seconds)

Memory usage (MiB)

JSON no shared data

0.027

18.64 MiB

JSON shared data "advanced"

0.063

3.89 MiB

JSON shared data "map_with_buckets"

0.087

403.55 MiB

String

3.216

582.70 MiB

Map

3.594

538.37 MiB

JSON shared data "map"

3.63

12.53 GiB

性能测试 2(读取完整 JSON 对象)


本测试从同一数据表中读取每一行的完整 JSON 文档。


测试结果显示,使用高级序列化格式时,读取整个文档的性能几乎与原始 JSON 序列化方式一致。这表明,在保留对选择性读取显著加速的同时,整体读取性能并未受到影响。

结论


全新的共享数据序列化机制将 ClickHouse 对 JSON 数据的支持提升至新高度。借助这一改进,用户可以高效地查询包含数万条唯一路径的复杂 JSON 文档,并在执行选择性读取时获得极佳性能。


这一增强能力进一步巩固了 ClickHouse 在处理大规模半结构化 JSON 数据分析场景中的领先地位。

2025-10-23 14:5831

评论

发布
暂无评论

智采云火了的背后,是企业降本增效的刚需

ToB行业头条

产品网站的FAQ页面该如何编辑?

Baklib

产品 FAQ

Baklib|如何搭建在线帮助中心站点?

Baklib

uniapp引入 iconfont

源字节1号

微信小程序 软件开发 前端开发 后端开发

SQL 碎碎念,你可能用不到但不能不知道的数据库技巧(1)

百里丶落云

数据库 后端 11月月更

马蜂窝毕博:分析完这9点工作原理,我们最终选择了 Apache SeaTunnel!

Apache SeaTunnel

开源 技术选型 数据集成 Seatunnel 数据集成平台

大咖说·我和我的伙伴们|云原生携手禾连健康助力医疗行业发展

大咖说

阿里云 微服务 云原生

python数据分析-开篇什么是数据分析

AIWeker

Python 人工智能 数据分析 11月月更

并发编程中的锁、条件变量和信号量

C++后台开发

Go 并发编程 linux开发 C++开发

Docker PHP 入门实践 (三)

Felix

php Docker thinkphp 11月月更

设计模式学习-基础知识

肥晨

设计模式 11月月更 设计模式基础

喜讯!麦聪DaaS平台荣获“2022行业信息化优秀产品”奖

雨果

数字化转型 DaaS数据即服务 麦聪软件

Redis核心技术

苏格拉格拉

redis 架构 持久化 部署 集群

【10.28-11.04】写作社区优秀技术博文一览

InfoQ写作社区官方

优质创作周报

从演进的视角理解微服务架构

苏格拉格拉

架构 微服务 微服务架构 架构演进

不愧是阿里内部Spring Boot笔记,从头到尾全是干货

小小怪下士

Java spring 程序员 阿里 springboot

Docker PHP 入门实践(二)

Felix

php Docker 实战 11月月更

Redis数据结构

苏格拉格拉

redis 缓存 Redis 数据结构

BI系统打包Docker镜像及部署的技术难度和实现

葡萄城技术团队

Docker 容器 BI

深圳中心化区块链交易所开发安全的重要性

W13902449729

区块链交易所搭建

Docker PHP 入门实践(一)

Felix

php 实战案例 Docker 镜像 11月月更

量子编程实践:Bell Pair电路及Deutsch算法

启科量子开发者官方号

#python #量子计算 #人工智能 #AI框架

一文带你详细了解JVM运行时内存

程序员小毕

Java 程序员 面试 后端 JVM

「百幄」之办公平台:进一道门,办所有事

融云 RongCloud

数字化 办公

Spring Boot「24」DAO 模式与 Repository 模式

Samson

Java spring Spring Boot 学习笔记 11月月更

稳定性治理方法论

苏格拉格拉

方法论 稳定性

QuTrunk与Paddle结合实践--VQA算法示例

启科量子开发者官方号

Python 人工智能 ai框架 量子计算 量子编程

FOTSL:端到端的文本检测与识别方法的原理方法与优势

合合技术团队

人工智能 场景 端口 文本检测 文本识别

集群并发下的数据覆盖问题

苏格拉格拉

缓存 分布式 并发 一致性

LED显示屏有色差要怎么处理?

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家

PingCAP 推出 TiDB Cloud Serverless Tier BETA 版

PingCAP

TiDB

性能飞跃!ClickHouse 处理复杂 JSON,速度提升58倍,内存减少3300倍_AI&大模型_ClickHouse_InfoQ精选文章