Scala 在大量分区上处理upsert不够快

Scala 在大量分区上处理upsert不够快,scala,apache-spark,databricks,delta-lake,azure-data-lake-gen2,Scala,Apache Spark,Databricks,Delta Lake,Azure Data Lake Gen2,问题 我们在ADLS Gen2上有一个Delta Lake设置,包括以下表格: brown.DeviceData:按到达日期进行分区(分区日期) silver.DeviceData:按事件日期和时间划分(Partition\u date和Partition\u hour) 我们将大量数据(每天超过6亿条记录)从活动中心摄取到brown.DeviceData(仅附加)。然后,我们以流式方式处理新文件,并使用delta MERGE命令(见下文)将其插入到silver.DeviceData中 到达

问题

我们在ADLS Gen2上有一个Delta Lake设置,包括以下表格:

  • brown.DeviceData
    :按到达日期进行分区(
    分区日期
  • silver.DeviceData
    :按事件日期和时间划分(
    Partition\u date
    Partition\u hour
我们将大量数据(每天超过6亿条记录)从活动中心摄取到
brown.DeviceData
(仅附加)。然后,我们以流式方式处理新文件,并使用delta MERGE命令(见下文)将其插入到
silver.DeviceData

到达Brown表的数据可以包含来自任何silver分区的数据(例如,设备可以发送它在本地缓存的历史数据)。但是,任何一天到达的数据中,超过90%来自分区
partitions\u Date IN(当前日期(),当前日期()-间隔1天,当前日期()+间隔1天)
。因此,为了升级数据,我们有以下两个spark任务:

  • “Fast”:处理来自上述三个日期分区的数据。延迟在这里很重要,因此我们对这些数据进行优先级排序
  • “Slow”:处理其余部分(除了这三个日期分区之外的任何部分)。延迟并不重要,但应该在“合理”的时间内(我认为不超过一周)
现在我们来讨论这个问题:虽然“慢”作业中的数据量要小很多,但它运行了几天,只处理一天的慢青铜数据,并且有一个大集群。原因很简单:它必须读取和更新许多银色分区(有时大于1000个日期分区),而且由于更新很小,但日期分区可能是千兆字节,因此这些合并命令效率很低

此外,随着时间的推移,这个缓慢的工作将变得越来越慢,因为它所接触到的银分区将增长

问题

  • 我们的分区方案和快/慢Spark作业设置通常是解决这个问题的好方法吗
  • 可以做些什么来改进此设置?我们希望降低慢作业的成本和延迟,并找到一种方法,使其随着任何一天以铜牌形式到达的数据量而增长,而不是随着银表的大小而增长
  • 附加信息

    • 我们需要MERGE命令,因为某些上游服务可以重新处理历史数据,然后也应该更新silver表
    • silver表的架构:
    创建表silver.DeviceData(
    DeviceID LONG NOT NULL,--发送数据的设备的ID
    数据类型字符串不为NULL,--它发送的数据类型
    Timestamp Timestamp NOT NULL,--数据点的时间戳
    值DOUBLE NOT NULL,--设备发送的值
    UpdatedTimestamp TIMESTAMP NOT NULL,--值到达时的时间戳
    分区日期日期不为空,--=截止日期(时间戳)
    分区\u Hour INT非空--=小时(时间戳)
    )
    使用DELTA
    分区人(分区日期、分区小时)
    位置“…”
    
    • 我们的合并命令:
    val silverTable=DeltaTable.forPath(spark,silverDeltaLakeDirectory)
    val批处理=…//流式更新批处理
    //我们要为分区修剪而插入的日期和时间
    //从流式更新批处理中收集
    val dates=“…”
    val hours=“…”
    val mergeCondition=s“”
    silver.u日期单位($dates)
    和silver.u小时($hours)
    silver.Partition\u Date=batch.Partition\u Date
    silver.Partition\u Hour=batch.Partition\u Hour
    而silver.DeviceID=batch.DeviceID
    和silver.Timestamp=batch.Timestamp
    和silver.DataType=batch.DataType
    """
    silverTable.别名(“silver”)
    .merge(batch.alias(“batch”),mergeCondition)
    //仅当事件较新时合并
    .when匹配(“batch.UpdatedTimestamp>silver.UpdatedTimestamp”).updateAll
    .when.insertAll
    .执行
    
    在Databricks上,有几种方法可以优化
    合并到
    操作的性能:

    • 对属于联接条件的列执行带ZOrder的优化。这可能取决于特定的DBR版本,因为旧版本(7.6 IIRC之前)使用的是real ZOrder算法,该算法适用于较少的列数,而DBR 7.6+默认使用Hilbert空间填充曲线
    • 使用较小的文件大小-默认情况下,
      OPTIMIZE
      创建1Gb的文件,这些文件需要重写。您可以使用
      spark.databricks.delta.optimize.maxFileSize
      将文件大小设置为32Mb-64Mb范围,以便重写更少的数据
    • 在表的分区上使用条件(您已经这样做了)
    • 不要使用自动压缩,因为它不能执行ZOrder,而是使用ZOrder运行显式优化。详情见
    • 调优,这样它将只索引条件和查询所需的列。它与合并部分相关,但可以稍微提高写入速度,因为不会为不用于查询的列收集统计信息
    本文讨论了
    合并到
    中的优化-要观察哪些指标,等等


    我不能100%确定您是否需要条件
    silver.Partition\u Date IN($dates)和silver.Partition\u Hour IN($hours)
    ,因为如果传入数据中没有特定的分区,您可能会读取比所需更多的数据,但需要查看执行计划。这说明了如何确保
    合并到
    中使用分区修剪。

    您使用的是Datatricks,还是您自己的delta lake?@AlexOtt其Datatricks“使用较小的文件大小”-我发现这非常有趣,现在每个(日期,小时)分区包含一个300MB的文件,据我所知,这意味着ZORDER或bloom filter指数