对于save
即将到parquet
之前的给定DataFrame,以下是架构:请注意,centroid0
是 first 列,并且是{ {1}}:
但是使用以下方法保存文件时:
StringType
,其中 df.write.partitionBy(dfHolder.metadata.partitionCols: _*).format("parquet").mode("overwrite").save(fpath)
为partitionCols
:
然后有一个(对我来说)令人惊讶的结果:
centroid0
分区列已移至行的 end centroid0
我通过Integer
确认了输出路径:
println
这是从保存的 path=/git/block/target/scala-2.11/test-classes/data/output/blocking/out//level1/clusters
读取 back 时的架构:
为什么对输入模式进行了这两种修改-以及如何避免它们-仍将parquet
保留为分区列?
更新一个优选的答案应该是为什么/当将分区添加到列列表的 end (相对于开始)时。我们需要对确定性排序的理解。
此外-是否有任何方法可以使centroid0
对推断出的列类型“改变主意”?我不得不将分区从spark
,0
等更改为1
,c0
等,以便将推断映射到c1
。也许这是必需的..但是如果有一些火花设置可以改变行为,那么这将是一个很好的答案。
答案 0 :(得分:1)
write.partitionBy(...)
时,Spark将分区字段另存为文件夹
这对以后读取数据很有用,因为它可以优化(仅包括某些文件类型,包括镶木地板),仅从您使用的分区读取数据(即,如果您读取并过滤了centroid0 == 1则不会读取火花)其他分区
此操作的结果是,分区字段(在您的情况下为centroid0
)不会仅作为文件夹名称(centroid0=1
,centroid0=2
等)写入实木复合地板文件中< / p>
这些的副作用是1.在运行时推断分区的类型(因为架构未保存在Parquet中),在您的情况下,您碰巧只有整数值,因此推断为整数
另一个副作用是,分区字段是在模式的末尾/开头添加的,因为它从拼花文件中读取模式是一个块,然后又将分区字段添加为另一个(再次) ,它不再是存储在实木复合地板中的架构的一部分)
答案 1 :(得分:0)
原因实际上很简单。当按一列进行分区时,每个分区只能包含所述列的一个值。因此,实际上在文件的各处都写入相同的值是没有用的,这就是Spark不这样做的原因。读取文件时,Spark使用文件名中包含的信息来重建分区列,并将其放在模式的末尾。列的类型不会存储,而是在读取时进行推断,因此您需要使用整数类型。 注意:关于在末尾添加该列的原因没有特别的原因。可能只是开始。我想这只是实现的任意选择。
为避免丢失列的类型和顺序,可以像df.withColumn("X", 'YOUR_COLUMN).write.partitionBy("X").parquet("...")
这样复制分区列。
但是,您将浪费空间。另外,例如,spark使用分区来优化过滤器。读取数据后,请不要忘记使用X列作为过滤器,否则您的列或Spark将无法执行任何优化。