
本文字数:11054;估计阅读时间:28 分钟
作者:Melvyn Peignon, Kseniia Sumarokova and Raúl Marín

除非您在过去几年中一直与世隔绝,否则您很可能已经听说过 Delta Lake 和 Iceberg 等开放表格式。
目标很简单:定义一种任何遵循其协议的查询引擎都能读写的数据格式。随着时间的推移,这些格式的功能已远不止简单的互操作性,它们直接在底层数据之上引入了更丰富的表语义,例如事务支持、模式演进和版本化数据管理。
最近,我们宣布 ClickHouse 已为数据湖做好准备,它作为查询引擎能够支持这些表格式。在这一发展过程中,我们与这些格式进行了更深入的协作,并像许多其他用户一样,也遇到了一些相同的挑战。
采用这些表格式并非易事。无论是通过外部库还是自定义实现,支持它们都需要持续跟进复杂且不断演进的协议。每个查询引擎仍需负责维护自己的集成,这通常会导致功能支持碎片化并增加维护开销。
在本文中,我们将探讨 ClickHouse 如何通过集成 Rust Delta Kernel 来解决这些挑战。此举在表格式与 ClickHouse 的查询引擎之间提供了一个可维护且一致的接口,使我们能够支持更多功能,同时显著降低了集成复杂性。

