广告卖家通过卖家中心(Seller Hub)的营销标签页、效果标签页和公开API,有效掌控和对比店铺的营销活动和推广商品的流量、销量的实时和历史数据,并通过网页或者 API 下载数据分析报告。
这一系统上线之初使用了自研的分布式 SQL 引擎,构建在对象存储系统之上。3 年前随着广告流量增加,我们把数据引擎切换到 Druid 上。
这一平台的主要挑战如下:
-
数据量大:
每日的插入数据记录有数百亿条,每秒的插入峰值接近一百万条。
-
离线数据摄入:
在不影响实时数据摄入的情况下,每天需要对前 1-2 天的数据进行在线替换。
根据上游数据团队发布清洗过的每日数据,广告数据平台需要在不影响查询的情况下每日替换实时数据,数据切换要求实现跨节点的全局原子操作。
-
完整性和一致性:
面向卖家的财务数据,离线更新后的数据要求不能有遗漏和重复;实时数据要求端对端的延迟在十秒内。
Druid 于 2011 年由 Metamarkets 开发,是一款高性能列式在线分析和存储引擎。
它于 2012 年开源,2015 年成为 Apache 基金会旗下项目。
Druid 在业界使用广泛,为千亿级数据提供亚秒级的查询延迟,擅长高可用、水平扩展。
另外为数据摄入提供了很多非常方便的聚合、转换模版,内建支持多种数据源,最快可以在几十分钟内配置好新的数据表,包括数据定义和数据摄入链路(Lambda 架构),大大提高了开发效率。
ClickHouse 由俄罗斯最大的搜索引擎公司 Yandex 研发,设计目标是支持 Yandex.Metrica(世界第二大 Web 分析平台)生成用户分析报表等核心功能。
ClickHouse 是一个数据库管理系统(DBMS),有数据库、表、视图、DDL、DML 等概念,并提供了较为完整的 SQL 支持。
其核心特性有如下几点:
-
高效的数据存储:
通过数据压缩和列式存储,可以达到最高 10 倍的数据压缩率。
-
高效的数据查询:
通过主键索引、向量化引擎处理、多处理器并发和分布式查询,最大压榨 CPU 的所有能力,在中小规模的数据量上尤为突出。
-
灵活的数据定义和接入:
通过支持 SQL 语言、JDBC 和关系模型,降低学习和迁移成本,可以和其他现有数据的产品无缝集成。
Druid 虽然提供了很多非常方便的数据摄入功能,但它的组件构成也较为复杂,节点类型有 6 种(Overload,Coordinator,Middle Manager,Indexer,Broker 和 Historical)。
除了自身的节点,Druid 还依赖于 MySQL 存储元数据信息、Zookeeper 选举 Coordinator 和 Overlord、HDFS 备份历史数据。
ClickHouse 的架构采用了对等节点的设计,节点只有一种类型,没有主从节点。如果使用了副本功能,则依赖于 Zookeeper 保存数据段的同步进度。
与此同时,eBay 的基础架构团队提出在定制 ClickHouse 的基础上,向产品团队提供列式数据库存储的服务。
除了运维和生命周期管理,基础架构团队对 ClickHouse 进行改造和二次开发,进一步提高了数据摄入和存储的效率,并在离线摄入方面弥补了和 Druid 的功能差距。
Druid 通过引入实时数据的索引任务,把实时数据处理成一个个分段数据(segment),并归档成历史数据。成为分段数据之后,该时段数据即不可写入。
由于并发实时索引任务数的限制,我们设置了 3 个小时的窗口长度(每个小时一个任务),因此超过 3 个小时的数据就无法写入。
在某些极端情况下,例如上游数据延迟或者实时数据消费过于滞后,就会导致离线数据替换前这部分数据的缺失。ClickHouse 则没有这个限制,任意分区都可以随时写入。
ClickHouse 支持的主键并不是传统意义下关系型数据库的主键。传统的主键要求每条表记录都有唯一的键值,通过查询主键可以唯一地查询到一条表记录。
而在 ClickHouse 中,主键定义了记录在存储中排序的顺序,允许重复,所以称之为排序键似乎更加合理。
事实上在 ClickHouse 里的主键定义通过 ORDER BY 声明,仅在个别场景中允许和排序键不一致(但必须是排序键的前缀)。
由于我们的产品是给卖家提供分析功能,几乎所有的查询限定在了单一卖家维度,因此通过主键按照卖家排序,可以极大地提高查询效率以及数据压缩率。
图 1
如图 1 所示,系统由 4 个部分组成:
-
实时数据获取模块,接入 eBay 的行为和交易实时消息平台。
-
离线数据替换模块,接入 eBay 内部的数据仓库平台。
-
ClickHouse 部署和外围数据服务。
-
报表服务,支撑广告主、商家后台和 eBay 公开 API。
ClickHouse 提供了丰富的 Schema 配置。这方面需要根据业务场景和数据模式反复斟酌和多次试验,因为不同的选择会对存储和性能有数量级的影响,一个错误的选择会导致后期巨大的调优和变更成本。
①表引擎
ClickHouse 的存储引擎的核心是合并树(MergeTree),以此为基础衍生出:
另外上述所有的合并树引擎都有复制功能(ReplicatedXXXMergeTree)的对应版本。
我们的广告数据平台的展示和点击数据选择了复制汇总合并树。这两类用户行为数据量极大,减小数据量节省存储开销并提升查询效率是模式设计的主要目标。
ClickHouse 在后台按照给定的维度汇总数据,降低了 60% 的数据量。
销售数据选择了普通的复制合并树,一方面由于销售数据对某些指标有除汇总以外的聚合需求,另一方面由于本身数据量不大,合并数据的需求并不迫切。
②主键
一般情况下,ClickHouse 表的主键(Primary Key)和排序键(Order By Key)相同,但是采用了汇总合并树引擎(SummingMergeTree)的表可以单独指定主键。
把一些不需要排序或者索引功能的维度字段从主键里排除出去,可以减小主键的大小(主键运行时需要全部加载到内存中),提高查询效率。
③压缩
ClickHouse支持列级别的数据压缩,显著地减少原始数据的存储量,这也是列存储引擎的巨大优势。查询阶段,较小的存储占用也可以减少 IO 量。
对不同列选择一种合适的压缩算法和等级,能把压缩和查询的平衡做到性价比最优。
ClickHouse 的所有列默认使用 LZ4 压缩。除此以外,一般的数据列可以选择更高压缩率的算法如 LZ4HC,ZSTD。
而对于类似时间序列的单调增长数据可以选择 DoubleDelta,Gorilla 等特殊压缩算法。
LZ4HC 和 ZSTD 等高压缩率的算法还可以自己选择压缩级别。在我们的生产数据集上,ZSTD 算法对 String 类型字段压缩效果较为显著。LZ4HC 是 LZ4 的高压缩比改进版,更适用于非字符串类型。
更高的压缩率意味着更少的存储空间,同时由于降低了查询的 IO 量,可以间接提升查询性能。
不过 CPU 也不是大风刮来的,数据的插入性能就成了牺牲品。根据我们内部测试的数据,在我们的生产数据集上使用 LZ4HC(6) 相比 LZ4 可以节省 30% 的数据,但实时数据摄取性能下降了 60%。