本周,我们欢迎 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自动生成的,这是一个管理我们各种模型(例如charges、credit notes、invoices、invites、fees、coupons,以及更多)的 ORM。我们编写了一些自定义查询(鉴于ORM 的性能限制),但在其他情况下,我们主要依赖 Active Record 进行大多数事务。
那么,像 ClickHouse 这样的 OLAP(联机分析处理)数据库在哪里发挥作用呢?好吧,Postgres 被设计为严格的原子性和一致性,这两个特性要求在运行任何可能处理它们的查询之前,必须完全接收数据。这为每分钟插入数百万行数据的表(例如,可计费事件,尤其是针对托管服务器等基础设施服务的事件)带来了问题。具体来说,问题不在于插入数据,而在于同时处理昂贵的分析查询而不阻塞队列。这些数据汇总问题是像 ClickHouse 这样的 OLAP 数据库的强项。
OLAP 数据库专为两个主要问题而设计:(i)
高效地回答具有近似精度的复杂读取查询,以及 (ii)
批量处理大量写入查询。然而,OLAP 数据库在历史上并不擅长修改数据(这通常需要重写整个数据库)或删除数据。
不同的 OLAP 解决方案(例如 ClickHouse、QuestDB、Druid)具有不同的优势,在下一节中,我们将深入探讨使 ClickHouse 成为赢家解决方案的特定特征。但所有 OLAP 解决方案都具有一个共同的特性——数据存储在相对于 OLTP 数据库(如 Postgres)而言的倒置布局中。
现在,从用户的角度来看,表的列和行仍然只是列和行。但实际上,在内存中和磁盘上,数据是按列扫描,而不是按行扫描。这使得聚合(例如,对特定字段中的所有值求和)非常、非常快,因为相关数据是按顺序读取的。
介绍 ClickHouse,我们选择的 OLAP 解决方案
ClickHouse 于 2016 年开源,目前可作为 AWS 和 GCP 上的无服务器云产品使用。它是使用最广泛的 OLAP 数据库之一。
ClickHouse 具有三个显著特性,使其成为满足我们需求的分析利器:(i)
动态物化视图、(ii)
专用引擎和 (iii)
向量化查询执行。
简要概括每个特性
- 动态物化视图。物化视图是可查询的视图,它们从底层表中的原始数据生成。虽然许多数据库(包括 Postgres)都支持物化视图,但 ClickHouse 的物化视图是特殊的触发器,它们在数据插入时执行查询。然后将查询结果分派到目标表,在那里它们会被合并和更新。这允许将工作从查询时间转移到插入时间:因为目标表通常更小,并且生成的查询更简单。这与普通物化视图形成对比,普通物化视图只是特定时间点的快照,刷新起来非常昂贵(ClickHouse 也支持这些,称为“可刷新物化视图”)。
- 专用引擎。许多数据库只有一个用于利用硬件来处理查询/事务的引擎。然而,ClickHouse 拥有专用的引擎,用于处理数学函数,例如对数字求和或求平均值。
- 向量化查询执行。ClickHouse 的专用引擎利用向量化查询执行,其中硬件并行使用多个单元来实现共同结果(称为 SIMD——单指令多数据)。
结合其列式存储,这些特性使 ClickHouse 能够轻松地对数据库值进行求和、平均以及一般聚合。
需要注意的是,PostgreSQL 并非完全无法实现类似的结果,但只能通过大量优化来实现。例如,有一个针对 PostgreSQL 设计的第三方 向量化执行器,它模仿了 ClickHouse 的原生支持。此外,还有 快速刷新模块,它利用 PostgreSQL 的日志动态更新物化视图。结合 PostgreSQL 触发器,开发人员可以创建类似 ClickHouse 的设置。但所有这些技术都需要大量的设置工作以及额外的列才能达到与 ClickHouse 相当的效率。
来自我的 PostgreSQL 与 ClickHouse 指南,用于 PostHog 的相关表情包
当然,将分析流程迁移到 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(一个开源事件流软件)读取行。当 raw_events_queue
表接收到插入时,events_raw_mv 会被触发,将事件的元数据从 JSON blob 映射到字符串数组,并将此数据推送到raw_events 表。这是一个MergeTree 表,专为大量写入而设计。Lago 的通用代码库通过我们的ClickHouseStores 类与 raw_events
交互,该类在聚合计费指标时被调用。raw_events
使用组织 ID、外部订阅 ID 和代码(加上时间戳)元组作为主键;鉴于 ClickHouse 对主键元组的复杂支持,这使得 ClickHouse 能够非常快地定位行。
我们如何部署 ClickHouse
由于 ClickHouse 是一个开源数据库,因此它可以自托管在任何普通的 Linux 服务器上。然而,许多公司信赖托管数据库解决方案,因为它们(i)
通常降低总体成本,(ii)
使数据库扩展更加容易,以及 (iii)
照顾安全的复制/备份。
目前,我们使用 Clickhouse Cloud,这是 ClickHouse Inc. 提供的托管解决方案,它部署了具有分离计算和存储的无服务器 ClickHouse 实例。Clickhouse Cloud 使我们能够更容易地扩展,而无需担心扩展问题。
ClickHouse 性能与 PostgreSQL 的对比
我们在下面对比了 PostgreSQL 和 ClickHouse 在几个关键聚合方面的性能。
加权求和聚合
PostgreSQL:6.6 秒
Clickhouse:48 毫秒
计数和求和聚合
PostgreSQL:6.5 秒
Clickhouse:350 毫秒
如上所示,我们主要的查询性能至少提高了 18 倍,最高提高了 137 倍,这是因为我们迁移到了 ClickHouse!
其他值得注意的采用 ClickHouse 的开源项目
我们并不是唯一使用 ClickHouse 的开源项目。事实上,我们甚至不是唯一从 PostgreSQL 迁移到 ClickHouse 的开源项目。另一个值得注意的例子是 PostHog,这是一个开源分析套件,由于每秒处理的 Web 事件数量庞大,它从 PostgreSQL 迁移到了 ClickHouse。
另一个很好的例子是 GitLab,它使用 ClickHouse 来存储 其可观测性套件中的流式事件数据。总的来说,对于开源公司(以及闭源项目)来说,随着规模的扩展,他们通常会发现 PostgreSQL 或 MySQL 等通用数据库并不适合。
甚至一些闭源解决方案,例如 HTTP 数据流产品 Tinybird,也已经 对 ClickHouse 做出了开源贡献,因为他们依赖 ClickHouse。慢慢地,ClickHouse 在 OLAP 世界中正在建立与 PostgreSQL 在 OLTP 领域取得的相同水平的成功。
结语
由于颠倒表布局的硬件优化,随着应用程序规模的扩展,不存在一种一劳永逸的数据库。鉴于我们产品的事件密集型性质,我们在旅程的相当早的阶段就遇到了这个问题。但是,这并不意味着每个团队都需要从 OLTP + OLAP 堆栈开始——只是为了在需要的时候做好准备。