作者回复: 第一题超赞~ 这个方法非常好,实际上我觉得比较hash的方法还棒!因为它天然能避开冲突的问题,而且存储效率很高,机智如你~ 真的特别好的方法,其实本质上这可以看成是一种编码方式了,老弟你也搞机器学习吗?编码的思路在Machine Learning用的非常普遍~ 当然这里有一个字典维护的成本,不过如果Join Keys的cardinality不是很夸张的话,其实还好~ 2、3的思路都没问题~
作者回复: SHJ和SMJ,最主要的区别,是SMJ可以利用外排,或者说可以依赖磁盘,对于内存的依赖,没那个重。但是SHJ就不同,HJ的build phase,必须完全依赖内存,这也是为什么会对小表的数量体量、数据分布有要求。一旦小表某一个分区,超过Task内存上限,就直接报OOM,整个Job也就fail了
作者回复: 前两题满分💯~ 第三题的话,AQE是需要Shuffle才能生效的,因此,要想利用AQE消除数据倾斜,得先对小表做repartition才行。 另外一种可行的办法,就是可以参考后面29讲负隅顽抗里面的“两阶段加盐Shuffle”,通过加盐把数据打散,让原本不均衡的数据,变得均衡,然后再用SHJ去做关联。当然,这种方法的开发成本会高很多,不过算是一种比较通用的方法。 这些方法,这些“招儿”,其实不好说孰优孰劣,还要结合具体数据量、场景,结合实际效果去看。不过,艺多不压身,调优比较重要的一点,就是要思考广、“招儿”多,然后通过对比不同优化方法的优劣势和效果差异,来更好地理解Spark,这样后面再有类似情况,就能轻车熟路啦~
作者回复: 确实,本质上就是Join Key和分区键的矛盾。 分区键要求Cardinality不能太高,否则就会出现文件分布过细、过散,分布式文件系统负担过重。但是另一方面,Join Key往往是userId、orderId、txId这类Cardinality很大的字段,这个是由业务逻辑决定的,很难改变。 因此,分区键和Join Key就会存在博弈,换句话说,是牺牲文件系统效率来强行触发DPP,还是保证文件系统效率而放弃DPP,这里面就会有一个取舍的问题。
作者回复: 编码的思路很赞~ 不过用自增列是不行的,同样的Join Key,应该有同样的编码,这样才不影响原有的关联逻辑,自增列会导致同样的Join Key,有不同的编码。 比如,Join Key是order_id,那么在transactions表中,会有多个同样的order_id出现,也就是多笔交易同属于一个订单,这个时候,自增列编码是不行的。
作者回复: 对,做小维表的目的,实际上还是把Shuffle joins转化成Broadcast joins,这个转化带来的收益实在是太大了!所以千方百计地尝试做Broadcast joins,确实是非常值得的一件事~ 预估大小UI没问题,另外还可以用executePlan查看执行计划的方法做预估。
作者回复: 不可以哈,Spark SQL目前还没有那么智能,你这么写的效果,和单独把users.type = ‘Head User’当作过滤条件的效果是一样的,都是通过Spark SQL AQE来动态触发Join策略调整,还做不到在静态优化阶段就提前决定在运行时把Shuffle Joins转化为Broadcast Joins。
作者回复: 确实,可以利用AQE的自动倾斜处理来handle 20G的小表,不过其实我们在下一章节会介绍,AQE的自动倾斜处理,只能handle task粒度的倾斜,没有办法处理Executors粒度的倾斜,这个时候,还是需要依赖两阶段的Shuffle来完成,具体的加盐操作可以参考后面一章哈
作者回复: 是的, 这个use case比较特殊,就是Join Keys特别多,而且都是String类型,因此整体数据体量很大。确实,像你说的,使用哈希的目的,就是缩减Join Keys大小,那么咱们自然要确认缩减之后的Join Keys,一定要远远小于原始的Join Keys,否则做这件事就没有意义啦~ Tungsten那个,整型占用4个字节,这个和数据类型有关,其实和Tungsten没什么关系的。
作者回复: 采用userId做分区键确实太细,实际应用中,我们几乎不会用这么细的ID做分区键。 先来说分桶吧,首先分桶一来能避免shuffle,二来能节省I/O(Bucketing pruning),不过,单纯用分桶,是不能触发DPP机制的。 再来说说分区+分桶,你是指采用不同的Keys吗?比如分区用比较粗粒度的字段、分桶用userId,是这个意思吗?假设分区键不是userId,那么即便分桶用了userId,Spark SQL也不会触发DPP。 DPP的条件确实比较苛刻,要求分区键包含Join Keys,因此分桶并不能帮上什么忙。 不过,尽管如此,分桶在避免Shuffle上倒确实是把好手~