Apache Spark - 深入了解存储格式(译)

Apache Spark - Deep Dive into Storage Format’s

Apache Spark一直在快速发展,包括对核心API的更改和添加,Spark是一种内存大数据处理系统,内存是它不可或缺的关键资源。因此,有效使用内存对它来说非常重要。让我们尝试在本文中找到以下问题的答案:

  • Spark使用什么存储格式?
  • 存储格式是如何在一段时间内发展的?

Spark使用什么存储格式?

  • Spark 1.0 to 1.3: 它从RDD开始,其中数据表示为Java对象。
  • Spark 1.4 to 1.6: 放弃 Java objects. DataSet 和 DataFrame 向数据以行为基础的存储的地方发展.
  • Spark 2.x: 支持向量化的 Parquet,它是柱状内存数据。

image

存储格式是如何在一段时间内发展的?

这个问题的答案不仅有趣而且冗长。让我们深入探讨驱动因素以及存储格式如何演变为两个部分的过程 :

  1. 从RDD到基于行的数据集的进步.
  2. 从基于行的数据集到基于列的Parquet.

Part1:第1部分:数据存储格式的演变 - RDD的提升到基于行的数据集。

如果不深入了解Project Tungsten,我们无法找到这个问题的答案。只需顺其自然,你就可以推断我们为什么要尽快研究Tungsten project。Tungsten项目自成立以来一直是Spark执行引擎的最大变化。这个项目导致了Spark的一些根本性变化。一个这样的变化催生了DataSet。让我们首先看一下Project Tungsten的目标。

Objective of Project Tungsten

Image大幅提高Spark应用程序的内存和CPU效率。推动性能接近现代硬件的极限.

为什么这个目标?

对CPU效率的关注是由于Spark工作负载越来越受CPU和内存使用而不是IO和网络通信的影响。要更好地理解这一点:

  • 让我们看看过去7年的硬件趋势.从图中可以看出,DISIK I/O和网络I/O的速度提高了10倍,但CPU保持相同:

image

  • 另一个值得注意的趋势是,在Spark的shuffle子系统中,序列化和hash(受CPU限制)已经被证明是关键的瓶颈,而不是底层硬件的原始网络吞吐量。

Problem1:

首先要做的是…在考虑提高效率的解决方案之前,我们需要了解旧执行引擎的问题。为了更好地理解这一点,让我们简单地完成一个过滤整数流的任务,看看spark 1.x如何解释它。
Image
得到一个integer的流,过滤出所有输入中是1的数字->如何告诉spark?->spark如何解释?->问题是 filter对spark是不透明的,他不知道匿名函数做一个简单的常量比较或者是一个复杂的计算来返回一个boolean

这个查询计划有什么问题?

如上图所示,Spark不知道:

  • 用户尝试对数据执行什么操作?
  • 数据的类型是什么?

由于缺乏透明度,优化查询计划几乎没有任何余地。

spark需要什么透明度?

Spark在两个方面需要透明度:

  1. Data Schema:
  • 每个数据记录有多少和什么字段?
  • 每个字段的数据类型是?
  1. User operation:
  • Spark应该知道用户试图在数据记录的哪个字段上执行什么样的操作。

这种透明度如何帮助Spark?

它有助于提高性能。用一个例子更容易说明它。考虑加入两个输入df1和df2。
JoinCondition: column x of df1 = column y of df2.

下图描绘了spark生成的连接及其查询计划:
Image
因为连接条件是匿名函数(myUDF),所以spark执行此连接的唯一方法是:

  1. 用df2进行df1的笛卡尔积。
  2. 现在, 对笛卡尔结果应用 filter fn (myUDF), 这是一个使用df1 [x] == df2 [y]条件进行过滤的匿名函数。运行时间= n ^ 2!

透明度如何有助于改善这一点?

上述查询中的问题是join条件是一个匿名函数。如果spark知道连接条件和数据模式[x和y列的数据类型]那么spark会::

  • 首先按x排序df1
  • 接下来按y排序df2
  • SortMerge通过df1 [x] == df2 [y]加入df1和df2 !! 运行时间减少到nlogn !!
    Image

行动计划1:

  1. 让用户注册dataschema - 这将引发它所处理的数据的透明度
  2. 使用户想要对数据执行的操作对Spark透明

问题2:

Spark试图在内存中做所有事情.因此,接下来的问题是要知道是否有办法减少内存占用。我们需要了解数据如何在内存中布局。

数据如何在内存中布局?

用RDD的数据存储为Java对象。每当我们想要对这些java对象和shuffle执行操作时,就会发生大量的序列化,反序列化,散列和对象创建。除了这个Java对象有很大的开销。

应该停止依赖JavaObjects吗?为什么?

Ans: Java对象有很大的开销考虑一个简单的字符串“abcd”。人们会认为它需要大约4个字节的内存。但实际上,存储值“abcd”的java字符串变量将占用48个字节。其细分如下图所示。现在,想象下面图片右侧显示的JavaObject(如Int,String,String)的大量开销。
Image

行动计划2:

替代java对象,提出了一种更紧凑,更少开销的数据格式

