ClickHouse 开源十年:数据分析领域的中流砥柱

本文字数:7244;估计阅读时间:19 分钟

作者:Alexey Milovidov

ClickHouse 于 2016 年 6 月 15 日 开源发布,至今已有十年。自那时起,它已发展成为最受欢迎的开源分析型数据库,拥有超过 2000 名贡献者。

开放式构建

开源项目有 不同的层级。

0 级:最低层级是将代码向公众公开供阅读,但仅此而已。这通常是档案类或博物馆类项目的情况,例如 Doom 或 MS-DOS。

1 级:下一层级是软件通过公共仓库 (public repository) 中的提交 (commits) 进行更新,但通常不接受外部贡献者。这也属于开源的范畴。SQLite 和 Ladybird 便是此类项目的代表。

2 级:接受外部贡献,但缺乏透明和开放的开发流程。大多数活跃的开源项目都处于这一层级。

3 级:具备开放的 贡献指南、任务追踪器、代码审查系统、开发路线图、测试与 CI 系统、发布周期、用户支持以及 文档。

我始终追求最高层级。 ClickHouse 应当成为以下方面的最佳典范:

如何构建一个卓越的数据库------如果你想构建一个新的数据库,ClickHouse 的源代码和开发实践将是最佳范例。我始终致力于编写高质量代码,以供所有人学习借鉴------通过保持其模块化、正交性,并配备完善的文档。当代码涉及复杂概念时,我会在注释中从零开始进行详细解释,从而让读者无需查阅教科书、维基百科或人工智能 (AI)。

C++ 开发学习之地。许多开发者都在寻找代表软件工程前沿的代码库,如今 ClickHouse 便是 C++ 领域最受欢迎的开源项目之一。在这里,每个人都能学到既激动人心(如 C++23),又看似"枯燥"(如构建系统、持续集成与测试、代码审查实践,以及 AI)的知识。

进行数据结构与性能优化实验的场所。你可以发起一个作为实验性的拉取请求 (Pull Request),即便不以合并为目标------它仍将以与生产版本相同的严格标准进行测试。无论你发现了新的内存分配器、新的压缩库、新的哈希表、数据格式,还是新的排序算法,都可以将其引入 ClickHouse,我们将对其进行全面而深入的检验。项目的路线图甚至包含了一些关于实验性、奇特乃至异想天开事物的章节。

在这里,你的工作将让你引以为傲。 ClickHouse 在更新日志中,甚至在数据库内部的 system.contributors 表中都会感谢每一位贡献者!有无数的例子表明,当贡献者提交了一个功能最初的、不完整的实现时,我们会共同努力帮助其完善。即使代码需要完全重写,我们也会积极主动地承担责任,并且始终将功劳归于最初的作者。因为我们重视你的使用场景,以及促成其实现的最初意图。简而言之,我们珍视每一位贡献者。

开源之前

原型与首次提交

ClickHouse 的首次提交于 2009 年 5 月 29 日完成,这是一项性能优化工作(旨在替换 libc 函数 localtimemktimegmtime,它们运行极其缓慢,且在性能分析器中频繁出现,令我深感困扰)。然而,这一切都发生在 ClickHouse 项目正式诞生之前。

ClickHouse 始于我的一个实验项目,当时我正在为一个网络分析系统处理数据。该系统与 Google Analytics 类似,负责接收来自网站的页面浏览日志。其实现方案包括 MySQL 数据库、C++ 数据处理,并在 MySQL 无法满足需求时,采用 C++ 自定义数据结构。MySQL 数据库主要用于存储为客户预聚合的报告,而自定义数据结构则用于计算用户会话、用户历史等信息。

彼时我的体会是------数据量不断增长,现有方案捉襟见肘,且新数据不断实时涌入。如果无法在五分钟内处理完五分钟的日志数据块,系统就会出现延迟。在这种延迟不断累积的压力下,我必须在当天工作时间内找到并部署任何能解决问题的创新方案。

正因如此,我当时不惜一切代价寻找任何可行的解决方案------无论是哪种数据库,何种库,都来者不拒。我们可以使用 TokuDB 吗?同事们使用的 LMDB 能否拯救我们?让我们试试 Judy Arrays。午餐时有人提到了 Hadoop,我们是否该尝试一下?在走廊里偶然听到 LZO 和 QuickLZ,不妨一试。如果我们将 HyperLogLog 对象存储在 MySQL BLOBs 中,又该如何对它们进行聚合求和呢?某个周末,我会去研读那本 数据压缩书 或是 事件循环服务器 的相关文档......

