咱们经常使用 Iceberg 构建湖仓一体平台的初衷是宿愿处置业务方在经常使用 Hive 数仓时的一些痛点。关键包括以下几慷慨面:
(1)Hive 的查问性能达不到交互式剖析的要求,所以经常须要把 Hive 的数据贮存到其它引擎当中。
(2)上一点形成了出仓链路越来越多,越来越复杂,保养老本高。
(3)另外,出仓的数据容易构成数据孤岛,形成数据冗余,造成存储老本下跌。
(4)最后,Hive 的时效性不好,即使用 FIink 流式的引擎写入,提前也会在小时级别。
咱们宿愿咱们的湖仓一体平台能够处置这些痛点,咱们的目的是:
(1)首先,平台要是互联互通的,要允许各种引擎的访问,防止数据孤岛的发生。
(2)第二,查问要高效,以满足交互式剖析的要求。
(3)第三,经常使用要尽或许的方便,尽或许降落业务方的门槛。
咱们的湖仓一体架构如上图所示,驳回 Iceberg 来存储数据,数据是在 HDFS 上。入湖的几条链路包括 FIink、Spark 引擎来写入,也提供 java 的 API,业务方可以间接经过 API 来写入数据,后盾有一个叫做 Magnus 的服务对 Iceberg 的数据启动始终的提升。另外咱们也用 Alluxio 来对数据启动缓存减速。咱们经常使用 Trino 来启动交互式剖析,对外提供查问接口。写入 Iceberg 的数据有一局部是要继续写入下游的 Iceberg 表。普通是数仓的分层建模的场景。只管咱们缩小了 Hive 出仓的链路,然而有一些场景或许 Trino 的查问还是达不到照应期间的要求。比如毫秒级的照应,或许还是会出仓到 ClickHouse、ES 等其它存储中。
上方繁难引见一下 Iceberg 的表结构,以及咱们为什么选 Iceberg 作为存储格局。
Iceberg 有文件级别的元数据治理。它基于 snapshot 来做多版本的控制。每一个 snapshot 对应一组 manifest,每一个 manifest 再对应详细的数据文件。咱们选 Iceberg 的一个比拟关键的要素是其放开的存储格局。它有着比拟好的 API 和存储规范的定义,繁难咱们在后续对它做一些配置上的裁减。
接上去引见咱们目前的一些比拟关键的上班。其中最**的一项是查问减速。
由于咱们面对的是 OLAP 的场景,普通是会有过滤条件的。所以咱们第一个思绪是如何尽或许过滤掉不须要扫描的数据。Iceberg 在文件级别记载了每一个列的一些统计消息,比如说 MinMax 值,这些统计可以在查问方案阶段就把一些不须要的文件过滤掉。咱们很直观的一个想法是,假设对数据启动排序,就会让相反的数据有更好的汇集效果,在过滤的时刻就会过滤掉更多的文件。
所以咱们最早是做了多维的排序。过滤字段或许有多个,不能用繁难的线性排序来做。线性排序只对靠前的排序字段有比拟好的汇集效果。所以咱们比拟了 Z-ORDER 和 Hibert Curve 这两种排序形式。从多维排序的成功来比拟,发现 Hibert 的汇集性会更好一点。所以咱们目前都是驳回 Hibert 的形式。不论是 Z-ORDER 还是 Hibert ,都要求介入排序的字段是一个整型值。关于非整型的数据,咱们用 Boundary Index 的形式来介入计算。
咱们会把数据依照须要多少区间,来切出不同的 Boundary。依据它的 Boundary Index 来介入 Z-ORDER 和 Hibert Curve 的计算。
有了排序,另一个疑问是多维的排序字段是无法以有限参与的。普通来说排序字段的个数越多,其汇集效果会越差。咱们对业务方的倡导是普通不要超越四个排序字段。假设有更多的过滤字段怎样办?咱们思考到关于一些基数比拟高的过滤字段,不去做排序,而是经过创立索引的形式,也能有一个比拟好的过滤效果。
咱们成功的索引是为了判别一个数据文件能否满足查问条件的要求。所以咱们的索引是文件级别的,一个表可以针对不同的列创立不同的索引。一个> 上方引见一下咱们允许的索引种类:
(1)BloomFilter: 计算比拟繁难,占用空间也比拟小。存在 false positive 的疑问,只允许等值的查问。
(2)Bitmap: 配置更弱小,允许等值和范围查问,婚配更精准,更精准是由于可以对多个条件婚配到的数据启动交并补计算,同时它前往的行号也可以协助进一步 skip 数据。Bitmap 的缺陷是占用空间比拟大,尤其是对一些高基数的字段,创立 Bitmap 索引,或许加载索引的期间曾经超越了过滤掉数据所浪费的期间,甚至会发生一些负向的效果。
(3)BloomRF: 咱们参考一篇论文,成功了一种 BloomRF 索引,它与 BloomFilter 的原理相似,然而用了多段的有序哈希函数来允许等值和范围的查问。它的存储开支也与 BloomFilter 相似。其疑问也是会有 false positive。
(4)TokenBloomFilter、NgramBloomFilter,TokenBitmap、NgramBitmap: 是针对 token 的索引,是为日志场景设计的。相当于对日志做一些分词的操作。分词成功,构建 BloomFilter 或许 Bitmap 这样的索引。TokenBloomFilter 和 TokenBitmap 针对的是英文的分词,Ngram 针对的是中文的分词。
除了索引以外,咱们也在做对估量算的允许,外部叫做 Cube,或许 AggIndex,是针对聚算计算的减速。目前允许单表和星型模型的查问。一个 Cube 的定义,关键定义两个消息:一个是 Cube 的维度字段;另一个是 Cube 须要的聚算计算,经常出现的如 count、min、max、count distinct 等都是允许的。另外聚合是做在文件级别的。
它是一个星型模型,lineorder 表是理想表,会关联 dates 、part 和 supplier 维表。假设要对这样一个查问场景去定义 Cube,一切须要在 group by 、where 语句中经常使用的字段都要作为维度字段。大家可以看到估量算是定义无理想表上的。它的估量算的定义是跟 lineorder 表关联的。然而这里经常使用到的一些列或许是有维表当中的列。咱们做了一个叫做关联列的成功。理想表不只可以用关联列来定义 Cube,同时也能用关联列对理想表的数据来启动排序和索引。像查问里,p_brand 上有一个过滤条件,Cube 数据也可以用到索引来启动过滤。上方的过滤条件也可以用来过滤理想表的数据。
定义了 Cube ,Magnus 服务会在后盾去担任 Cube 文件的生成。由于是文件级别的聚合,所以生成的逻辑是每一个文件会去关联其余的文件。比如这是理想表当中的一个> 对聚合值的处置,由于咱们做的是文件级别的聚合。所以真正查问的时刻,还须要把文件级别的聚合再做 global merge, 才干获取最终的一个聚合效果。这里分两种状况:
一种是可以间接累加的一些聚合值,如 min、max、count,在生成 Cube 文件的时刻,可以间接存储聚合结果;有一些不能间接累加,比如 Average,存储的是两边形态。查问时须要判别能否用 Cube 来照应,比如下图中展现的查问:
它是一个原始的逻辑方案。咱们会去找查问当中的 aggregation 节点。关于 aggregation 节点,判别其 source 表中能否存在一个 Cube 能满足聚算计算的要求。假设找到,会把逻辑方案启动转换。转换完,原来的 table scan 就会切换成 Cube 形式,就不去读原始的数据了,而是去读 Cube 文件的数据。由于 Cube 文件是异步生成的,所以就必需会存在一种状况,或许有一些文件曾经构建了 Cube,有一些文件或许还没有生成 Cube。查问改写这一侧会稍微有一点不一样。关于这种状况,咱们的处置思绪是把有 Cube 的局部,坚持跟原来一样的改写形式;没有 Cube 的局部,现场把 Cube 的数据算进去,与已有 Cube 的数据做一次性 union ,再做 global merge,这样可以获取一个最终的结果。
当然这个做法只实用于只要大批文件还没有 Cube 的状况。假设大局部文件都没有 Cube,那么间接退步成原始的计算会更好。
Cube 做好之后,咱们目前在探求用 star-tree index 对 Cube 来做一个增强。咱们参考了 Apache Pinot 的成功。
要处置的疑问是,Cube 是可以照应不同的维度组合的。比如 Cube 的定义或许选了三个维度,查问的时刻只用到了其中的两个或许一个,Cube 也是可以照应的。所以从节俭存储的角度来说,用最细粒度的维度来定义 Cube。这样只要要一个 Cube,就可以照应一切维度组合的查问。
然而假设维度选的比拟多,生成的 Cube,它的数据量也会比拟大。而且维度多了,聚合效果会变差。假设用最细粒度定义的 Cube,去照应很少维度的查问,两边还须要额外做很多聚合的计算。
假设针对每一个查问都去定义特定的 Cube,可以保障查问的时刻 Cube 必定是最优的。然而它的疑问是所须要的存储老本就会比拟高,一切不同的组合,都要成功,生成不同的 Cube 文件。
Star-Tree Index 宿愿在两者之间做一个折中。针对咱们的 Cube 生成 Star-Tree Index 这样一个数据结构。
举一个例子,比如我的 Cube 的定义是 Dim1、Dim2、Dim3 这三个字段,聚合值是 count。只管维度一共有三个,然而罕用的或许是 Dim1、Dim2 这两个。这时刻就可以依照 Dim1、Dim2 指定这两个维度字段来生成 star tree。star tree 是一个多叉树,每一层对应一个维度。每一层的节点是这一个维度的取值。比如 Dim1 的取值是 1、2、3,Dim2 的取值是 a、 b 、c。Star-Tee Index 会针对不同的取值来结构树的节点。每一层还会有一个不凡的 star 节点,star 节点的含意是疏忽掉这一层的取值,或许咱们以为 star 是一个通配符。所有聚合在一同,它的聚合的结果是多少。关于 star 节点,会额外生成一些 star record。star 节点上方的这些节点都会生成详细的一个 star record。比如例子外面,Dim1 取值是 “*” 的时刻,Dim2 或许有 a、d 这两种。假设查问当中只用到了 Dim2 这一个维度,那么可以经过 star record 来启动照应。由于我只要要思考 Dim1 为 “*” 的状况。
引见完查问减速,再来讲一下咱们目前做的智能提升的一些上班。
针对的是咱们的 Magnus 服务。咱们最基本的目的是宿愿尽或许降落用户的经常使用门槛。比如 Hive 用户,他或许须要了解一些大数据的原理;小文件多了,应该怎样处置,或许须要做一些兼并;Hive 表应该怎样做分桶,文件外部怎样做排序。咱们目前所处的一个阶段,叫做智能化的阶段。用户不须要知道这么多底层的常识。然而他还是须要通知我一些业务上的逻辑。比如罕用的过滤字段是哪些,罕用的聚合的模型是什么样子的。咱们再依据用户提供的消息来智能帮他去创立索引,去创立 Cube。
最终咱们是宿愿进一步简化,用户只是建表。表在建进去的经常使用环节当中,咱们可以对它做一个智能的继续的提升。Magnus 服务就是以此为目的来开发的。它关键担任的配置包括:
(1)一个是智能的后盾提升,目前一切 Iceberg 表的写入操作,Magnus 都会监听,当监听到写入事情后,它会依据自己外部的一些调度逻辑,经过 spark 义务对表启动一些操作,比如排序、创立索引、构建 Cube 等。
(2)另一个比拟关键的配置是,它可以协助咱们把 Iceberg 表的一些概略做一个图形化的展现,便于咱们定位和排查疑问。比如下图中显示的一张 Iceberg 表。
可以看到表是定义了排序字段的,在界面上可以看到它某一个分区下有多少个文件,这些文件有哪些曾经依照用户的要求做了排序,有哪些曾经依照用户的要求去构建了索引等等。
(3)第三个配置是智能化的介绍。成功形式是经常使用 Trino 把查问明细所有落库。
查问明细当中蕴含了每张表用到的过滤字段,Magnus 服务会去活期去剖析这些查问明细,联合用户的历史查问以及 Iceberg 表自身的统计消息。当然有一些统计消息或许是须要用 Trino 去现场计算进去的。联合这些消息,会给出一些提升倡导。
上方的例子展现的是 Magnus 对某一张表的一次性提升倡导。可以看到表外面用户原本是定义了排序和索引字段的。Magnus 剖析结果来看,首先是排序可以参与几个字段,同时可以删掉一些不用要的字段。索引也是可以去掉一些用不到的索引。后续咱们会思考依据介绍去验证效果。假设效果好,前面可以思考去智能协助用户启动修正。
最起初引见一下咱们目前落地的状况。
目前关键场景包括BI 报表、目的服务、A/B Test、人群圈选和日志等。
Iceberg 表总量大概为 5PB,日增 75TB。Trino 查问每天在 20 万左右 ,P95 的照应期间是 5 秒。咱们给自己的定位为秒级到 10 秒级。过滤的数据量(预算)为 500TB/ 天,占比约 100%~200%。