Action Plan1 + Action Plan2 together:

  1. Have user register dataschema. -这将激发它处理的数据的透明度
  2. 使用户想要对数据执行的操作对Spark透明.
  3. 创建更紧凑,更少开销的新数据布局.
这为“数据帧和数据集”铺平了道路

什么是DataSet / DataFrame?

数据集是一个强类型,不可变的对象集合,其中包含两个重要的更改,这些更改引入了我们在行动计划中讨论的内容:

  • 引入了新的基于二进制行的格式
  • 数据模式注册

引入了新的基于行的二进制格式:

  • 为了避免java对象的大量开销,Spark调整了新的基于二进制行的存储,如下表所述:

Image

下图说明了一个例子。在这个例子中,我们采用了一个tuple3对象t (123, “data”, “bricks”) and 让我们看看它是如何以这种新的行格式存储的。
Image

  • 第一个字段123作为其原语存储在原位。
  • 在接下来的2个字段databricks是字符串,是可变长度的。因此,这两个字符串的偏移量存储在原位[ 32L并48L分别显示在下图中].
  • 存储在这两个偏移中的数据的格式为“长度+数据”。在偏移32L,我们存储4 + data,同样在偏移48L我们存储6 + bricks.

数据的schema

以下示例显示了如何注册数据模式:在此示例中,我们正在创建“学生”数据集.

case class Student(id: Long, name: String, yearOfJoining: Long, depId: Long)
val students = spark.read.json(“/students.json").as[Student]

注意 .as[Student] 函数调用是使用Spark注册输入学生数据的schema.

让我们看看如何在数据集上应用转换

转换只不过是简单的操作,如过滤,连接,映射等,它们将数据集作为输入并返回新的转换数据集。请在下面找到两个转换示例:

  1. Filter students by YearOfJoining > 2015
// syntax: dataset.filter(filter_condition)
students.filter("yearOfJoining".gt(2015))

您可能已经注意到过滤条件不再是匿名函数。我们现在明确告诉spark过滤使用 yearOfJoining > 2015 条件

  1. Join Student with Department
// syntax: ds1.join(ds2, join_condition)
students.join(department, students.col("deptId").equalTo(department.col("id")))

请注意,join condition (students.col("deptId").equalTo(department.col("id"))) 也不是匿名函数!! 我们明确告诉spar join在students[depId] == department[id]的条件

关于Dataset的更多注意事项:

  • 因此,我们使用简单的单行代码注册了dataschema: .as[Student].
  • DataSet操作非常明确。 在那里,用户正在执行什么操作,哪一列对Spark来说是显而易见的.
  • 因此,Spark获得了它想要的透明度。
  • 谁将DataSet转换为Tungsten Binary格式,反之亦然?
  • Ans: Spark为DataSet提供编码器API,负责将DataSet转换为spark内部Tungsten二进制格式,反之亦然。.
  • A less obvious advantage with Encoders: 编码器的一个不太明显的优点:编码器急切地检查您的数据是否与预期的架构匹配,在您尝试错误处理TB数据之前提供有用的错误消息.

RDD’s of JavaObjects (vs) Dataset’s

image

Benefits of Dataset’s

  • 紧凑(开销较小)
  • 显着减少我们的内存占用.
  • Spark知道它现在正在处理什么数据。
  • Spark还知道用户想要在Dataset上执行的操作
  • 以上两个列出的好处都为一个不那么明显的优势铺平了道路:
  • 简单的一个可能的就地转换,无需反序列化。(让我们看看下面将如何发生这种情况)

dataset就地转换是什么意思?

使用RDD,对数据 (like filter, map, groupBy, count etc), 应用转换操作,有3个步骤:

  1. 它首先反序列化为java对象
  2. 我们在Java对象上应用转换
  3. 最后将javaobject序列化为字节.

使用数据集,就地转换本质上意味着,我们不需要反序列化数据集以对其应用转换。让我们看看接下来会发生什么…

如何在DataSet / Dataframe中进行就地转换?

让我们看看现在同样的旧过滤器fn是如何表现的。考虑我们要以year>2015条件在dataframe 过滤输入.请注意,由dataframe代码指定的条件 df.where(df(“year” > 2015))不是一个匿名函数.Spark确切地知道它为此任务需要哪个列,并且它需要大于比较。
image

为此查询生成的低级字节代码看起来像这样,如上图所示


// This filter function is returning boolean on 'year > 2015' condition
bool filter(Object baseObject) {

// 1. compute offset from where to fetch the column 'year'
int offset = baseoffset + <..>

// 2. Fetch the value of 'year' column of the given baseObject 
// directly without deserialing baseObject
int value = Platform.getInt(baseObject, offset)

// 3. return the boolean
return value > 2015
}

需要注意的有趣的事情如下:

  • filter fn 对spark不是匿名的
  • 对象未反序列化以查找列’year’的值
  • 'year’的值直接从偏移量中获取
  • 就地转换只能用于这样的简单操作

这很棒!!让我们看看就地转换如何帮助提高速度?

至少用于简单的用例…

  • 我们可以直接操作序列化数据=>较小的反序列化
  • 较小的反序列化=>较少的对象创建
  • 减少对象创建=>较小的GC
  • 因此速度!!

最后:什么是DataSet / DataFrame?

  • 使用模式映射的强类型对象
  • 在tungsten中以行格式存储
  • Dataset API的核心是一个名为Encoder的新概
  • Encoder负责转换到/从 spark内部Tungsten二进制格式
  • Encoder在执行任何操作时使用data schema.
  • 虽然DataSet是一个强类型对象,但DataFrame是DataSet [GenericRowObject]
  • 现在,我们已经深入了解了数据集如何以及为何出现并且看起来如此充满希望,为什么火花会转移到基于柱的Parquet?让我们找出原因…

Part2数据存储格式的演变 - 从基于行的dataset到基于列的Parquet:

根据我们到目前为止的讨论,Spark 1.x使用了基于行的存储格式来实现DataSet。Spark 2.x增加了对基于列的格式的支持。柱状格式基本上是基于行的存储的转置。所有的整数都打包在一起,所有的字符串都在一起。下图说明了基于行的存储格式与基于列的存储格式的内存布局。
Image

为什么 columnar?

第二代Tungsten Engine试图使用一些标准优化技术,如循环展开,SIMD,预取等现代编译器和CPU应用于运行时加速。作为其中的一部分,Spark提出了两种新的优化技术,称为WholeStageCodeGeneration和Vectorization(这些技术的细节可以在这里找到) here). 为些spark做了两件事情:

  1. 调整其执行引擎以执行向量操作并在算法级别获得数据级并行性
  2. Spark从基于行的内核数据迁移到柱状内存数据,使其能够进行进一步的SIMD优化,例如数据分段,以获得更好的缓存和下面列出的其他优势。