在稳定数据管道的同时,我也在思考可以为产品带来哪些新功能。如果我们记录用户在链接上的点击行为,便能在每个页面上展示一个 热力图。如果能记录每次点击在 DOM 中的具体位置,我们就能生成一个 点击图。为了 4 月 1 日,我曾用 Flash 制作了一个带有红蓝互补色的 3D 点击图。然而,更具吸引力的功能是,允许用户自由构建报告,而非局限于一套预聚合报告的范畴。

为了完成这项任务,我研究了列式数据库 (column-oriented databases) 。我曾从各公司的邮件列表、dbms2.com 等网站以及广告部门的同事那里了解到它们。其核心理念是存储非聚合但结构化的日志,并在客户等待页面加载时实时聚合这些日志。我测试了几款 MySQL 扩展:Infobright、InfiniDB,以及一些独立的分析型数据库:Vertica、MonetDB 和 LucidDB。然而,它们都未能成功处理每天 1000 亿条记录、每条包含 500 列的数据加载任务。于是我尝试实现一个自定义数据结构的简单原型:每天、每个网站的每一列(仅存储整数,用哈希替代字符串)都保存在一个单独的二进制文件中(约十亿个文件需使用 XFS 文件系统)。该文件采用轻量级压缩,每天更新一次,有数小时的延迟,通过一个 API 进行查询,该 API 支持指定分组列、聚合函数、过滤器和排序功能(查询以 XML 格式指定)。最困难的部分在于,如何通过"反聚合" MySQL 中的历史数据来填充新系统,并确保聚合结果与原系统保持一致,最终由我的同事 Evgenii Gatov 解决了这一难题。

这个简单的原型(命名为 OLAPServer,于 2008 年 12 月实现,2009 年 1 月部署)运行成功。我还创建了一个端点,使用户能够分析全球互联网数据,而不仅仅是单个网站的数据,效果出奇地好。例如,公司内部的统计部门原本使用内部版本的 MapReduce 处理互联网日志,然而,公司分析师们很快便转而使用我的服务,因为它能够即时响应查询。

报告生成器的第一个原型。前端和设计同样由我完成。

然后,我决定重构 MySQL 中原有的聚合报告方案(其在 50 个分片上积累了约 50 TB 数据)。许多自定义数据结构以 BLOB 形式存储,为了聚合这些数据,程序需要先从数据库中读取,应用自定义代码处理后,再写回数据库。更重要的是,MySQL 中的数据未经压缩,且数据读取速度缓慢。这主要是因为数据到达(按时间)的顺序与查询范围(按网站 ID)的顺序不一致。在研究 LevelDB 和 TokuDB 之后,我决定实现一个自定义数据结构,专门用于增量聚合和后台合并。该表中的每条记录都由一个自定义 C++ 结构体定义,代表一个 CRDT,并具备 addupdatemergeserializeText / BinarydeserializeText / Binary 等方法。在读取时,这些部分聚合的数据会最终合并并返回给 API。这种数据结构可以应用于任何聚合报告场景,例如按区域统计的独立用户和访问量,或是每个页面的点击热图。

这个简单的原型(名为 Metrage)同样取得了成功。至此,我们拥有了两种自定义数据结构:一种是面向列的数据结构,用于存储非聚合数据,每日更新,且仅支持整数类型;另一种是面向行的数据结构,支持任意 CRDT,并能实时更新。

在很长一段时间内,这两种自定义数据结构都能很好地解决我们遇到的问题。坦白说,当时并没有人提出更高的要求。但我开始思考:如果能将面向列的方法与合并树相结合,既能提升聚合速度,又能支持实时更新和数据局部性,同时还能将其泛化,以支持真正的查询语言和丰富的数据类型,那将会怎样?而这,正是 ClickHouse 的缘起。

ClickHouse 是如何构建的?

ClickHouse 是一个罕见的数据库系统范例,它并非基于任何现有系统,而是完全从零开始实现的。如今,大多数数据库管理系统 (DBMS) 都是在诸如 Postgres、Datafusion 甚至 ClickHouse 等现有系统之上构建的。因此,深入探讨如何在没有任何基础的情况下从零引导一个 DBMS,以及其具体实现步骤,或许会非常有意思。

2009 年的首次提交 涉及对同一单体仓库(mono-repository)中其他数据结构的优化。之所以能看到这些提交,是因为在开源过程中,我精心拆分了代码仓库,并完整保留了所有历史记录。

