规范
建模篇
- 【强制】分区字段和分桶字段不能是同一个字段,分区字段作为分桶没有意义了,容易造成数据倾斜
- 【强制】建表必须写表的comment和字段的comment,而且需要是跟业务对应的有意义的comment,平台的建表页面可以直接同步hive表的字段comment,推荐使用
- 【强制】doris表模型,数仓给的聚合结果使用replace模型(类似于商品看板、经营大盘),数仓给的明细数据使用聚合模型,禁止数仓给的明细数据使用duplicate模型
- 【强制】doris表只保留本次需求需要的字段,数仓表中多余的字段禁止导入到doris,如果后续需求迭代需要,再进行导入
- 【推荐】日周月创建不同的doris表,这样能最合理的走分区和rollup,如果数仓只提供天维度的表,导入周月就相当于导入压力增大了两倍;不建立月表,通过日表创建rollup的方式出,由于rollup只是分区内创建,所以月的查询没法走月分区查询,查询性能肯定是没有直接用月表来的快;总之又是导入和查询之间的权衡
- 【推荐】分桶字段可以使用bu_id(仓维度使用wh_id)来分桶,分桶可以简单理解成es的分片,目前新集群的doris机器5台,分桶不宜太多,建议设置在3-8之间,具体根据数据量来权衡,数据量越大,可以设置的多一些
- 【推荐】建表最好不要自己使用sql创建,建议使用平台的建表页面创建,目前平台只支持分区为日的表创建,分区为周、月的无法使用,后续都支持了之后,该条规范将转为【强制】
查询篇
- 【强制】聚合模型的doris表中,禁止在聚合函数中增加if等函数,例如sum(if ….),添加了函数之后该查询无法走rollup;通过explain查看执行计划,会发现ScanNode中PreAggregation对应的值是OFF,OFF就表示不能走rollup
- 【强制】页面传指标id查询具体某些指标的功能(例如指标定制、点击核心指标的对应曲线查询),后端数据库查询,必须只查询前端传的指标,禁止一次性通过数据库查询出所有指标,然后只给前端返回他请求的;
- 【强制】duplicate或者unique模型,禁止使用select *,强制原因:doris是列式存储,比如说id=1这条数据,对应的各个列的值是存储在独立的文件中的,不像mysql这种行式存储,一行数据是物理存储在一起的;所以对于doris来说,查询一列跟查询多列,在io上的性能消耗是成倍增加的
- 【强制】拼sql进行查询的时候,必须明确字段类型,int类型的sql必须写成
dt=20210101
,不能写成dt='20210101'
,否则走不了分区查询(ps. 好像后来又能走了,不过还是按照字段类型写比较合理) - 【强制】禁止代码中使用多线程查询doris,doris对cpu很敏感,如果觉得接口性能不好,需要努力的方向是优化接口中的慢sql,而不是并发的去执行这些sql,假设一个sql需要500ms执行完成,那么即使一个查询中有10个sql,也只需要5s时间,足够满足用户的需求;
- 【推荐】下钻查询尽量不要一次性返回所有数据(用户点下钻的时候前端不需要请求后端);推荐理由:一次性查询对数据库压力会更大,而且对于我们的页面来说,大部分用户不会点下钻,只会看汇总,一次性查询所有的是对查询资源的浪费,尤其是后端需要实时聚合的查询;该规范后续最好转成【强制】
拒绝聚合函数中增加if函数
doris如果在聚合函数中增加其他函数,是没有办法走rollup的。
通过explain查看执行计划,会发现ScanNode中PreAggregation对应的值是OFF,OFF就表示不能走rollup
对维度列使用聚合函数,不会影响rollup的使用
1 | # 表是到sku粒度的,同时创建了到三级类目的rollup |
不需要的列不要查询
1 | # 下面这个曲线的查询,每次最多展示2个曲线,这里一次把所有的结果都查询出来了,查询时间在1.3s左右 |
排序对于性能的影响也很大
1 | # 查询某天的sku的指标,需要根据销售额排序,查询列是*,发现查询很慢,需要8s+ |
查询的时候明确字段类型
字段类型如果是int,但是sql中使用的条件是字符串类型的值,比如说dt='20210423'
,这种是没有办法走分区查询的,explain查看执行计划,发现分区扫描还是会扫面全部分区,而不是只扫描查询条件中对应的分区
将维度名称字段作为维度列对查询性能的影响
目前针对维度名称,有三种处理方式:
- doris表中不存储名称字段,名称字段通过维表查询
- doris表中存储名称字段,存储的是作为指标,用max的聚合方式
- doris表中存储名称字段,作为维度,跟id字段并列
第一种跟doris没关系了,先不考虑;后面两种第一种经过了验证,作为指标,对查询性能基本上没有影响;
但是最后一种,作为维度,要想查询出名称,需要在group by后面加上名称列,这个需要验证是否会影响性能,增加分组列还是对性能有一定影响的。
grouping set
1 | SELECT |
- GROUPING SETS对应两个,其中(id)这个grouping,对应的结果,name一定是null
- 但是(id, name)这个grouping,如果数据中name本来就有null,那么也可能是null
- 怎么区分name的null到底是本来的null还是因为聚合函数中没有它才返回的null,通过GROUPING(name)函数可以判断,这个函数的返回值是1,说明一定是(id)这个grouping生成的,通常这个grouping生成的是聚合结果,需要放在前面
相关文档:
grouping set的缺点
如果grouping set中对应的分组组合,维度粒度相差非常大的话,性能会有问题,比如说全部品类维度和sku维度,第一个是所有sku的汇总,另一个是sku明细的展示。
- 因为sku是最细粒度,所以只能走底表查询,相当于汇总数据也就不能走rollup了,只能从底表实时聚合,性能差;
- 正常页面都是有分页的,如果汇总满足了条数,明细都不需要进行查询了,这时候通过grouping set会进行多余的查询,浪费资源、性能也不好
解决方案,代码分多次查询,对结果进行组合:
- 先查询汇总的总条数,如果超过3000条,不需要查询明细条数和明细数据
- 如果汇总条数小于3000条,那么需要查询明细的条数,返回给前端总条数
- 判断前端请求的页的数据,需要查询哪个维度,还是两种维度都需要查询
bucket数量对查询的影响
bucket数量不能太少也不能太多,太少影响查询性能
- 极端情况下:一个分区只有一个分桶,那么相当于某个日期的查询只会用到一台机器的资源
- tablet的查询好像是并发查询的,如果分桶非常多,并发也会非常大,就会造成doris的cpu使用率比较高
经验值:
- 聚合结果的查询:由于没有什么计算,而且不会有多事业部的实时聚合,按照事业部分桶多一些倒也无妨,因为只会查询某个事业部,命中某个tablet,即使多个事业部,因为是聚合结果,也是重io,轻计算的;但是分桶最好也不要超过机器的数量
- 明细数据的实时聚合查询:分桶数量不要超过机器数量
一个查询打满doris
供应商看板的一个下载接口,一次下载操作就将doris打满了,原因是内部采用多线程查询doris,而且表中有很多的join;由于是下钻查询,查询doris的次数应该有一千多次,查询次数太多,虽然不是1000个查询并发,但是10几个并发,一直持续100次,压力也是很大的。
bitmap的全局字典
bigint能支持的最大数字是:18446744073709551615
针对日均、单城类型的指标,需要进行多字段的拼接来构建bitmap,例如日均单城在售sku数:dt+bu+onshelf_sku_id
直接拼接的成数字的话,可能会造成超出bigint的最大范围,例如20210527+11000039+50004121
拆分明细数据和聚合数据
类似于卖家多维分析这种,需要明细数据、类目聚合数据的情况,因为需要品类鉴权,所以需要数仓给明细结果,服务端实时进行聚合。
但是在查询性能上明细查询从聚合模型上出,性能不是很好,例如下面的两个sql:
1 | # 聚合查询明细指标,查询时间5s |
可以看出,查询同样的数据,聚合方式的查询时间是直接查询明细的50倍;所以建议在建模的时候:
- 三级类目以下的结果(四级、sku),直接通过数仓聚合的方式,将结果计算好,doris通过unique key的模型进行数据存储,查询的时候只需要select*,不需要group by
- 三级类目以及以上的结果,通过doris的聚合模型,将数仓提供的明细数据聚合到三级类目粒度,查询的时候实时通过doris进行聚合查询
一个指标关联多种维度应该如何查询
比如说销售额占比这个指标,公式是:当前所选类目销售额除以事业部总的销售额;这种如何动态生成sql
duplicate模型与rollup
duplicate模型也可以创建rollup,但是作用跟聚合模型的rollup不太相同:
- 聚合模型的指标列会进行聚合计算,但是duplicate模型不会有指标列的概念,更不会进行聚合运算
- duplicate模型创建的rollup也可以选部分列,但是只相当于是二级索引,rollup中的数据条数是跟base表完全一样的,只是列发生了变化
一个创建rollup的例子:
1 | CREATE TABLE kldp_data_stat_test_stage.doris_sm_star_pa_org_area ( |