作者 | 蔡芳芳
采访嘉宾 | 陈星、邵祎旸、海书山
ClickHouse 开源于 2016 年,在一众大数据计算引擎里算是一个后起之秀。但凭借性能方面的突出优势,这几年 ClickHouse 在分析型数据库领域可谓风生水起。
作为 ClickHouse 深度用户,字节跳动拥有国内规模最大的 ClickHouse 集群。根据官方提供的最新数据,截至 2022 年 2 月底,字节跳动内部的 ClickHouse 节点总数已经超过 18000 个,管理总数据量超过 700PB,最大的集群规模在 2400 余个节点。在这之上,承载着字节跳动广泛的业务增长分析工作。
熟悉 ClickHouse 的开发者可能会知道,虽然 ClickHouse 性能强大,但可扩展性、易用性却差强人意,随着使用不断深入、集群规模不断扩大,使用和运维的技术门槛会变得越来越高。不支持弹性扩缩容更是一个长期被诟病的问题。
为了解决实际业务场景对 ClickHouse 的需求,字节跳动基于开源的 ClickHouse 做了大量二次开发和深度投入。这部分投入到今天也还在继续,使得字节跳动在 ClickHouse 的积累上相对领先业界,并最终沉淀出了去年在火山引擎平台正式发布的商业化产品 ByteHouse。
从 ClickHouse 到 ByteHouse,经历了怎样的演进历程?ClickHouse 节点增长至近 2 万台这一路,技术团队遇到了哪些挑战?ClickHouse 云原生化改造是如何展开的?本文深度采访了火山引擎 ByteHouse 团队核心成员,试图更清晰地呈现出 ClickHouse 在字节跳动内部的“进化”之路。
试水 ClickHouse
字节跳动最早开始接触 ClickHouse 是在 2017 年年底。
对于字节来说,用户增长分析的重要性不言而喻。这是一项十分考验运营团队能力的工作,怎么衡量不同运营方法的有效性、该考量哪些数据指标、如何对指标的波动进行更深层次的原因分析等等,其中涉及大量数据分析,对于分析的实时性也有很高的要求,这些都离不开一个好用的实时数据分析平台的支撑。
在字节内部第一个“吃螃蟹”、试水 ClickHouse 的业务场景,恰恰就是用户增长分析,直到现在它也依然是字节 ClickHouse 最重要的业务场景之一。
其实在尝试 ClickHouse 之前,为了解决数据量和分析效率的问题,字节的工程师们已经在数据分析引擎层面做了不少探索,当然也经历了一些曲折。
在 OLAP 引擎上,团队尝试过 Kylin、Druid、Spark 等。这些不同的尝试,也是根据当时面临的最迫切的问题做出的选择。比如一开始,最需要解决的是“快”,所以团队选了 Kylin,它的优点是能够提供毫秒级别的查询延时。但 Kylin 也存在需要预聚合、需要提前定义数据模型和无法进行交互式分析等问题,随着数据量变大反而会导致返回结果慢,所以后来团队又改用 Spark 来解决问题。但 Spark 同样存在不少问题困扰着团队,比如查询速度不够快、资源使用率高、稳定性不够好,以及无法支持更长时间的数据等。
这时候团队开始反思:现有的查询引擎是否能持续支撑后续业务高速发展?我们要从什么维度来考虑?是从我们现有什么样的技术能力,还是从我们需要什么样的技术能力?
基于这个选型思路的转变,团队开始不设边界来看,对于 OLAP 来说,需要考量哪些因素?
一是对 OLAP 非常朴素又简单的要求:高可用和强性能。不论给 OLAP 加上多少复用、赋予多少身份,最核心且首要的诉求是能存储足够多的数据、足够稳定,并且可以非常快地查到数据。这是第一个要求——要好用,即满足海量数据下交互式分析的性能要求,达到秒级响应。
二是复用。在好用的基础上,团队希望能尽量用一套技术栈解决大部分问题甚至是所有问题,这就需要引擎是可定制的,能让开发人员在这套技术栈上搭建各种面向场景化的应用。
三是易用,能让用户更加自主地把产品使用起来。
最终,经过对当时市面上已有的多款开源引擎的调研和测试,团队最终选择采用 ClickHouse 作为 OLAP 查询引擎,并开始基于此不断迭代。
在早期试水阶段,团队的基本思路是先提供基础能力,满足特定业务场景用户的基本使用需求,然后在用户使用过程中一边收集反馈一边做优化。
要让业务方用起来,首先得为 ClickHouse 补上原来缺失的数据生命周期管理能力,提供数据接入的基本功能。这样一来,业务方只需要在数据接入服务中注册并进行配置,服务就会自动完成元数据管理和导入任务的调度,每次当外部数据源就绪后,接入服务会自动触发,并将相应的数据转储和格式化到 ClickHouse 中。调度任务执行完毕后,业务方用户就可以直接在平台上进行查询分析。然后是提升 SQL-based 指标计算的执行效率,包括 UD(A)F 增强、SQL 语法增强等,另外在数据可视化组件开发上,团队也费了不少功夫。
通过早期试水,ClickHouse 整体方案的可行性在实际业务应用中得到了验证,它能够满足交互式响应的用户体验需求,数据产品迭代较快且稳定性基本满足要求。同时,方案整体架构的水平扩展能力,也经历了从几十台机器扩大到几百台机器规模的考验。
虽然彼时 ClickHouse 还只是一个能够解决单一应用性能问题、满足特定业务场景需求的引擎,但团队看到了这套方案的可能性,并为其设定了新的目标:打造成一个公司级别通用的 OLAP 系统。ClickHouse 很快进入在字节内部大规模应用的新阶段,新的问题和挑战也随之而来。
规模扩大:从单一业务到全公司
随着 ClickHouse 支持的业务场景从用户增长分析这个单一应用,扩展到 BI 分析、AB 测试、模型预估等多个场景,需求方不断增多,不同业务需求对技术的要求也发生了比较大的变化。通用的技术已经很难解决所有需求,这就要求团队针对不同的应用场景抽象出对应解决方案,其中涉及不少自底向上的自研功能。与此同时,ClickHouse 集群规模扩张了至少一个数量级,暴露出了 ClickHouse 在应对大数据量和高可用等方面的不足。
首先,ClickHouse 本身是存储计算紧耦合架构,本地存储容量比较有限,一般单节点存储大约只有几十 TB。伴随用户数增长而来的海量数据很容易让原来就有限的本地存储更加捉襟见肘。虽然增加外部存储空间可以解决部分问题,但无限制扩容也不现实。其次,数据量大了之后,数据写入对查询服务的影响已经无法完全忽略。
针对本地存储局限问题,团队采用了冷热数据分级存储的解决思路,也就是把长期不查的数据放到底层的冷库存里,远程的计算资源可以出让给其他业务,而本地存储只管理日常需要查询的近几个月数据。
对于数据写入影响查询服务性能的问题,则考虑把数据写入服务放到查询服务外部。当导入数据量比较小的时候,直接用引擎自身的资源格式化之后放到本地存储;当数据量很大的时候,则由数据导入服务负责把外部数据源格式化 ClickHouse 能够识别的形式,完成数据排序、compressed 等构建工作,然后把结果写回共享存储里面,此时 ClickHouse 只需要把格式化好的源数据拉取过去注册一下就可以查询了。当数据达到一定的生命周期、查询频率降低后,再把数据重新 down 到 HDFS 上。
这套方案解决了数据量变大时导入服务和查询之间的资源竞争问题,同时也让按需补充计算资源和存储资源变得可行,不再需要因为流量峰值或波动就动不动扩展集群规模。
虽然现在看来,这套方案还存在一些不足,也没有做到完全的弹性架构,但已经朝着完全弹性的方向迈出了重要的一步。
除了海量数据带来的挑战,当集群扩展到一定规模之后,可用性问题也越来越棘手。产品扩张导致数据分区变多、节点数变多、故障变多,最常见的硬盘故障几乎每天都会发生。从可用性的视角来看,ClickHouse 社区版本的复制方案 ReplicatedMergeTree(ZK)已经面临瓶颈;而增多的数据分区会导致故障恢复时间变长,又进一步增加了运维的复杂度与难度。
ClickHouse 社区版原生的 Replication 方案有太多的信息存在 ZooKeeper 上,为了保证服务,一般会有一个或者几个副本,在字节内部主要是两个副本的方案。
早期内部曾有一个 400 个节点的集群,只存了半年的数据。突然有一天团队发现服务特别不稳定,ZK 的响应经常超时,table 可能变成只读模式,发现 znode 太多。而且 ZK 并不是可水平扩展的框架,按照当时的数据预估,整个服务很快就会不可用了。
团队分析后得出结论,实际上 ClickHouse 把 ZK 当成了三种服务的结合,而不仅把它当作一个 Coordinate service,可能这也是大家使用 ZK 的常用用法。ClickHouse 还会把它当作 Log Service,很多行为日志等数字的信息也会存在 ZK 上;还会作为表的 catalog service,像表的一些 schema 信息也会在 ZK 上做校验,这就会导致 ZK 上接入的数量与数据总量会成线性关系。按照这样的数据增长预估,ClickHouse 可能就根本无法支撑头条、抖音的全量需求。
为了解决问题,团队基于 MergeTree 存储引擎开发了一套自己的高可用方案,思路就是把更多 ZK 上的信息卸载下来,ZK 只作为 coordinate Service。只让它做三件简单的事情:行为日志的 Sequence Number 分配、Block ID 的分配和数据的元信息,这样就能保证数据和行为在全局内是唯一的。
关于节点,它维护自身的数据信息和行为日志信息,Log 和数据的信息在一个 shard 内部的副本之间,通过 Gossip 协议进行交互。保留原生的 multi-master 写入特性,这样多个副本都是可以写的,好处是能够简化数据导入。下图是一个简单的框架图。
HaMergeTree 简单框架
改造完成后,服务的高可用基本上得到了保障,ZooKeeper 压力也不会随着数据增加而增加。这让 ClickHouse 更大规模使用变得可能,即使是一个集群上千台机器或者一个节点有几千张表,改造后的服务都可以 Hold 住。
针对故障恢复时间长的问题,团队也做了一些优化和改进。比如 ClickHouse 社区版为了极致的性能优化,将元数据存储在内存中,但因此会在实际使用中带来服务重启等各种异常。对应地,团队做了元数据持久化的改进,将故障重启时间从小时级降到分钟级,减少对实际业务的影响,从而有效提升 SLA 到 99.95% 的级别。
与此同时,团队逐步为 ClickHouse 社区版补齐了运维支持能力。从安装部署层面的自动化配置,到日常运维层面的故障节点替换、健康度检测、故障预警等功能,将原生的引擎升级为企业版产品,从而极大降低产品使用和运维过程中对人的依赖,也降低了业务接入成本。通过界面化的建表等 DDL 操作,抽象 ClickHouse 社区版复杂的底表逻辑,进一步降低业务用户的上手和理解成本。此外,提供了多种实时与离线的数据源接入方式,规范化数据写入,减少人为错误或不当使用。
整体而言,团队将大部分基于 ClickHouse 的使用经验和最佳实践都产品化,沉淀为服务和工具,提升了业务用户的易用性;同时,通过提供可视化的运维与监控界面,降低了黑屏操作和重复排障的频率,系统管理员的可管理性也得到加强。这些都让曾经使用和运维难度都极高的 ClickHouse 变得更加“易用”。
借助产品化的运维能力,如今即使面对上万台的集群规模,字节内部也仅需要几位 SRE 就能完成集群的日常运维工作。当然,这是后话了。
从初步试水到大规模应用,团队围绕字节跳动真实业务场景,一边解决技术挑战一边打磨产品,基于 ClickHouse 做了大量二次开发和深度优化。这套基于 ClickHouse 长出来的实时数据分析系统,跟 ClickHouse 社区版相比,已经进化出了很多不一样的地方。
下一代云原生数仓:ByteHouse 初长成
试水 ClickHouse 第三年,ByteHouse 在字节跳动内部正式立项,比火山引擎立项还要早几个月。对于这个从字节真实业务场景中进化出来的实时数据分析引擎,团队希望它在云原生时代发挥更大的价值,不只要服务于内部业务,还要把它做成一个服务于更多外部用户的 ToB 产品。
新产品定位为下一代云原生数仓,对标 Snowflake。这一方面是因为团队看到了云原生趋势下的一些外部机会,另一方面是内部确实有需求。
团队希望对已有产品做一次比较大的架构升级,以解决字节内部使用 ClickHouse 过程中遗留的痛点,比如“老大难”之一的动态扩缩容问题。ClickHouse 原生架构扩缩容非常困难,虽然字节内部开发了一些工具,但真正扩容起来耗时仍比较长。近几年字节的业务特点也在不断变化,突增流量发生的频率越来越高,原来的扩缩容方案已经无法满足越来越高的系统弹性要求。
总体而言,云原生架构主要想解决几个问题:1)计算能力和存储能力能弹性扩缩容;2)Adhoc 场景下资源利用率的问题;3)多租户、多任务之间的隔离;4)面向云的部署运维模式等。另外借这次架构升级,也希望进一步扩大产品的使用场景、提升易用性,简化应用开发的成本。
过去一两年,ByteHouse 团队从上述需求出发进行架构升级的开发工作,重点包括以下几方面:
- 元数据管理独立出来放在分布式 KV 系统中,因为元数据访问频率很高,对性能影响比较大,里面又涉及到元数据缓存的工作;
- 存储对接共享存储系统,不是简单添加一个 Storage Policy,而是结合共享存储的特点设计数据格式、访问方式、数据变更方式等;
- 提供事务能力,ACID 是数据库系统的一个基本要求, 没有这个能力数据正确性没法保障;
- 复杂查询的处理能力,也就是大家更常说的分布式 Join,除了执行能力有大的提升之外,自研优化器也即将上线;
- 动态的资源管理,能够根据负载自动调整计算资源组规模 ;
- 容器化部署与运维,将所有计算资源都放在容器中,利用容器编排平台(如 Kubernetes)来帮助集群管理,并实现多级资源隔离(租户间、读与写、查询间、后台任务等等),进一步提升资源利用与合理分配,使得 ByteHouse 可以扩展到不同环境。
在原型验证阶段,团队遇到了新架构下的种种问题,其中最为突出的问题,是存储计算分离、更多的组件、更长的链路导致了性能下降。针对这一问题,团队反复试验并进行了深度优化。
第一,通过多级缓存机制来填补远程数据访问导致的损失。在 server 节点对元数据缓存,在 worker 节点有数据缓存,配合合适的哈希算法,提升缓存的命中率,大大抵消这部分的性能损耗。
第二,新架构下,数据被存在分布式文件系统或对象存储中,为了避免产生过多的小文件而影响性能,团队决定使用 Compact 数据格式,而不是分列的存储模式。另外为了适配不同的对象存储与分布式文件系统,团队打造了一层抽象存储层,使后端存储系统的可扩展性更上一层楼。
第三,在云原生架构下,组件多且链路长,元数据的管理成为重中之重。团队引入分布式事务,支持 ACID,保证在不同节点写入的数据都可以被同时看到。另外,数据在被并行更新时,为了保证用户可以同时更新同一份数据并最终确保数据被正确更新,团队开发了细粒度的锁。针对不同的请求,引擎会使用不同类型的锁(DB/Table/Partition/Part),在保证准确性的同时,也不牺牲不必要的并发性。再利用多版本管理(MVCC),READ 操作不会受到 WRITE 操作的影响或阻塞,在大量并发的 READ 和 WRITE 场景下,性能可以进一步提高。
现阶段 ByteHouse 已具备与传统 ClickHouse 相当的有竞争力的性能表现,同时兼具云原生架构带来的高资源利用率、低使用与运维成本。
此外,ByteHouse 也针对主流云厂商做了一定的适配工作。基于立项之初的定位——做一个云中立的产品,ByteHouse 并没有和火山引擎云产品唯一绑定。
2021 年 8 月,ByteHouse 正式对外发布,开始服务于字节外部的更多用户。在字节内部,团队也在积极推动主要业务往新的云原生架构迁移。目前内部用户增长分析、BI 分析两大业务已经开始使用 ByteHouse 新架构,这两个业务也是字节内部迁移到新架构的第一批用户。受限于服务器资源,目前字节内部还有比较高比例的线上集群没有迁移到新架构,现阶段暂时是两个版本共存。陈星表示,这是迁移过程中的一个中间态,未来还是希望做到以云原生架构为主,除非有一些对延时或波动特别敏感的业务,在云原生架构没有彻底解决问题之前,可能会保留使用原来的架构。
当然,让业务方主动配合迁移不是一件容易的事,架构迁移的推进既需要 ByteHouse 团队做好对内部平台的适配,让接入和使用基本对用户无感,也需要团队持续优化 ByteHouse 的功能、性能和稳定性。
产品层面的下一步,邵祎旸表示,ByteHouse 团队会朝做最好的云上数仓这个方向努力,希望可以推动 SaaS 的进程,并在多云上面帮助用户做到开箱即用的体验。在已经具备高性能实时查询分析的基础上,团队会进一步关注诸如数据流实时性、上下游打通以及整体体验和兼容性的优化等方面的工作。
商业化层面,ByteHouse 一方面会践行火山引擎的整体战略,探索金融、汽车、大消费等不同行业的落地形态,希望找到更多可复制的场景;另一方面,ByteHouse 会对国内市场云中立 云原生的大数据分析平台的市场模式和分析洞察做更多探索。在海书山看来,这条路径目前国内厂商也在不断摸索之中,大家都还没有找到很好的答案。
采访嘉宾介绍:
陈星:火山引擎分析型数据库研发负责人 负责 ClickHouse、Doris 以及下一代云原生数据库的研发,主导全新架构的设计,把控分析型数据库的发展方向。高效稳定地支持内部多种业务场景背后的分析引擎,为外部不同行业领域的客户场景持续赋能,曾在 IBM 从事 DB2 内核研发工作。
邵祎旸:火山引擎 ByteHouse 高级研发工程师 负责分析型数据库引擎的产品化及 ToB 输出落地。针对性地解决客户使用场景中的痛点,为数据引擎提供企业级特性提升,包括数据资产管理,系统可观测性与运维工具,上下游对接等等。
海书山:火山引擎数据平台产品解决方案架构师 10 年数据行业从业经历,曾就职于 Merkle、Kyligence 等数字营销和大数据厂商,对数据仓库、大数据和云计算解决方案有深入研究和实践经验,尤其在金融、零售、汽车等行业拥有丰富的数据架构、实施和咨询经验。