ClickHouse 是一款高性能、面向列的 SQL 数据库管理系统 (DBMS),专为在线分析处理 (OLAP) 而设计。OLAP 指的是对海量数据集执行复杂计算(例如聚合、字符串处理、算术运算)的 SQL 查询,它能够在数毫秒内处理数十亿乃至数万亿行数据。
有关 ClickHouse 的介绍,包括其存在原因及其如何实现高性能,我们推荐阅读入门指南。如需深入了解其速度背后的架构决策,请查阅“为何 ClickHouse 如此之快”系列文章(https://clickhouse.com/docs/concepts/why-clickhouse-is-so-fast)。
在本文的其余部分中,我们将专门探讨 ClickHouse 与 Delta Lake 的集成,深入剖析我们是如何集成 Delta Kernel 的。

让 ClickHouse 具备数据湖 (Data Lake) 就绪能力的关键目标之一,是使用户能够直接从 Delta Lake 等开放表格格式中查询数据。我们最初的方法是直接与 Delta 协议 交互,实现原生支持。虽然这赋予了我们完全的控制权,但也揭示了格式本身的复杂性,以及持续跟进不断演进的规范所带来的维护成本。
SELECT
cityHash64(URL),
count() AS cnt
FROM deltaLake('https://datasets-documentation.s3.amazonaws.com/lake_formats/delta_lake/')
GROUP BY cityHash64(URL)
ORDER BY cnt DESC
LIMIT 5
查询结果如下所示:
Query id: 99b0ef8a-f8e0-4c27-bb8b-33c99733c0c7
┌──────cityHash64(URL)─┬─────cnt─┐
1. │ 8128408125044552281 │ 3288173 │ -- 3.29 million
2. │ 17146718649009901992 │ 1625250 │ -- 1.63 million
3. │ 935030221159485792 │ 791465 │
4. │ 6972355099133249997 │ 582400 │
5. │ 13961208022208327194 │ 514984 │
└──────────────────────┴─────────┘
5 rows in set. Elapsed: 3.878 sec. Processed 100.00 million rows, 14.82 GB (25.78 million rows/s., 3.82 GB/s.)
Peak memory usage: 9.16 GiB.
随着支持范围的扩大,我们逐渐认识到,在确保集成可维护性的前提下,实现全面的功能覆盖将变得日益困难。鉴于此,我们决定引入 Delta Kernel,这使我们能够将协议处理的负担剥离出去,从而专注于 ClickHouse 最擅长的领域:高性能查询执行。

Delta Lake Kernel (内核) 抽象了底层格式的诸多复杂性,从而在查询引擎和协议之间划清了明确的界限。它负责处理 Delta 文件,并向查询引擎暴露定义明确的接口,使得 ClickHouse 能够以“黑盒”对象的方式进行操作,而无需关心其底层的实现细节。
与 ClickHouse 直接实现协议的原始方法相比,这种方式不仅降低了实现复杂度和维护开销,也使得跟进新功能变得更加容易。

Delta Kernel 提供了一系列保证措施,使其成为支持 Delta Lake 的一个极具吸引力的基础,同时无需查询引擎自行实现协议。具体而言,它负责以下方面:
• 解析以 JSON 格式存储的事务日志
• 读取和解释 Delta 元数据 (metadata)
• 解析快照并确定正确的数据文件集合
• 基于元数据应用数据跳过 (data skipping) 优化
通过集中处理这些逻辑,Kernel (内核) 使各查询引擎无需独立实现和维护对不断演进的协议的支持。
与此同时,像 ClickHouse 这样的查询引擎仍需保留对性能关键组件的控制权,尤其是文件读取部分。在优化 Parquet 读取器方面投入了大量的工程精力,这些优化对整体查询性能至关重要。
Delta Kernel 的设计充分考虑了这种平衡。

通过其 Engine API,它允许各个引擎插入其针对文件访问和数据读取等组件的优化实现,提供数据文件的元数据,包括统计信息和删除向量,从而使 ClickHouse 能够高效地进行下游过滤和处理。此外,除了提供与快照交互的高级接口外,它还会告知查询引擎需要对磁盘数据应用哪些转换,以使其与表的逻辑格式保持一致。

Delta Kernel 不仅抽象了 Delta 协议的复杂性,还解锁了许多由我们自行实现和维护会更具挑战性的能力。我们无需为每个功能单独构建和迭代支持,而是继承了一个一致且定义良好的实现,从而使我们能够直接在 ClickHouse 中公开这些能力。
在实践中,这使我们能够以远超原生实现的速度,交付范围更广的功能。
写入。 Delta Kernel 在事务层面全面支持 Delta 表的写入操作,涵盖事务日志处理和一致性保障。Delta Lake 为这些操作提供 ACID (Atomicity, Consistency, Isolation, Durability) 语义保证,可防止部分更新或冲突更新,并支持难以从头正确实现的并发访问模式。然而,底层的 Parquet 数据文件由 ClickHouse 写入,而 Delta Kernel 仅负责协调和记录相关的事务元数据。
Schema 演进。 Delta 表可以随时间推移进行演进,无需进行完全重写。可以以受控方式添加或修改列,并且所有更改都会在事务日志中进行跟踪。这使得 ClickHouse 能够与结构随时间变化的数据集进行交互,同时不会中断查询或数据管道。
为实现此功能,Delta Kernel 会公开逻辑 Schema(反映表如何呈现给用户)和底层 Parquet 文件使用的物理 Schema。对于每个数据文件,它还提供 Schema 转换元数据,使 ClickHouse 能够协调文件级 Schema 与当前表定义之间的差异。这确保了即使 Schema 随时间推移而演进,数据也能被一致且透明地读取。
时间旅行。 Delta 表的每次改动都带有版本信息,支持对数据历史快照的查询。这实现了可复现性、审计和调试等工作流,因为用户无需维护单独的副本,即可查询数据集的历史版本。
Delta Kernel 支持灵活的、带版本信息的访问模式,允许用户在特定快照版本读取表数据。它还通过 Change Data Feed (CDF) 提供行级变更可见性,将插入、更新和删除作为事件流暴露出来。这极大地简化了增量管道的构建、修改的审计或下游系统的同步。在 ClickHouse 25.12 中,我们通过 deltaLake 表函数 公开了 Delta Lake 的 CDF。用户可以通过设置 delta_lake_snapshot_start_version
和 delta_lake_snapshot_end_version
来查询不同表版本之间的行级变更。
SELECT *
FROM deltaLake('s3://path/to/table')
SETTINGS
delta_lake_snapshot_start_version = 5,
delta_lake_snapshot_end_version = 10;
结果中包含 _change_type
、_commit_version
和 _commit_timestamp
等元数据列,使用户能够理解数据随时间演变的方式,并更直接地追溯表历史。
我们将在后文提到,我们计划利用这些信息为 ClickPipes 提供 Change Data Feed 支持。
分区裁剪。 Delta 元数据包含分区信息,使查询引擎能够在执行期间跳过不必要的数据子集。通过 Delta Kernel 公开此信息,ClickHouse 可以避免扫描无关文件,减少 I/O 操作,进而提升查询性能。
基于统计信息的裁剪。 除了分区,Delta 还追踪文件级别的统计数据,例如最大值和最小值。这些数据可用于数据跳过,使得查询能够跳过不满足特定谓词的文件。这对于大型数据集而言是一项关键优化,能显著减少所需读取的数据量。
所有这些功能共同构成了庞大的功能体系。如果原生实现这些功能,不仅需要投入大量的工程资源,还需要持续的维护来跟进不断演进的 Delta 协议。通过采用 Delta Kernel,我们得以专注于查询执行和性能优化,同时依赖共享实现来确保协议的正确性和功能的完整性。

为了理解这项集成如何与 ClickHouse 契合,首先理解其构建系统的设计至关重要。
ClickHouse 构建系统
ClickHouse 拥有诸多设计选择,初看起来可能有些独特,但一旦理解,便会发现其强大之处。其 SQL 扩展,如表达式别名,就是一个很好的例子。ClickHouse 允许定义别名,并在同一子查询中复用,甚至可以在列声明的不同部分引用,而不仅仅局限于最终的投影。尽管这最初可能令人感到意外,但它能实现更灵活、更简洁的查询模式。
构建系统也不例外。与 ClickHouse 的其他部分一样,它也拥有独特的特性,初看起来可能令人意外,但实则是深思熟虑的设计决策的体现。为了理解我们在集成 delta-kernel-rs 时遇到的挑战,首先审视这些特性及其如何影响代码集成方式至关重要。
首先,最终的二进制文件不包含任何外部依赖,目前唯一的例外是 libc 库。默认的 Linux 构建仅依赖于相对较旧的 glibc 版本:x86_64 架构上是近 20 年前的 2.4 版,ARM 架构上是约 11 年前的 2.18 版。它不依赖 libstdc++ 或任何其他外部库。这确保了该二进制文件能在各种环境中一致运行,无论系统安装了什么,其行为都完全相同。
第二项独特之处则直接源于此。由于不支持动态加载,所有依赖都必须内置到二进制文件中。我们通过将它们作为子模块(submodules)内嵌(vendoring)到仓库的 contrib/
目录下实现这一点。这大大简化了开发,消除了对系统库版本的担忧,并确保所有必需的代码始终可用。这种做法在 Go 和 Rust 等新兴生态系统中越来越常见。然而,这种方法也伴随着一些权衡,尤其是在打包(packaging)方面,它可能导致更大的二进制文件,并引发对依赖更新及时性的担忧。
这些约束自然而然地引出了下一项设计选择。外部依赖使用与核心代码库相同的编译器选项和标志进行构建,仅在极少数必要情况下有所例外。我们还为每个依赖维护了一套简化的 CMake 配置,这些配置是根据我们的特定需求量身定制的。
以对待内部代码的相同标准来处理外部代码,其价值迅速显现。通过对所有依赖项运行我们的 Sanitizer (代码检查工具) 和 Fuzzer (模糊测试工具),我们经常能在集成初期便发现问题。解决这些问题不仅能提升 ClickHouse 自身的质量,也能回馈整个开源生态系统。
ClickHouse 内部的 Rust
Rust 引入 ClickHouse,旨在为数据库引擎添加一些辅助功能,例如新的函数。最初的演示引入了 BLAKE3 (Rust 包) BLAKE3,用于提供 blake3
函数,并使用一些手写的 C 语言封装器和 Corrosion Corrosion 来处理 cmake target (编译目标)。
尽管最初的这个库后来被移除(并由 LLVM 的 C++ 实现取代),但其他库已被添加并目前仍在项目中:
• prql :用于支持 PRQL 方言
• skim :用于在客户端中支持模糊搜索
• chcache :一个实验性工具,旨在替代 ClickHouse 开发中的 sccache
• chdig :严格来说并非一个库,而是一个很棒的终端用户界面 (TUI),类似于 top 命令,用于调试 ClickHouse。
然而,在推进 Rust 集成或仅仅是开发其他功能的过程中,我们发现初始实现存在一些不得不解决的缺点。
首先是 Sanitizer (代码检查工具) 支持问题。ClickHouse 中的所有代码都通过 Sanitizer 进行构建和测试。每次提交和拉取请求 (PR) 都会运行 ASAN、MSAN、TSAN 和 UBSAN 相关的构建检查,因此 Rust 代码也需要达到相同的标准。
在解决了一些初期问题后,我们选择直接在 Rust 内部启用 Sanitizer 功能,而非依赖外部工具。然而,Rust 对 Sanitizer 的支持目前依赖于不稳定的编译器功能,这些功能仅在 nightly (每夜构建) 工具链上可用。这意味着,为了在 Rust 代码中引入 Sanitizer,我们所有涉及 Rust 代码的构建都必须使用 nightly 版本的 Rust。
我们遇到的第二个问题是,当 Cargo 或 GitHub 尝试获取 Rust 包 (crates) 时,会间歇性地发生网络故障,这与我们在构建过程中避免依赖外部服务的通用策略相悖。
在我们这样的规模下,这已不再是微不足道的不便。我们每天运行数千次构建,因此即使是很低的失败率,也会导致持续不断的干扰,需要投入调查和修复。这促使我们将依赖项获取视为基础设施层面的问题,而非偶发的边缘案例。
为此,我们借助 cargo-local-registry,将所有 crate
(单元包)及其依赖项进行内嵌(vendored)并放入一个子模块中,同时配合自定义的 config.toml
配置和编译标志,以达到以下目的:
1. 禁用在线获取依赖项,以及
2. 转而使用内嵌的源码。
这种方法的一个结果是,某些依赖项与特定的 rustc
版本绑定,尤其是在进行 sanitizer
(代码清理)构建时。因此,我们不得不使用与内嵌依赖项集相匹配的编译器版本。
作为依赖项内嵌工作的一部分,我们引入了一个包含所有 crate
的工作区(workspace)。这使我们能够统一依赖项的解析,同时简化了配置和构建管理。
您可以在 这篇博客文章 中(https://clickhouse.com/blog/rust),找到我们一年多来处理 Rust
的经验总结。
集成 Delta Lake
尽管我们已经多次迭代并改进了 Rust
集成,但此前足以涵盖所有用例的简化方法,对于集成 delta-kernel-rs 而言,却无法开箱即用。
构建 crate
为了集成 delta-kernel-rs
,我们不得不摒弃此前的方案,将其构建为一个独立的包,或者更准确地说,构建为它自己的工作区。
以前,当我们只有少量简单的小 crate
时,我们通过将它们的源码复制到构建目录中,并从那里进行编译来处理构建。这种做法有两大目的。首先,它避免了 Cargo
缓存冲突。在我们早期的方案中,我们将 crate
源码复制到一个共享的构建目录中,以注入自定义的 Cargo
配置。这导致多个 crate
和工作区重复使用同一目标目录,进而使得 Cargo
错误地复用或废弃现有产物,并触发不必要的重建。其次,它为我们提供了一种直接的方式,通过为每个构建生成自定义的 Cargo.toml
文件,来注入不同的构建配置。
这种方法不适用于 delta-kernel-rs 这类项目。我们关注的 crate ffi
及其依赖项,依赖于基于路径的父目录链接,并引用同一仓库内的其他 crate。与我们之前那些不引用其他 crate 的独立 crate 不同,ffi
具有与仓库布局紧密耦合的内部关联性。复制源代码会打破这些假设,并需要对项目进行深度重构。
相反,我们现在选择就地构建项目,以保留其原始的工作区结构。我们不再复制源代码或修改上游文件,而是为每个构建配置生成一个独立的 Cargo.toml,并通过 Cargo 的 --config
标志 来传递。这使我们能够在保持源代码树完整的同时,灵活控制构建设置。
然而,这项改变再次引入了原始问题:当多个工作区构建到同一个目标目录时,会发生缓存冲突。我们没有在 Cargo 层面绕过此问题,而是 直接在 Corrosion 中解决了它。实际上,这意味着 Corrosion 现在可以为每个工作区或配置隔离构建产物,从而防止跨工作区缓存干扰并消除不必要的重复构建。
OpenSSL 依赖
在其依赖图深处,delta-kernel-rs 引入了 openssl crate,该 crate 又会链接到系统安装的 OpenSSL 动态库。这与我们避免系统依赖的要求相冲突。
注意:虽然理论上可以通过切换到 rustls 来避免 OpenSSL,但实际上这对我们并不可行。将 reqwest 与 rustls 结合使用会导致构建失败,原因在于 aws-lc-sys 链接到了系统库;同时,尝试使用 native-tls 也未能成功。
经过多次迭代,我们放弃了纯粹通过 Cargo flags 进行控制,转而通过 Corrosion 进行妥善配置。这使我们能够链接到我们自己静态构建的 OpenSSL 库,并确保 CMake 正确解析依赖链。尽管此配置在多数情况下运行可靠,但它要求 Cargo 配置与 CMake 集成之间进行精细协调。
交叉编译故障
在 crate 成功构建并集成后,我们发现交叉编译失败。根本原因相对简单:Cargo 尝试构建动态库,而这些动态库引用了目标环境中不存在的系统库。
解决方案是将构建限制为仅 staticlib 类型,而非使用默认的 crate 类型。这避免了链接到系统提供的动态库,并确保了交叉编译环境下的兼容性。
在此期间,我们发现了一个Cargo bug,该问题现已修复。它表现为在命令行中多次指定 --crate-type=staticlib
会导致增量构建失效。
因 sanitizer 符号缺失导致的 CI 随机故障
这是我们遇到的另一个问题,与 crate 本身无关,并且是在代码已集成并已在 CI (持续集成) 中运行之后才出现的。在进行了一些构建更改后,我们开始看到无法解释的链接器错误,提示ASAN 符号缺失。
这出乎意料,因为这些构建并未启用 sanitizers (运行时检查工具)。内部对象没有明显理由依赖 ASAN。经过多轮试错,我们将问题追溯到了 sccache。然而,在本地重现此问题的尝试均告失败,因为构建标志的更改会导致不同的缓存键。
将 sccache
从 0.7.7 升级到 0.10.0 后,问题最初似乎得到了解决(同时应用了几个补丁 [1] [2]),尽管其根本原因依然不明。然而,几周后,同样的问题再次浮现,为此我们禁用了 sccache
。目前,我们不再使用任何包装器来缓存 Rust 构建。
当前状态
一方面,当前各项工作进展顺利,令人欣慰。值得注意的是,至少对我个人而言,我花在调试和配置 Rust 构建上的精力,比阅读 Rust 代码要多出 20 到 50 倍,更不用说编写代码了,因此可以说最核心的部分已经到位。
另一方面,仍有几处功能未能按预期运行。
• 针对该 crate 的 Memory sanitizer (MSAN) 构建已被禁用。在使用 MSAN 构建的 ring 库进行链接时存在兼容性问题。解决此问题需要深入理解多层嵌套依赖的构建方式,而我更倾向于直接移除该依赖,而非投入精力去正确构建它。
• 通常情况下,在构建 Rust crates 时,我们未能完全遵循 ClickHouse 使用的所有 CMake 选项。因此,Cargo 可能会在库中引入我们希望避免的指令集。例如,我们使用 NO_ARMV81_OR_HIGHER 来禁用较新的 ARM 指令集,以兼容旧版标准。但问题在于, ring 默认需要 Neon SIMD 指令。
如果我们能够将我们自己的 S3 客户端传递给 delta-lake-ffi
,并将请求委托给外部处理,那么上述所有三个问题都将迎刃而解。ClickHouse 本身已经拥有一个复杂的网络栈,包含了重试、日志记录、事件跟踪和监控等功能。因此,在理想的配置下,我们将能够完全不依赖 Rust 内部的网络访问。这将一举消除相关依赖及其带来的构建复杂性。
在实践中,当需要跨越多个特性鲜明的构建系统工作时,发现这类问题是司空见惯的,并且通常需要深入探究其内部机制。更广泛地说,我们发现 Rust 在依赖组合方面引入的复杂性远超 C++,这使得集成和对构建过程的控制变得更具挑战性。

鉴于 ClickHouse 部署通常需要大规模运行,这会暴露出在规模较小或需求不高的环境中鲜见的边缘情况和性能瓶颈。针对真实客户工作负载的测试揭示了 Rust kernel 中的多项局限性,需要进行有针对性的修复。我们没有选择维护长期分支或内部补丁,而是优先尽可能地将这些改进贡献回上游,以确保它们不仅造福 ClickHouse,也能惠及更广阔的生态系统。
值得一提的贡献包括:
• 增强日志记录灵活性以调试性能问题(https://github.com/delta-io/delta-kernel-rs/pull/1111)。 在存在大量元数据文件的环境中,我们观察到源自 Delta kernel 的查询性能缓慢问题,由于日志初始化是静态的,导致执行过程的可见性有限。我们贡献了增强功能,允许日志在运行时动态配置,从而在生产系统中实现更有效的故障排除和操作内省。
• 异步元数据处理以提升可扩展性(https://github.com/delta-io/delta-kernel-rs/pull/1827)。 我们发现元数据文件的同步处理存在瓶颈,尤其是在元数据基数较高的表中。为此,我们修改了 FFI 接口,通过传递句柄而非引用来引入支持异步处理的更改。这使得包括对象存储读取在内的元数据操作可以在单线程回调之外并行执行,从而显著提升了大规模元数据文件的读取性能。

尽管我们对 Rust kernel 及其与 ClickHouse 的当前集成状况非常满意,但在大规模运营和构建生产级工作流时,仍暴露出一些不足之处。
其中最直接的限制是,目前无法通过 Rust kernel 创建空的 Delta Lake 表。当前,ClickHouse 可以附加到现有的 Delta 表,推断其 Schema,并通过 ClickHouse 表使其可查询,例如:
CREATE TABLE hits_delta
ENGINE = DeltaLake('https://datasets-documentation.s3.amazonaws.com/lake_formats/delta_lake/');
SELECT
cityHash64(URL),
count() AS cnt
FROM hits_delta
GROUP BY cityHash64(URL)
ORDER BY cnt DESC
LIMIT 5;
查询结果如下所示:
Query id: 8eb0cb7a-fc22-46d4-94eb-e44fce02f0d9
┌──────cityHash64(URL)─┬─────cnt─┐
1. │ 8128408125044552281 │ 3288173 │ -- 3.29 million
2. │ 17146718649009901992 │ 1625250 │ -- 1.63 million
3. │ 935030221159485792 │ 791465 │
4. │ 6972355099133249997 │ 582400 │
5. │ 13961208022208327194 │ 514984 │
└──────────────────────┴─────────┘
5 rows in set. Elapsed: 3.608 sec. Processed 100.00 million rows, 14.82 GB (27.72 million rows/s., 4.11 GB/s.)
Peak memory usage: 9.27 GiB.
然而,它无法初始化新表,也无法在对象存储上生成并持久化相应的 Delta (Delta) 日志和元数据。解决此问题将显著提升可用性,使用户能够直接从 ClickHouse (ClickHouse) 定义表,并从一开始就以 Delta Lake (Delta Lake) 为底层支撑。这是我们计划贡献的一个领域,旨在实现更完整、双向的集成。
此前介绍的 Delta Kernel (Delta Kernel) 中对 Change Data Feed (CDF) (Change Data Feed) 的支持,能够提供表版本之间的行级变更数据。展望未来,这将为 ClickPipes (ClickPipes) 中基于 Delta Lake 数据的面向 CDC (Change Data Capture) 的工作流奠定基础。
在此基础上,我们有一条明确的路径来提升可用性和内省能力。如果将表历史、提交时间线和版本元数据作为 ClickHouse 的原生系统表暴露出来,将显著简化数据演变的理解、管道调试,并能更有信心地操作增量工作负载。

将 Rust Delta Kernel (Rust Delta Kernel) 集成到 ClickHouse,标志着我们处理开放表格式方式的转变,即从定制化实现转向共享的、定义明确的抽象。这使我们能够加速功能开发,减少维护开销,并专注于最核心的目标:提供规模化的高性能分析能力。与此同时,在处理实际工作负载时,我们也深刻认识到,这些集成的稳健性与其所处的生态系统密切相关。通过向上游(upstream)贡献改进并不断弥补现有差距,我们希望能构建一个更健壮、更具互操作性的数据湖生态系统。随着这项工作的不断演进,我们期待 ClickHouse 与 Delta Lake 的结合,能为批处理(batch)和实时分析(real-time analytical)工作负载提供一个日益无缝的底层基础。
/END/
征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:tianyi.wang@clickhouse.com
关于我们
ClickHouse 是全球速度最快,资源利用最高效的在线分析列式数据库管理系统。现在,ClickHouse可以作为一个安全可扩展的无服务器应用在云中提供服务。通过云服务,ClickHouse使得任何人都能轻松获取高效的实时分析处理能力。2023年,ClickHouse正式进入中国,请访问clickhouse.com以获取更多信息。





