Skip to main content

为什么 ClickHouse 如此快?

ClickHouse 的设计目标之一就是速度。在开发过程中,查询执行性能一直是首要考虑的因素,但同时也考虑了其他重要特性,比如用户友好性、可扩展性和安全性,这样 ClickHouse 才能成为一个真正的生产系统。

"Building for Fast", Alexey Milovidov (CTO, ClickHouse)

"Building for Fast" talk from ClickHouse Meetup Amsterdam, June 2022.

"Secrets of ClickHouse Performance Optimizations" talk from Big Data Technology Conference, December 2019, offers a more technical take on the same topic.

ClickHouse 是如何做到如此快的?

架构选择

ClickHouse 最初是作为一个原型构建的,只是为了做好一件事:尽可能快地过滤和聚合数据。这是构建典型分析报告所需的任务,也是典型 GROUP BY 查询所做的事情。ClickHouse 团队做出了几个高层次的决策,这些决策结合在一起,使得实现这个任务成为可能:

列式存储: 源数据通常包含数百甚至数千列,而报告可能只使用其中的几列。系统需要避免读取不必要的列,以避免昂贵的磁盘读取操作。

索引: 内存中的 ClickHouse 数据结构允许只读取必要的列,以及这些列的必要行范围。

数据压缩: 将同一列的不同值存储在一起通常会导致更好的压缩比(与行式系统相比),因为在实际数据中,相邻行的同一列通常具有相同的值,或者不同的值并不多。除了通用压缩外,ClickHouse 还支持专用编解码器,可以使数据更加紧凑。

矢量化查询执行: ClickHouse 不仅以列的形式存储数据,还以列的形式处理数据。这有助于更好地利用 CPU 缓存,并允许使用SIMD CPU 指令。

可扩展性: ClickHouse 可以利用所有可用的 CPU 核心和磁盘来执行甚至单个查询。不仅在单个服务器上,还可以在整个集群的所有 CPU 核心和磁盘上执行。

低级细节的关注

但许多其他数据库管理系统也使用类似的技术。真正让 ClickHouse 脱颖而出的是对低级细节的关注。大多数编程语言提供了大多数常见算法和数据结构的实现,但它们往往过于通用,无法发挥作用。每个任务都可以被视为具有各种特征的景观,而不仅仅是随机实现。例如,如果你需要一个哈希表,这里有一些关键问题需要考虑:

  • 选择哪个哈希函数?
  • 冲突解决算法:开放寻址 vs 链接
  • 内存布局:键和值的一个数组还是分开的数组?它将存储小值还是大值?
  • 填充因子:何时以及如何调整大小?如何在调整大小时移动值?
  • 如果值将被移除,哪种算法将更好?
  • 我们是否需要使用位图进行快速探测、字符串键的内联放置、不可移动值的支持、预取和批处理?

哈希表是 GROUP BY 实现的关键数据结构,ClickHouse 可以为每个特定查询自动选择30 多种变体之一。

同样的情况也适用于算法,例如,在排序中,你可能需要考虑:

  • 要排序的是什么:数字数组、元组、字符串还是结构?
  • 所有数据是否完全可用于 RAM?
  • 我们需要稳定排序吗?
  • 我们需要完全排序吗?也许部分排序或第 n 个元素就足够了?
  • 如何实现比较?
  • 我们是否在排序已经部分排序的数据?

依赖于它们所处理的数据特征的算法通常可以比它们的通用对应物做得更好。如果事先不知道,系统可以尝试各种实现,并在运行时选择最适合的实现。例如,查看 ClickHouse 中如何实现 LZ4 解压缩的文章

最后但同样重要的是,ClickHouse 团队始终关注互联网上声称他们已经想出了最佳实现、算法或数据结构来做某事的人,并尝试它们。这些声明大多数情况下都是虚假的,但偶尔你确实会找到一个宝石。

构建自己的高性能软件的技巧
  • 在设计系统时要考虑低级细节。
  • 根据硬件能力进行设计。
  • 根据任务的需求选择数据结构和抽象。
  • 为特殊情况提供专门的实现。
  • 尝试新的、“最佳”的算法,即使是昨天读到的。
  • 根据统计数据在运行时选择算法。
  • 在真实数据集上进行基准测试。
  • 在 CI 中测试性能回归。
  • 测量和观察一切。 :::