我通过两种方法生成Parquet文件:Kinesis Firehose和Spark作业。它们都被写入S3上的相同分区结构中。可以使用相同的Athena表定义查询两组数据。两者都使用gzip压缩。
但是,我注意到,Spark生成的Parquet文件大约是Firehose文件的3倍。有什么理由是这样吗?使用Pyarrow加载它们时,我确实注意到一些架构和元数据差异:
>>> import pyarrow.parquet as pq
>>> spark = pq.ParquetFile('<spark object name>.gz.parquet')
>>> spark.metadata
<pyarrow._parquet.FileMetaData object at 0x101f2bf98>
created_by: parquet-mr version 1.8.3 (build aef7230e114214b7cc962a8f3fc5aeed6ce80828)
num_columns: 4
num_rows: 11
num_row_groups: 1
format_version: 1.0
serialized_size: 1558
>>> spark.schema
<pyarrow._parquet.ParquetSchema object at 0x101f2f438>
uri: BYTE_ARRAY UTF8
dfpts.list.element: BYTE_ARRAY UTF8
udids.list.element: BYTE_ARRAY UTF8
uuids.list.element: BYTE_ARRAY UTF8
>>> firehose = pq.ParquetFile('<firehose object name>.parquet')
>>> firehose.metadata
<pyarrow._parquet.FileMetaData object at 0x10fc63458>
created_by: parquet-mr version 1.8.1 (build 4aba4dae7bb0d4edbcf7923ae1339f28fd3f7fcf)
num_columns: 4
num_rows: 156
num_row_groups: 1
format_version: 1.0
serialized_size: 1017
>>> firehose.schema
<pyarrow._parquet.ParquetSchema object at 0x10fc5e7b8>
udids.bag.array_element: BYTE_ARRAY UTF8
dfpts.bag.array_element: BYTE_ARRAY UTF8
uuids.bag.array_element: BYTE_ARRAY UTF8
uri: BYTE_ARRAY UTF8
架构差异是否可能是罪魁祸首?还有吗?
这两个特定的文件不包含完全相同的数据,但是根据我的Athena查询,Firehose文件中所有行的所有列表的总基数约为Spark的2.5倍文件。
已编辑添加:
我写了以下内容,基本上将每个实木复合地板文件的内容转储到标准输出每行一行:
import sys
import pyarrow.parquet as pq
table = pq.read_table(sys.argv[1])
pydict = table.to_pydict()
for i in range(0, table.num_rows):
print(f"{pydict['uri'][i]}, {pydict['dfpts'][i]}, {pydict['udids'][i]}, {pydict['uuids'][i]}")
然后我将其针对每个镶木地板文件运行,并将输出通过管道传输到文件。这是原始两个文件的大小,将上述python代码指向每个文件的输出,以及该输出的压缩版本:
-rw-r--r-- 1 myuser staff 1306337 Jun 28 16:19 firehose.parquet
-rw-r--r-- 1 myuser staff 8328156 Jul 2 15:09 firehose.printed
-rw-r--r-- 1 myuser staff 5009543 Jul 2 15:09 firehose.printed.gz
-rw-r--r-- 1 myuser staff 1233761 Jun 28 16:23 spark.parquet
-rw-r--r-- 1 myuser staff 3213528 Jul 2 15:09 spark.printed
-rw-r--r-- 1 myuser staff 1951058 Jul 2 15:09 spark.printed.gz
请注意,两个实木复合地板文件的大小大致相同,但是firehose文件的“打印”内容大约是spark文件中“打印”内容的2.5倍。而且它们具有相同的可压缩性。
那么:如果不是原始数据,那么Spark Parquet文件中的所有空间将被占用吗?
已编辑添加:
下面是“ parquet-tools meta”的输出。每列的压缩率看起来相似,但是firehose文件的每个未压缩字节包含更多的值。对于“ dfpts”列:
消防水带:
SZ:667849/904992/1.36 VC:161475
火花:
SZ:735561/1135861/1.54 VC:62643
parquet-tools元输出:
file: file:/Users/jh01792/Downloads/firehose.parquet
creator: parquet-mr version 1.8.1 (build 4aba4dae7bb0d4edbcf7923ae1339f28fd3f7fcf)
file schema: hive_schema
--------------------------------------------------------------------------------
udids: OPTIONAL F:1
.bag: REPEATED F:1
..array_element: OPTIONAL BINARY L:STRING R:1 D:3
dfpts: OPTIONAL F:1
.bag: REPEATED F:1
..array_element: OPTIONAL BINARY L:STRING R:1 D:3
uuids: OPTIONAL F:1
.bag: REPEATED F:1
..array_element: OPTIONAL BINARY L:STRING R:1 D:3
uri: OPTIONAL BINARY L:STRING R:0 D:1
row group 1: RC:156 TS:1905578 OFFSET:4
--------------------------------------------------------------------------------
udids:
.bag:
..array_element: BINARY GZIP DO:0 FPO:4 SZ:421990/662241/1.57 VC:60185 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 58, min/max not defined]
dfpts:
.bag:
..array_element: BINARY GZIP DO:0 FPO:421994 SZ:667849/904992/1.36 VC:161475 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 53, min/max not defined]
uuids:
.bag:
..array_element: BINARY GZIP DO:0 FPO:1089843 SZ:210072/308759/1.47 VC:39255 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 32, min/max not defined]
uri: BINARY GZIP DO:0 FPO:1299915 SZ:5397/29586/5.48 VC:156 ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY ST:[num_nulls: 0, min/max not defined]
file: file:/Users/jh01792/Downloads/spark.parquet
creator: parquet-mr version 1.8.3 (build aef7230e114214b7cc962a8f3fc5aeed6ce80828)
extra: org.apache.spark.sql.parquet.row.metadata = {"type":"struct","fields":[{"name":"uri","type":"string","nullable":false,"metadata":{}},{"name":"dfpts","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"udids","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"uuids","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}}]}
file schema: spark_schema
--------------------------------------------------------------------------------
uri: REQUIRED BINARY L:STRING R:0 D:0
dfpts: OPTIONAL F:1
.list: REPEATED F:1
..element: OPTIONAL BINARY L:STRING R:1 D:3
udids: OPTIONAL F:1
.list: REPEATED F:1
..element: OPTIONAL BINARY L:STRING R:1 D:3
uuids: OPTIONAL F:1
.list: REPEATED F:1
..element: OPTIONAL BINARY L:STRING R:1 D:3
row group 1: RC:11 TS:1943008 OFFSET:4
--------------------------------------------------------------------------------
uri: BINARY GZIP DO:0 FPO:4 SZ:847/2530/2.99 VC:11 ENC:PLAIN,BIT_PACKED ST:[num_nulls: 0, min/max not defined]
dfpts:
.list:
..element: BINARY GZIP DO:0 FPO:851 SZ:735561/1135861/1.54 VC:62643 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 0, min/max not defined]
udids:
.list:
..element: BINARY GZIP DO:0 FPO:736412 SZ:335289/555989/1.66 VC:23323 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 0, min/max not defined]
uuids:
.list:
..element: BINARY GZIP DO:0 FPO:1071701 SZ:160494/248628/1.55 VC:13305 ENC:RLE,PLAIN_DICTIONARY ST:[num_nulls: 0, min/max not defined]
答案 0 :(得分:1)
您可能应该以不同的方式提出问题:
为什么Firehose数据的压缩比Spark数据更有效?
您在Parquet中对此有几种可能的解释:
不同的列值基数
除压缩方案外,Parquet尝试对您的值使用最有效的编码。特别是对于BYTE_ARRAY,它将默认尝试使用字典编码,即将每个不同的BYTE_ARRAY值映射到一个int,然后仅将int存储在列数据中(更多信息here)。如果字典太大,则会退回以仅存储BYTE_ARRAY值。
如果Firehose数据集的值差异远小于Spark数据集,则一个可能正在使用有效的字典编码,而另一个则没有。
排序的数据
排序后的数据通常比未排序的数据压缩要好得多,因此,如果您的Firehose列值是自然排序的(或者至少更经常重复),则拼花编码和gzip压缩将实现更好的压缩率
不同的行组大小
Parquet将行组中的值拆分为可调大小(Spark中的parquet.block.size
配置)。压缩和编码是在行组级别应用的,因此行组越大,压缩越好,但编码可能会更糟(例如,您可以从字典编码切换为纯byte_array值),并且在读取或写入时对内存的要求更高。 / p>
如何了解您的情况?
使用parquet-tools检查列的详细编码数据:
例如关于我的一个数据集:
$ parquet-tools meta part-00015-6a77dcbe-3edd-4199-bff0-efda0f512d61.c000.snappy.parquet
...
row group 1: RC:63076 TS:41391030 OFFSET:4
--------------------------------------------------------------------------------
options:
.list:
..element: BINARY SNAPPY DO:0 FPO:6042924 SZ:189370/341005/1,80 VC:269833 ENC:RLE,PLAIN_DICTIONARY ST:[no stats for this column]
...
row group 2: RC:28499 TS:14806649 OFFSET:11648146
--------------------------------------------------------------------------------
options:
.list:
..element: BINARY SNAPPY DO:0 FPO:13565454 SZ:78631/169832/2,16 VC:144697 ENC:RLE,PLAIN_DICTIONARY ST:[no stats for this column]
列数据上的ENC
属性为您提供用于该列的编码(在这种情况下为DICTIONARY)SZ
属性为您提供compressed size/uncompressed size/compression ratio
和VC
的数字编码的值。
您可以在我的示例中看到,仅由于数据分布,行组2中的压缩率比行组1中的压缩率稍好。
更新:
查看您提供的统计信息,您可以看到数据集中的dfpts
列的平均编码值大小为904992/161475 = 5.6字节,而spark版本的平均编码值大小为1135861/62643 = 18.13字节,即使两者都是相同的字典编码。这可能意味着RLE在您的firehose数据集上效率更高,因为您有很多重复值或很多不同值。
如果在保存为实木复合地板之前在spark中对dfpts
列进行排序,则很可能实现与Firehose数据相似的编码比率。
答案 1 :(得分:0)
我能想到的两件事可以归因于差异。
1.实木复合地板特性。
在Spark中,您可以使用以下代码片段查找与Parquet相关的所有属性。
如果使用Hadoop配置设置了属性,
import scala.collection.JavaConverters._
// spark = SparkSsssion
spark.sparkContext.hadoopConfiguration.asScala.filter {
x =>
x.getKey.contains("parquet")
}.foreach(println)
如果属性是使用Spark(spark-defaults.conf
,--conf
等)设置的,则
spark.sparkContext.getConf.getAll.filter {
case(key, value) => key.contains("parquet")
}.foreach(println)
如果我们也能够获得firehose(我不熟悉)配置,我们可以进行比较。否则,配置还应该给出可能出问题的一般信息。
2. Spark和FireHose使用的实木复合地板版本不同。
Parquet社区可能已经在版本之间更改了Parquet配置的默认设置。