此次分享分为三部分内容,第一部分通过讲解推荐和广告业务的两个典型案例,穿插介绍字节内部相应的改进。第二部分会介绍典型案例中未覆盖到的改进和经验。第三部分会提出目前的不足和未来的改进计划。
(文末附 ClickHouse 沙龙第四场:《ClickHouse 在 A/B 实验和模型训练的使用》报名方式)
沙龙视频
ClickHouse 在实时场景的应用和优化
讲师:郭映中 字节跳动 ClickHouse 研发工程师
文字沉淀
早期实践

外部事务
在介绍实时场景之前,我先简单讲一下早期的离线数据是如何支持的:
在第一场分享中,技术负责人陈星介绍了 ClickHouse 在字节跳动内部最早支持的两个业务场景,用户行为分析平台和敏捷 BI 平台。这两个平台的数据主要由分析师或者数仓同学产出,以 T+1 的离线指标为主。考虑到 ClickHouse 并不支持事务,为了保障数据的一致性,我们在 ClickHouse 系统外实现了一套外部事务:
数仓同学一般会在 HDFS/Hive 准备好原始数据;数据就绪后,会执行一个基于 Spark 的 ETL 服务,将数据切成 N 份再存回 HDFS(必要的话也会做一些数据的预处理);再发起 INSERT Query 给 ClickHouse 集群的每一个 shard,将对应的数据文件从 HDFS 中直接导入到 MergeTree 表中,需要注意的是,这里没有把数据写入分布式表(i.e. Distributed table)中;每个节点上的 MergeTree 表写入成功之后,会由外部事务校验整个集群的数据是否写入成功:如果部分节点导入失败,外部的导入服务会将部分写入的数据回滚并重新执行导入任务,直到数据完全导入成功,才允许上层的分析平台查询数据。也就是说,当 ClickHouse 中仅有不完整的数据时,外部的查询服务 不会查询当天的数据。

除了离线的场景,也有业务方希望执行 INSERT Query 将数据即时地导入 ClickHouse 中,从而能查询到实时的数据。然而,我们曾经出现过由于业务同学高频写入数据,导致文件系统压力过大最后无法正常查询的线上问题。
这里我解释一下直接写入数据的风险:
直接写入的风险
用户写入 ClickHouse 一般有两种选择:分布式表(i.e. Distributed),MergeTree 表:
写入分布式表:
数据写入分布式表时,它会将数据先放入本地磁盘的缓冲区,再异步分发给所有节点上的 MergeTree 表。如果数据在同步给 MergeTree 里面之前这个节点宕机了,数据就可能会丢失;此时如果在失败后再重试,数据就可能会写重。因而,直接将数据写入用分布式表时,不太好保证数据准确性的和一致性。
当然这个分布式表还有其他问题,一般来说一个 ClickHouse 集群会配置多个 shard,每个 shard 都会建立 MergeTree 表和对应的分布式表。如果直接把数据写入分布式表,数据就可能会分发给每个 shard。假设有 N 个节点,每个节点每秒收到一个 INSERT Query,分发 N 次之后,一共就是每秒生成 NxN 个 part 目录。集群 shard 数越多,分发产生的小文件也会越多,最后会导致你写入到 MergeTree 的 Part 的数会特别多,最后会拖垮整个文件的系统。
写入 MergeTree 表:
直接写入 MergeTree 表可以解决数据分发的问题,但是依然抗不住高频写入,如果业务方写入频次控制不好,仍然有可能导致 ClickHouse 后台合并的速度跟不上写入的速度,最后会使得文件系统压力过大。
所以一段时间内,我们禁止用户用 INSERT Query 把数据直接写入到 ClickHouse。
典型案例-推荐系统

业务需求
随着 ClickHouse 支持的业务范围扩大,我们也决定支持一些实时的业务,第一个典型案例是推荐系统的实时数据指标:在字节跳动内部 AB 实验 应用非常广泛,特别用来验证推荐算法和功能优化的效果。
最初,公司内部专门的 AB 实验平台已经提供了 T+1 的离线实验指标,而推荐系统的算法工程师们希望能更快地观察算法模型、或者某个功能的上线效果,因此需要一份能够实时反馈的数据作为补充。他们大致有如下需求:
研发同学有 debug 的需求,他们不仅需要看聚合指标,某些时间还需要查询明细数据。
推荐系统产生的数据,维度和指标多达几百列,而且未来可能还会增加。
每一条数据都命中了若干个实验,使用 Array 存储,需要高效地按实验 ID 过滤数据。
需要支持一些机器学习和统计相关的指标计算(比如 AUC)。

当时公司也有维护其他的分析型引擎,比如 Druid 和 ES。ES 不适合大批量数据的查询,Druid 则不满足明细数据查询的需求。而 Click