我开始实现新数据库管理系统 (DBMS) 的首次提交(当时 ClickHouse 这个名字尚未诞生)在此,它实现了内存中的列存储:可以看到大家已经熟悉的 IColumn 和 Field 类。可以将其与今天的实现进行比较 :) 你可能会觉得这与 Apache Arrow(专注于内存中的列表示)很相似,并好奇我们为何没有采用它。然而,当时 Apache Arrow 尚未问世(其他列式存储格式,例如 RCFile、Trevni、ORC 和 Parquet 也都还不存在)。

随后,本次提交 引入了聚合函数(aggregate functions)。至今,这仍然是 ClickHouse 最重要的组成部分之一。

接着,表引擎(table engines)也随之引入。有趣的是,表引擎曾被命名为"primary key",但仅仅持续了几天。这使得在磁盘上读写列成为可能。第一个表引擎 类似于至今仍存在的 TinyLog 引擎。

随后,压缩功能 被加入。最初使用的是 QuickLZ,但当我阅读了 Yann Collet 的博客后,便迅速将其替换为 LZ4。

然后是 block streams (数据块流),这些数据处理管道的组件负责以流式方式生成、消费或转换数据列块。如今,它们已被 Processors (处理器) 取代。这一改进为 结果格式化 和实现表查询奠定了基础。同一提交还新增了 StorageSystemNumbers,最初用于测试 查询管道 (query pipelines),如今仍是我们备受喜爱的 system.numbers 表。ClickHouse 中 第一个查询管道 的功能是将数字以 TSV 格式打印出来。

