本周,我们欢迎来自 Lago 的一篇博客文章,讲述他们如何使用 ClickHouse 扩展事件引擎,并在过程中将查询速度提升高达 137 倍!此博客文章的原始版本已发布在 Github 上。
简介
像许多公司一样,在扩展我们的核心产品 Lago(一个开源的基于使用量的计费平台)的过程中,我们不得不中途更改数据库堆栈。随着我们越来越受欢迎,我们开始每分钟接收数百万个事件。我们最初的仅 Postgres 堆栈——在早期阶段为我们提供了很好的服务——已经无法满足需求。我们遇到了严重的加载时间问题,影响了我们整个应用程序的性能。
经过一番探索,我们决定使用分布式 ClickHouse 实例专门处理我们的流式事件。我们的分析服务现在可以直接查询 ClickHouse,一个 OLAP 数据库。对于所有其他数据需求,我们保留了 Postgres。
这个策略是成功的。自从重构以来,我们从未回头。
今天,我们将探讨混合数据库堆栈的这个决定,更具体地说,我们将探讨为什么我们选择 ClickHouse。
OLTP 与 OLAP 数据库
大多数开发人员,包括初级开发人员,都有使用 OLTP(在线事务处理)数据库(如 Postgres)的经验。顾名思义,OLTP 数据库旨在处理事务。事务是一个单独的工作单元,可以包括诸如 (i)
读取、(ii)
插入、(iii)
更新和 (iv)
删除等操作。
OLTP 数据库通常是通用数据库。由于它们支持所有类型的数据处理,因此它们可以在一定程度上用于任何数据问题。并且,即使在大规模的情况下,OLTP 数据库对于需要以下功能的软件来说也是非常出色的
- 原子事务,其中一组分组的事务要么全部发生,要么全部不发生
- 一致性,其中写入和更新之间的查询是确定性的和可预测的
对于大多数问题,这些都是重要的品质。对于某些问题,它们绝对至关重要。银行应用程序在账户之间转移资金时不能有差异。对于这些问题,需要 OLTP 数据库来实现美分级的精度。
今天,我们仍然使用 Postgres 作为我们的主要数据库,通过我们的 database.yml 文件进行配置。鉴于我们使用 Ruby on Rails,我们的 Postgres 模式由 Rails 的 Active Record 自动生成,Active Record 是一个 ORM,用于管理我们的各种模型,例如charges、credit notes、invoices、invites、fees、coupons 和 更多模型。我们编写了一些自定义查询(鉴于ORM 的性能限制),但在大多数情况下,我们严重依赖 Active Record 来处理大多数事务。
那么,像 ClickHouse 这样的 OLAP(在线分析处理)数据库在哪里发挥作用呢?Postgres 的设计初衷是严格的原子性和一致性,这两个属性要求数据在任何可能处理它们的查询运行之前完全摄取。这对于每分钟插入数百万行(例如,可计费事件,尤其是那些用于托管服务器等基础设施服务的事件)的表来说,会产生问题。具体来说,问题不在于插入数据,而在于同时处理昂贵的分析查询而不会锁定队列。这些数据汇总问题正是像 ClickHouse 这样的 OLAP 数据库的优势所在。
OLAP 数据库旨在解决两个主要问题:(i)
有效地回答具有近似准确度的复杂读取查询,以及 (ii)
批量处理大量写入查询。但是,OLAP 数据库在历史上并不擅长修改数据(通常需要重写整个数据库)或删除数据。
不同的 OLAP 解决方案(例如,ClickHouse、QuestDB、Druid)具有不同的优势,在下一节中,我们将深入探讨使 ClickHouse 成为成功解决方案的特定特征。但所有 OLAP 解决方案都具有一个共同的特点——相对于像 Postgres 这样的 OLTP 数据库,数据以倒置布局存储。
现在,从用户的角度来看,表的列和行仍然只是列和行。但实际上在内存中和磁盘上,数据是按列而不是按行扫描的。这使得聚合(例如,添加某个字段中的每个值)非常非常快,因为相关数据是顺序读取的。
进入 ClickHouse,我们选择的 OLAP 解决方案
ClickHouse 于 2016 年开源,如今以 无服务器云产品的形式在 AWS 和 GCP 上提供。它是采用率最高的 OLAP 数据库之一。
ClickHouse 具有三个显著的特性,使其成为满足我们需求的分析利器:(i)
动态物化视图、(ii)
专用引擎和 (iii)
向量化查询执行。
总结如下
- 动态物化视图。物化视图是从底层表中的原始数据生成的、可查询的视图。虽然许多数据库都支持物化视图,包括 Postgres,但 ClickHouse 的物化视图是特殊的触发器,可以在插入数据时对数据执行查询。然后将查询结果分派到目标表,在目标表中合并和更新它们。这允许将工作从查询时间转移到插入时间:因为目标表通常较小,并且生成的查询更简单。这些与普通的物化视图形成对比,后者只是特定时间点的快照,并且刷新成本非常高(这些也在 ClickHouse 中受支持,称为“可刷新物化视图”)。
- 专用引擎。许多数据库都有一个用于利用硬件处理查询/事务的单一引擎。然而,ClickHouse 为数学函数(如求和或平均数)配备了专用引擎。
- 向量化查询执行。ClickHouse 的专用引擎利用向量化查询执行,其中硬件并行使用多个单元来实现共同的结果(称为 SIMD——单指令、多数据)。
结合其列式存储,这些特性使 ClickHouse 能够轻松地对数据库值进行求和、平均和总体聚合。
需要注意的是,Postgres 并非完全无法实现类似的结果,但这只能通过大量的优化来实现。例如,有一个为 Postgres 设计的第三方向量化执行器,它模仿了 ClickHouse 的原生支持。还有一个 Fast Refresh 模块,它使用 Postgres 的日志来动态更新物化视图。结合 Postgres 触发器,开发人员可以创建一个类似 ClickHouse 的设置。但所有这些技术都需要大量的设置工作和额外的列才能达到与 ClickHouse 相当的效率。
来自我为 PostHog 编写的 Postgres 与 Clickhouse 指南中的相关 meme
当然,将分析流程迁移到 ClickHouse 只是成功了一半。接下来是实际将 ClickHouse 部署到生产环境——其中存在一些策略。
我们如何使用 ClickHouse
在讨论我们的 ClickHouse 实施时,基本上有两个不同的主题:(i)
我们使用 ClickHouse 做什么,以及 (ii)
我们的 ClickHouse 实例是如何部署和维护的。
我们查询 ClickHouse 的内容
我们的 ClickHouse 实例 摄取由我们的用户分派的原始可计费事件。虽然我们不编写自己的 ClickHouse 模式(因为它是由 Active Record 自动生成的),但它被写入一个文件,该文件 在我们的开源存储库中可用。我们的 ClickHouse 实例只有两个表——raw_events
和 raw_events_queue
——以及一个物化视图 events_raw_mv
。仅此而已。我们不会在 ClickHouse 中存储任何其他“业务关键”数据,除非是分析查询所需的数据。
详细来说,我们的 raw_events_queue 表使用 Kafka 表引擎从 Apache Kafka(一个开源事件流软件)读取行。events_raw_mv 在 raw_events_queue 表接收到插入时触发,将事件的元数据从 JSON blob 映射到字符串数组,并将此数据推送到 raw_events 表。这是一个 MergeTree 表,专为大量写入而设计。raw_events 是 Lago 的通用代码库通过我们的 ClickHouseStores 类进行交互的接口,当 聚合可计费指标时会调用该类。raw_events 使用 organization_id、external_subscription_id 和 code 的元组,加上时间戳作为主键;鉴于 ClickHouse 对主键元组的精细支持,这使 ClickHouse 能够非常快速地定位行。
我们如何部署 ClickHouse
由于 ClickHouse 是一个开源数据库,因此它可以自托管在任何普通的 Linux 服务器上。但是,许多公司信任托管数据库解决方案,因为它们 (i)
通常可以降低总体成本,(ii)
使数据库更容易扩展,并且 (iii)
负责安全的复制/备份。
今天,我们使用 Clickhouse Cloud,这是 ClickHouse Inc. 提供的托管解决方案,它部署了一个具有解耦计算和存储的无服务器 ClickHouse 实例。ClickHouse Cloud 使我们更容易成长,而无需担心扩展问题。
ClickHouse 性能 vs Postgres
我们在下面对比了 Postgres 和 ClickHouse 在几个关键聚合方面的性能
加权求和聚合
Postgres:6.6 秒
Clickhouse:48 毫秒
计数和求和聚合
Postgres:6.5 秒
Clickhouse:350 毫秒
如图所示,通过迁移到 ClickHouse,我们的主要查询性能至少提高了 18 倍,最高提高了 137 倍!
其他使用 ClickHouse 的著名开源项目
我们不是唯一使用 ClickHouse 的开源项目。事实上,我们甚至不是唯一从 Postgres 迁移到 ClickHouse 的开源项目。另一个著名的例子是 PostHog,这是一个开源分析套件,由于每秒处理的网络事件数量巨大,它从 Postgres 切换到 ClickHouse。
另一个很好的例子是 GitLab,它使用 ClickHouse 在其可观察性套件中存储流式事件的数据。总的来说,开源公司(以及闭源项目)在开始扩展时发现像 Postgres 或 MySQL 这样的通用数据库不适合是很常见的。
即使是一些闭源解决方案,如 HTTP 数据流产品 Tinybird,也因其对 ClickHouse 的依赖而对 ClickHouse 做出了开源贡献。慢慢地,ClickHouse 正在 OLAP 领域建立与 Postgres 在 OLTP 领域相同的成功水平。
结束语
由于反转表布局的硬件优化,随着应用程序的扩展,没有一种万能的数据库。考虑到我们产品的事件密集型性质,我们在早期就遇到了这个问题。但是,这并不意味着每个团队都需要从 OLTP + OLAP 堆栈开始——只是要在时机到来时做好准备。