ClickHouse 在 NVIDIA Vera CPU 上跑分登顶
有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。
DataHot 速览
ClickHouse 团队在 NVIDIA 早期访问的 Vera CPU 平台上运行标准版 ClickHouse 与 ClickBench,未做针对性优化便登顶 ClickBench 硬件排行榜,且优势明显。Vera 双路系统提供 88 个 Arm-v9.2 兼容核心/路,共 176 核、352 线程,最大内存带宽 1.0-1.2 TB/s。文章还用过滤、聚合、连接三个微基准展示 ClickHouse 在 176 核上接近线性扩展。核心挑战不在硬件规格,而在数据库能否随核心数、缓存和带宽同步扩展。
为什么值得关注:这是数据库引擎在新硬件平台上的第一方性能实测,涉及 ClickBench 排名、176 核扩展性与实时分析负载,对评估分析型数据库硬件选型和性能边界有直接参考价值。
译文
AI 逐段翻译ClickHouse 的工程师们对性能有着执着的追求。每一次发布,我们都会寻找新方法,让 ClickHouse 在底层硬件上运行得更快、扩展得更好。当 NVIDIA 最近向我们提供了一台即将推出的 Vera CPU 平台的早期访问服务器时,我们做了显而易见的事:在上面运行 ClickBench,这是我们广受欢迎的分析基准测试。我们使用了网站上的标准 ClickHouse 二进制文件,没有专门针对 NVIDIA Vera 在数据库中进行任何优化或定制。结果不言自明:
NVIDIA Vera 以巨大优势直接登顶 ClickBench 硬件排行榜。
任何数据库工程师都会承认,Vera 是一个真正令人印象深刻的平台。它配备 88 个 NVIDIA Olympus 核心,完全兼容 Arm-v9.2。在双路系统中,得益于 NVIDIA Spatial Multithreading,总共拥有 176 个核心和 352 个硬件线程。每个核心可访问 96/64 kB 的 L1 数据/指令缓存、2 MB 的 L2 缓存和 164 MB 的共享 L3 缓存。最大内存带宽范围为 1.0 TB/s 至 1.2 TB/s,具体取决于系统中的 DRAM 容量。
你可能会说,更多的 CPU 核心、更多的缓存和更高的内存带宽自然会让任何基准测试运行得更快。虽然这有一定的道理,但真正的挑战在于让数据库性能随机器资源线性扩展。基本上:如果一台机器的性能翻倍,即 CPU 核心数翻倍、缓存翻倍、内存带宽翻倍等等,人们会期望查询运行速度也翻倍,而不只是快不到两倍。数据在磁盘上的组织方式、在内存中的表示方式以及处理方式——所有这些都会影响数据库的扩展能力。如果没有在每一层进行精心的工程设计,数据库将无法利用 Vera 庞大的核心数量和内存带宽,可能会让性能得不到充分发挥。
更强大的硬件有助于处理实时分析、数据仓库、可观测性和机器学习中新的数据和计算密集型用例。虽然传统上大多数查询是由仪表板(例如 Grafana、Superset)或 BI 工具(例如 Tableau 和 Looker)生成的,但我们越来越多地看到 ClickHouse Cloud 中的智能体自主回答数据问题。从数据库的角度来看,智能体查询具有三个特征:第一,它们本质上比常规查询更加“即席”,例如,它们可以具有复杂的形状,并且查询之间也会发生显著变化。第二,它们在不固定的时间到达,并且经常是突发性的。第三,大量智能体查询通常并行运行,因为智能体会生成自身也并行运行的子智能体。
我想在 Vera 上测试 ClickHouse 的可扩展性,因此我开发了三个微基准测试,模拟大多数常规查询和基于智能体的查询中常见的基本数据库操作。
- 过滤求值(SQL WHERE 子句)、
- 聚合(SQL GROUP BY 子句)以及
- 连接(SQL JOIN 子句)。
合成数据集应完全存储在内存中,这样磁盘 I/O(对象存储或本地 SSD)就不会构成人为瓶颈。幸运的是,ClickHouse 提供了Memory 表引擎专门用于此目的。ClickHouse 的查询处理层与其存储层完全解耦。假设存储带宽足够,基准测试结果可以转化为MergeTree表中的常规表现,无论是在ClickHouse Cloud还是ClickHouse 开源版中。
表设置
我们的测试表由单列组成,包含 625 亿个随机字符串,每个字符串长度在 5 到 10 个 ASCII 字符之间。表大小恰好为 1 TB。字符串以原始形式存储,未压缩(Memory 表引擎中压缩是可选的)。
1CREATE TABLE default.strings2(3 `s` String4)5ENGINE = Memory;67INSERT INTO strings SELECT randomPrintableASCII(5+ (rand() %6))8FROM numbers_mt(62500000000);微基准测试 1:过滤求值
我运行的第一个微基准测试是一个简单的 LIKE 查询:
1SELECTcount()2FROM strings3WHERE s LIKE'%ab%'; -- how many strings contain sub-string ‘ab’我使用不同的线程数(设置max_threads)测量了查询运行时间:
| max_threads | 时间 | 加速比 | 效率 |
|---|---|---|---|
| 1 | 50.44 秒 | 1.00x | 100.00% |
| 4 | 13.19 秒 | 3.82x | 95.60% |
| 16 | 3.25 秒 | 15.52x | 97.00% |
| 64 | 1.23 秒 | 41.01x | 64.08% |
| 256 | 1.11 秒 | 45.44x | 17.75% |
“时间”是运行查询的时间。“加速比”指定了与max_threads = 1相比,查询运行时间快了多少。“效率”是观察到的加速比与预期加速比的比值。例如,使用 32 个线程时,预期加速比为 32x。较低的加速比意味着效率低于 100%。
运行时间随查询线程数的增加而成比例减少……直到在 64 线程处遇到瓶颈。将线程数进一步增加到 256 并没有带来显著的额外加速。这种行为通常表明查询是 I/O 受限的,意味着核心处理数据的速度快于内存总线从 DRAM 读取或写入数据的速度。我们可以通过将总数据量(1 TB)除以查询执行时间来快速验证。
| max_threads | 时间 | 加速比 | 效率 | 吞吐量 |
|---|---|---|---|---|
| 1 | 50.44 秒 | 1.00x | 100.00% | 19.83 GB/s |
| 4 | 13.19 秒 | 3.82x | 95.60% | 75.82 GB/s |
| 16 | 3.25 秒 | 15.52x | 97.00% | 307.69 GB/s |
| 64 | 1.23 秒 | 41.01x | 64.08% | 813.01 GB/s |
| 256 | 1.11 秒 | 45.44x | 17.75% | 900.90 GB/s |
确实,当查询停止扩展时,内存 I/O 为 813 GB/s(64 核),分别为 900 GB/s(256 核)。这个数值大致对应于 Vera 系统的物理内存带宽。这表明查询受内存限制——增加更多核心不会进一步加速它。
为了确保所有 CPU 资源在此限制之外也能被充分利用,我修改了查询,使其更加 CPU 密集:
1SELECTcount()2FROM strings3WHEREmatch(s, '[0-9][a-z][A-Z]'); -- how many strings match this regular expression?之前的查询执行的是简单的子字符串搜索,而这个查询会对表中的每个字符串求值一个开销高昂的正则表达式。以下是结果:
| max_threads | 时间 | 加速比 | 效率 |
|---|---|---|---|
| 1 | 1209.7 | 1.00 | 100.00% |
| 2 | 598 | 2.02 | 101.15% |
| 4 | 302.9 | 3.99 | 99.84% |
| 8 | 150.9 | 8.02 | 100.21% |
| 16 | 75.5 | 16.02 | 100.14% |
| 32 | 37.8 | 32.00 | 100.01% |
| 64 | 19.1 | 63.34 | 98.96% |
| 128 | 9.8 | 123.44 | 96.44% |
| 176 | 7.4 | 163.47 | 92.88% |
| 256 | 5.6 | 216.02 | 84.38% |
| 352 | 5.3 | 228.25 | 64.84% |
现在的执行时间几乎完美地随 CPU 核心数扩展。嗯,几乎总是如此……从 176 核(Vera 机器中物理核心的数量)开始,效率开始逐渐下降。超过这个核心数后,更多的软件线程由逻辑硬件线程而非物理硬件线程来服务。逻辑硬件线程(或 SMT 线程)与其他逻辑硬件线程共享物理核心。虽然逻辑硬件线程非常适合掩盖停顿,例如缓存缺失、分支预测错误或远程数据访问,但对于像过滤查询这样的计算密集型工作负载,它只能提供有限的帮助(例如 10-30% 的加速)。这正是我们在这里观察到的现象。
查询处理以显而易见的方式并行化:ClickHouse 将表分割成 352 个大小相等的虚拟块,然后同时对这些块进行过滤。这可以在 EXPLAIN PIPELINE 输出中看到:
1EXPLAIN PIPELINE2SELECTcount()3FROM strings4WHEREmatch(s, '[0-9][a-z][A-Z]')5SETTINGS max_threads =352;1┌─explain───────────────────────────┐2│ (Expression) │3│ ExpressionTransform │4│ (Aggregating) │5│ Resize 352 → 1 │6│ AggregatingTransform × 352 │7│ (Expression) │8│ ExpressionTransform × 352 │9│ (Filter) │10│ FilterTransform × 352 │11│ (ReadFromMemoryStorage) │12└───────────────────────────────────┘不详细解释计划算子,x 352 在每个算子后面表示 352 个虚拟表块被并行处理。最后的 Resize 算子将 352 个部分结果合并为最终结果。
微基准测试 2:聚合
接下来,我测试了一个聚合查询:
1SELECT ascii(s) AS first_char, count(), sum(sipHash64(s))2FROM strings3GROUPBY first_char;该查询按字符串的第一个字符分组(即 255 个分组),并返回每个分组的大小以及每个字符串的 (sip)hash 值之和。255 个分组是数据库工程师所说的“低基数”聚合。高基数聚合,例如数百万个分组,从数据库角度来看提出了更大的挑战,但低基数聚合在实践中更为常见,例如按小时对数据集进行分组。为了避免再次受限于内存,哈希计算使查询有意地更加 CPU 密集。
以下是不同查询线程数下的结果:
| max_threads | 时间 | 加速比 | 效率 |
|---|---|---|---|
| 1 | 884.1 | 1.00 | 100.00% |
| 2 | 437.6 | 2.02 | 101.02% |
| 4 | 221.3 | 4.00 | 99.88% |
| 8 | 110.6 | 7.99 | 99.92% |
| 16 | 55.7 | 15.87 | 99.20% |
| 32 | 27.4 | 32.27 | 100.83% |
| 64 | 13.9 | 63.60 | 99.38% |
| 128 | 7.2 | 122.79 | 95.93% |
| 176 | 5.4 | 163.72 | 93.02% |
| 256 | 4.9 | 180.43 | 70.48% |
| 352 | 4.9 | 180.43 | 51.26% |
运行时间再次几乎完美地随核心数扩展。Vera 的神经分支预测和复杂的预取帮助 ClickHouse 高效地写入和读取哈希表,并掩盖由数据倾斜引起的访问延迟。与前一个查询一样,效率在超过物理核心数后开始下降,因为计算密集型查询只能有限地利用 SMT。
这是 EXPLAIN PIPELINE 的输出:
1EXPLAIN PIPELINE2SELECT3 ascii(s) AS first_char,4count(),5sum(sipHash64(s))6FROM strings7GROUPBY first_char8SETTINGS max_threads =352;1┌─explain─────────────────────────┐2│ (Expression) │3│ ExpressionTransform × 352 │4│ (Aggregating) │5│ Resize × 14 26 → 26 │6│ AggregatingTransform × 352 │7│ (Expression) │8│ ExpressionTransform × 352 │9│ (ReadFromMemoryStorage) │10└─────────────────────────────────┘最好从下往上理解该计划。ExpressionTransform 在 352 个并行流中为每个表行计算 ascii(分组键)和 sipHash(sum 的参数)。AggregatingTransform 然后为每个流创建哈希表,当最后一个流处理完成时,将它们合并为一个组合哈希表。这两个算子完全并行化,并充分利用硬件资源。该计划还包含另外两个算子:Resize 再次拆分组合哈希表,并将其条目分散到下一个算子——ExpressionTransform——的各个输入中。在这个查询中,哈希表只有少数条目(256 个),但 ClickHouse 无条件地将 resize 算子插入查询计划,因为哈希表大小在运行时才能知道。ExpressionTransform(位于管道的最后)在 SELECT 列表中计算额外的表达式。在这个查询中,SELECT 列表直接引用了分组表达式和聚合结果——聚合后没有发生额外的计算,该算子基本上不执行任何工作。但假设它必须执行一些工作,ExpressionTransform 也会以最大并行度运行。
微基准测试 3:连接
第三个也是最后一个微基准测试是与较小表的连接。这代表了事实表与维度表之间连接的常见用例。我对原始表进行了采样,创建了一个包含 625k 行(占原始表的 0.001%)的维度表:
1CREATE TABLE strings_tiny2(3 s String4)5ENGINE = Memory;67INSERT INTO strings_tiny8SELECT s FROM strings9WHERE rand() %99999=010LIMIT 625000;接下来,我运行了一个简单的连接查询:
1SELECTcount()2FROM strings AS big JOIN strings_tiny AS small ON big.s = small.s;以下是结果:
| max_threads | 时间 | 加速比 | 效率 |
|---|---|---|---|
| 1 | 1132.6 | 1.00 | 100.00% |
| 2 | 554 | 2.04 | 102.22% |
| 4 | 286.1 | 3.96 | 98.97% |
| 8 | 138.7 | 8.17 | 102.07% |
| 16 | 71.2 | 15.91 | 99.42% |
| 32 | 35.9 | 31.55 | 98.59% |
| 64 | 17.8 | 63.63 | 99.42% |
| 128 | 9.3 | 121.78 | 95.14% |
| 176 | 7.3 | 155.15 | 88.15% |
| 256 | 6.1 | 185.67 | 72.53% |
| 352 | 6.1 | 185.67 | 52.75% |
运行时间几乎与查询线程数成比例下降。换句话说,ClickHouse 完美扩展。
该查询的 EXPLAIN PIPELINE 语句相当复杂,因此我在这里省略它。该连接使用了 ClickHouse 的 parallel_hash 连接算法,描述见 此处,确保哈希表可以由所有查询线程并行构建。它还构建了一个 运行时过滤器 以加速探测哈希表。在实践中,连接列通常是事实表主键的一部分(“外键连接”)。在这个实验中,没有表有主键,但如果有,它将解锁 另一个性能优化,这是我们最近引入的。
附加内容:SIMD 向量化
上述三个微基准测试主要依赖多线程来加速查询处理。另一种并行化数据处理的方式是 SIMD——单指令多数据。ClickHouse 充分利用硬件的 SIMD 能力,使用 编译器自动向量化 或 内联函数 来加速热循环。在这些频繁重复的代码段中,CPU 一次处理多个值,而不是逐个处理每个值。
因此作为附加内容,我在 Vera 机器上运行了以下简单的过滤查询,以说明 SIMD 的好处。为了演示目的,我禁用了多线程:
1CREATE TABLE t (x UInt32) ENGINE = Memory;2INSERT INTO t SELECT rand() FROM numbers(2500000000);34SELECTcount()5FROM t6WHERE x >20000000007FORMAT Null8SETTINGS max_threads =1;该查询在 0.42 秒内完成。
接下来,我打开了 ClickHouse 的 C++ 代码中的 WHERE 过滤器实现:
1/// From src/Functions/FunctionsComparison.h2staticvoidconst ContainerA & a, B b, PaddedPODArray<UInt8> & c)3{4size_t size = a.size();5const A * __restrict a_pos = a.data();6 UInt8 * __restrict c_pos = c.data();7const A * a_end = a_pos + size;89while (a_pos < a_end)10 {11 *c_pos = Op::apply(*a_pos, b);12 ++a_pos;13 ++c_pos;14 }15}))a 和 b 代表过滤条件的左侧和右侧——即 x 和 2000000000。Op::apply(“应用操作”)为一组输入行计算 a < b 并将结果作为 c 返回。
为了演示 ClickHouse 如何从强大的 SIMD 指令中受益,我指示编译器通过插入来禁用自动向量化Pragma("clang loop vectorize(disable) interleave(disable)")直接放在 while 循环之前。这让 ClickHouse 顺序比较输入值,而不应用 SIMD 指令。结果,该查询现在的执行时间为 1.48 秒,比之前慢 3.5 倍。开启自动向量化后,ClickHouse 使用 Vera 的 128 位 NEON 指令同时比较四个 32 位整数(理论加速为 4 倍)。除 NEON 之外,Vera 还提供强大的 6x128 位 SVE2 实现,可在其执行单元之间实现显著更高的向量处理吞吐量。ClickHouse 对这些指令的支持目前正与开源社区共同添加中:PR #122860。
总结
“NVIDIA Vera 是一个令人难以置信的快速且强大的服务器平台——是我们迄今见过最快的。ClickHouse 致力于将其巨大潜力转化为世界级的分析性能。” — Alexey Milovidov,ClickHouse 首席技术官
尽管 ClickHouse 如今已能在高核心数机器上良好运行,ClickHouse 工程团队当然会持续质疑现状。查询执行过程中是否存在任何低效之处?查询流水线的各个步骤是否尚未充分利用硬件并行性?数据结构能否做得更紧凑,从而更具缓存效率?数据库能否更好地利用压缩、异步处理、批处理或缓存?敬请期待后续博客文章,了解 ClickHouse 的性能工程,我们将在其中揭示 ClickHouse 如何在规模上分析、优化和测试查询性能。
有关 NVIDIA Vera 的更多信息,请访问:https://developer.nvidia.com/blog/nvidia-vera-cpu-boosts-ai-factory-throughput-to-accelerate-agentic-workloads/
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