柱状存储什么时候提出?

绩效基准:

  1. 常规数据访问与复杂的偏移计算: 数据访问在列式格式中更为规则。例如,如果我们有一个整数,我们总是相隔4个字节访问它们。这对cpu非常好。对于基于行的格式,有复杂的偏移计算来知道它在哪里。
  2. 密度存储: 由于数据的性质是同质的,我们可以根据数据类型应用更好的压缩技术。
  3. 兼容性和零序列化: 列式格式更兼容,因为许多高性能系统已经使用了Columnar format 像numpy,tensorflow等。在它上面加上spark,它们在内存中意味着零序列化和零拷贝。例如,我们的大多spark计划都是在火花中进行评估,最后我们要调用 tensor flow,当它完成时,我们想要回来。使用柱状内存格式的spark,与tensorflow兼容。因此,它将完成而无需进行序列化等。它只是一起工作。
  4. 与内存缓存的兼容性: 由于明显的原因,使用内存缓存的列式存储更加兼容。
  5. 更多扩展::通过在GPU上运行,在工业中实现了现代数据密集型机器学习应用程序吞吐量。对于同类数据运算,GPU比CPU更强大。具有均匀的柱状存储为将来卸载GPU和TPU的处理铺平了道路,以利用其先进的硬件。(请在下面的附录部分中找到GPU和TPU之间的性能比较)

性能基准测试:
-让我们测试 Spark 1.x Columnar data (Vs) Spark 2.x Vectorized Columnar data.

  • 为此,考虑了最受欢迎的hadoop堆栈柱状格式的Parquet。
  • 火花1.6中的parquest扫描性能以1100万/秒的速度运行
  • 在火花2.x中矢量化以大约9千万行/秒的速度运行大约快9倍。
  • 矢量Parquet基本上是直接扫描数据并以矢量化方式实现它。
  • 这是有希望的,并清楚地表明这是正确的事情!
    Image

结论 - Spark中的数据布局之旅

我们已经看到:

  • java对象的缺点
  • 用schema使用新的内部tungsten行格式的强类型的DataSet
  • 数据集优势:较少的序列化/反序列化,较少的GC,较少的对象创建以及可能在简单情况下就地转换。
  • Spark引入了对柱状数据的支持,使用了一种名为Vectorization in spark 2.x的新技术,以更好地利用现代CPU和硬件的改进,如循环展开,SIMD等。

原博客主页

References

Appendix

A quick additional note on GPU & TPU’s:

  • GPU:

    • Modern day GPUs thrive on SIMD architecture.
    • GPU is tailored for highly parallel operation while CPU executes and is designed for serial execution.
    • GPU have significantly faster and more advanced memory interfaces as there is need to shift around a lot more data than CPUs.
    • GPU are a result of evolution into a highly parallel, multi-threaded cores with independent piplines & stages supported by high memory band width. Modern day cpus are 4-8 cores but GPUs are 1000s of cores.
      image
  • TPU:

    • Tensor Processing unit (TPU) is an application specific hardware developed by Google for Machine Learning.
    • Compared to GPU it is designed explicitly for higher volume of reduced precision computation with higher throughput of execution per watt.
    • Hardware has been specifically designed for Google’s Tensor Flow framework.

Following graph depicts the performance/watt comparision between CPU, GPU, TPU and TPU’ - latestTPU:
image

link

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值