这里(https://play.clickhouse.com/play?user=play\&tab=Query B) 你可以查看 ClickHouse 中引入表引擎的顺序。

ClickHouse 代码库中第一个 关系运算符 (relational operator) 是 LIMIT。

接着,我尝试 添加一个 SQL 解析器 (SQL parser)。首次尝试使用了 boost::spirit,但最终失败了。一段时间后,我开发了一个 递归下降解析器 (recursive descent parser)。

值得一提的是,一些最初被拒绝或后来又重新引入的想法。最初,我曾尝试添加一个用于存储可变长度编码数字的列。但由于性能缓慢,该列被移除。直到很久之后,我们才引入了不依赖于列的自定义压缩编解码器。早期,我还添加了一种可以包含任意字段值的 Variant 列类型。它同样性能不佳,所以我将其移除------直到 Variant 的一个更好版本于 2025 年才被添加。我曾设计过固定大小(fixed-size array)和可变大小(variable-size array)的数组数据类型,但因缺乏实际需求而将其移除。直到今天,我们才考虑重新引入它。我认为移除不必要的代码比添加新代码更为重要,而目前,移除冗余代码已成为我最热衷的工作之一。在 ClickHouse 中,你可以找到大量标题为"remove trash"的提交记录,类似的情况并不少见。

在此,你可以看到 ClickHouse 中测试的第一个实际表结构------它正是你如今在 ClickBench 上仍能见到的 hits 表。

在尝试读写这个表的过程中,我们发现 C++ iostreams 性能低下。因此,在这个提交中,可以看到 WriteBufferReadBuffer 的引入,这些组件至今仍在沿用。

SQL 中最早的函数------算术运算符------ 在此处 出现。这使得第一个 SELECT 查询解释器 得以实现。尽管当时 SELECT 查询解释器只能通过测试程序访问,但它仍能让我们快速实现新的 聚合 函数、常规函数、关系运算符、数据格式及其他组件。

ClickHouse 服务器 于 2012 年 3 月 9 日 问世,而 clickhouse-client 则 于 3 月 25 日 推出。再加上 LogTinyLogMergeDistributedMemory 等表引擎,ClickHouse 已足以在生产环境中部署。首次部署是为了存储传入的日志块以供进一步处理,并对原始日志进行全局查询(这正是 MergeDistributed 表引擎的作用)。可以说,ClickHouse 最初的生产用途是一个支持 SQL 查询的持久化日志队列 😂。

随后我引入了 MergeTree 表引擎,它支持在后台进行数据的增量排序。这样一来,即使数据按时间顺序写入,对单个网站的范围查询也能快速响应。这使得我们能够将其部署到生产环境,以替代早期的 OLAPServer 和 Metrage 等原型。MergeTree 的第一个版本包含了一些针对我们生产环境的特点,例如在夜间更积极地合并数据块。

2012 年,我有幸聘请了团队的第二名成员,他就是 Michael Kolupaev。时至今日,我依然很高兴能与他共事。

我们的生产环境部署在多个区域数据中心。基础设施团队每月会故意关闭一个数据中心一小时,这项操作被称为"演练",目的是让缺乏准备的服务经历停机,以此促使所有团队构建高可用的多数据中心服务。因此,生产环境中的所有数据和服务都必须在多个数据中心进行复制。最初,我为此采用了简单的双写机制,并在数据中心恢复后进行数据回填。但我们追求的是100%的一致性以及自动修复能力,为此,分布式共识机制必不可少。我的一些同事是Java工程师,因此他们引入了ZooKeeper作为协调系统(别担心,我已经原谅他们了)。Michael 利用 ZooKeeper 作为元数据层,实现了 ReplicatedMergeTree。这使得 ClickHouse 得以在2014年成功部署,用于处理生产环境中的用户查询。

ClickHouse 是如何开源的?

2014年,ClickHouse 已投入生产环境,每日存储数百亿条记录并响应客户的实时查询。我还将其开放给公司内的数据科学家,他们利用 ClickHouse 来计算互联网趋势。我发布了一份关于 ClickHouse 用法的简洁文档(https://presentations.clickhouse.com/original_website/reference_en.html?_gl)。广告、电商、基础设施和业务分析等其他部门也尝试使用 ClickHouse,并将其部分用例从其他系统迁移过来,例如内部的 Map-Reduce(他们当时确实在使用 Perl 脚本处理文本日志)、MySQL 和 Postgres。到2014年底,ClickHouse 已在公司内部被广泛使用,但当时仅限于这家公司(有一个例外:CERN 也曾通过合作,将其部署用于 LHCb 实验)。

当我观看技术会议上的演讲和阅读博客时,我注意到其他公司的工程师经常开发类似于 OLAPServer 或 Metrage 的系统,因为现有数据库无法妥善处理他们的用例------这对我来说是一个非常熟悉的故事!我当时想------如果我能向大家介绍 ClickHouse 会怎么样呢?我在2015年发表了一篇关于 ClickHouse 的文章(translation),这进一步证明了人们对它的兴趣。我的想法是------如果我能让 ClickHouse 普惠大众,它就能填补这一市场空白。如果我不这样做------最终会有其他人来做,那将是非常可怕的。

我准备了一份清单,列出了潜在的优势和风险,以促使公司管理层批准其开源发布。不知怎的,我的努力足够有说服力,提案获得了批准。于是我制定了发布计划,设计师制作了第一个 Logo,我搭建了第一个网站(https://presentations.clickhouse.com/original_website/?_gl),准备了博客文章,并(与基础设施团队协作)创建了一个 Debian 仓库。最终,ClickHouse 于2016年6月15日向全世界开放。

我希望这个故事也能激励每一位工程师尝试开源自己的代码。最坏的情况是,它可能一无所获;但也有可能像 ClickHouse 一样,影响数代人!不必担心自己的代码会让你感到难为情------我刚刚展示了我十五年前的代码,现在看来也有点好笑。如今,ClickHouse 已成为全球各大公司最青睐的分析数据库。

ClickHouse 是面向 AI 时代打造的高性能实时分析数据库,能够以极致性能处理海量数据分析任务。凭借高并发、低延迟和云原生架构,ClickHouse 广泛应用于可观测性、数据仓库、实时分析及 AI 数据基础设施等场景。我们致力于帮助企业在公有云平台上构建安全、弹性且高性价比的实时分析与 AI 数据平台,加速释放数据价值,推动智能化创新与数字化转型。目前,Trip.com、DiDi、Meta、Sony、Netflix、Deutsche Bank、Sierra、Cloudflare 等全球领先企业均在使用 ClickHouse 支撑其关键业务和数据分析平台。

相关推荐
程序软件分享3 小时前
多语言DAPP综合盘交易所系统源码搭建剖析
vue.js·金融·开源
冬奇Lab4 小时前
开源项目第167期:Buzz — Block 开源的人机协作工作空间,Agent 是成员不是 Bot
人工智能·开源·agent
OpenCSG4 小时前
CSGClaw v0.4.0-v0.4.1 版本更新
开源
菩提小狗4 小时前
每日极客日报 · 2026年07月25日
ai·开源·极客日报·it热点·技术资讯
在线梦游6 小时前
稳定免费的主流网盘高速解析网站,公益白嫖网站支持在线解析生成高速下载链接
开源
其实防守也摸鱼9 小时前
GitHub开源项目破圈方法论:从技术自嗨到生态共赢
服务器·数据库·学习·开源·github·命令行·linux系统
梦梦代码精10 小时前
PHP 还是 Java?LikeShop 多版本对比,附6条落地避坑建议
低代码·docker·开源·代码规范
Ai_easygo11 小时前
多Agent数据分析报告自动化实战:用CrewAI组支AI团队,丢份数据就出报告(从架构到评估)
人工智能·数据分析·自动化