Spark多列去重/聚合:哈希列vs原列方案性能对比
Spark DataFrame多列去重方案对比:哪种更优?
我需要对Spark DataFrame的3列(id_1、id_2、id_3)执行去重操作,现有两种方案,结合以下补充条件,想知道哪种更优:
方案一
df = df.withColumn( "hash_dup", f.hash( f.coalesce(f.col("id_1"), f.lit("")), f.coalesce(f.col("id_2"), f.lit("")), f.coalesce(f.col("id_3"), f.lit("")) ) ).dropDuplicates(["hash_dup"])
方案二
df = df.dropDuplicates(["id_1","id_2","id_3"])
补充说明
- LE1:针对TB级高基数键组合数据,Spark会使用SortAggregate而非HashAggregate。
- LE2:考虑到Hash为Int类型,比多列字符串占用内存更少,想了解数据类型和列数是否会影响性能。
- LE3:鉴于hash存在碰撞问题,猜测使用sha2进行对比会更优。
结论先行:方案二更优,且不建议用hash/sha2替代原字段去重
1. SortAggregate场景下的性能对比
在TB级高基数、依赖SortAggregate的场景里,方案二直接基于原字段去重的性能反而更好:
- 方案一多了一步
withColumn计算hash列的开销,TB级数据量下,这部分额外计算会被大幅放大,拖慢整体执行速度。 - SortAggregate的核心是排序+去重,不管是排序原字段还是hash值,时间复杂度都是O(n log n)。但原字段排序时,Spark能利用列存储的优化特性(比如Parquet/ORC的字典编码、列压缩)减少IO开销;而hash列是临时计算生成的,没有这些优化加持,实际IO和内存占用未必比原字段更优——哪怕hash是Int类型,你也得先把原字段读出来计算hash,这一步的内存开销已经存在了。
2. 数据类型与列数的影响
你提到hash是Int类型内存占用更少,但这里存在误区:
- Spark处理字符串字段时,会通过字符串池复用重复值,即便高基数场景下,原字段的内存管理也经过了优化;而计算hash时,每个字段组合都要生成新的Int值,这部分内存是额外新增的。
- 多列复合排序时,Spark的SortAggregate对内存的管理是专门优化过的,不会比单列hash排序占用更多内存——反而因为不需要额外存储hash列,整体内存开销更低。
3. hash碰撞与sha2的问题
f.hash返回的是Int类型,碰撞概率极高,TB级高基数数据下,几乎一定会出现不同字段组合生成相同hash的情况,直接用hash去重会导致错误去重(误删不同行),这是业务上绝对不能接受的。- 换成sha2(比如
sha2(concat_ws("|", id_1, id_2, id_3), 256))确实能降低碰撞概率,但sha2生成的是长字符串,内存占用比原字段组合还大,而且计算sha2的开销远高于原字段排序,完全得不偿失。 - 哪怕要做哈希去重,也必须在hash之后用原字段做二次校验,但这样就失去了hash去重的意义,反而比方案二多了计算hash+二次校验两步操作,性能更差。
总结
对于TB级高基数数据的去重,直接用dropDuplicates指定原字段是最优选择:既保证结果完全正确,又避免了额外计算开销,还能利用Spark对原字段存储的优化特性。
内容的提问来源于stack exchange,提问作者bmcristi
相关产品推荐
相关产品推荐

