使用 pyarrow/parquet-cpp 重新分区 parquet-mr 生成的镶木地板会使文件大小增加 x30?
Repartitioning parquet-mr generated parquets with pyarrow/parquet-cpp increases file size by x30?
我正在使用 AWS Firehose 将传入记录转换为镶木地板。在一个示例中,我有 150k 条相同的记录进入 firehose,一个 30kb 的镶木地板被写入 s3。由于 firehose 分区数据的方式,我们有一个辅助进程(由 s3 put 事件触发的 lambda)读取 parquet 并根据事件本身内的日期对其进行重新分区。在这个重新分区过程之后,30kb 的文件大小跳到 900kb。
正在检查两个镶木地板文件-
- 元没有改变
- 数据没有变化
- 他们都使用 SNAPPY 压缩
- firehose parquet 由 parquet-mr 创建,pyarrow 生成的 parquet 由 parquet-cpp
创建
- pyarrow 生成的 parquet 有额外的 pandas headers
完整的重新分区过程-
import pyarrow.parquet as pq
tmp_file = f'{TMP_DIR}/{rand_string()}'
s3_client.download_file(firehose_bucket, key, tmp_file)
pq_table = pq.read_table(tmp_file)
pq.write_to_dataset(
pq_table,
local_partitioned_dir,
partition_cols=['year', 'month', 'day', 'hour'],
use_deprecated_int96_timestamps=True
)
我想尺寸会有一些变化,但我很惊讶地发现有这么大的差异。鉴于我所描述的过程,什么会导致源镶木地板从 30kb 变为 900kb?
Parquet 使用不同的列编码来非常有效地存储低熵数据。例如:
- 它可以使用增量编码来只存储值之间的差异。例如
9192631770, 9192631773, 9192631795, 9192631797
将有效地存储为 9192631770, +3, +12, +2
.
- 它可以使用字典编码来简短地引用公共值。例如,
Los Angeles, Los Angeles, Los Angeles, San Francisco, San Francisco
将存储为 0 = Los Angeles, 1 = San Francisco
和引用 0, 0, 0, 1, 1
的字典
- 可以使用运行长度编码只存储重复值的个数。例如,
Los Angeles, Los Angeles, Los Angeles
将有效地存储为 Los Angeles×3
。 (实际上据我所知目前纯RLE只用于boolean类型,但思路是一样的。)
- 以上的组合,特别是 RLE 和字典编码。例如,
Los Angeles, Los Angeles, Los Angeles, San Francisco, San Francisco
将存储为 0 = Los Angeles, 1 = San Francisco
和引用 0×3, 1×2
的字典
对于上面示例中的 3 到 5 个值,节省的并不多,但是您拥有的值越多,收益就越大。由于你有 150k 条相同的记录,收益将是巨大的,因为使用 RLE 字典编码,每个列值将只需要存储一次,然后标记为重复 150k 次。
但是,pyarrow 似乎没有使用这些 space-saving 编码。您可以使用 parquet-tools meta
查看这两个文件的元数据来确认这一点。这是一个示例输出:
file schema: hive_schema
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
id: OPTIONAL INT32 R:0 D:1
name: OPTIONAL BINARY O:UTF8 R:0 D:1
row group 1: RC:61 TS:214 OFFSET:4
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
id: INT32 UNCOMPRESSED DO:0 FPO:4 SZ:107/107/1.00 VC:61 ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY ST:[min: 1, max: 5, num_nulls: 0]
name: BINARY UNCOMPRESSED DO:0 FPO:111 SZ:107/107/1.00 VC:61 ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY ST:[min: Los Angeles, max: San Francisco, num_nulls: 0]
编码显示为ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY
。
我正在使用 AWS Firehose 将传入记录转换为镶木地板。在一个示例中,我有 150k 条相同的记录进入 firehose,一个 30kb 的镶木地板被写入 s3。由于 firehose 分区数据的方式,我们有一个辅助进程(由 s3 put 事件触发的 lambda)读取 parquet 并根据事件本身内的日期对其进行重新分区。在这个重新分区过程之后,30kb 的文件大小跳到 900kb。
正在检查两个镶木地板文件-
- 元没有改变
- 数据没有变化
- 他们都使用 SNAPPY 压缩
- firehose parquet 由 parquet-mr 创建,pyarrow 生成的 parquet 由 parquet-cpp 创建
- pyarrow 生成的 parquet 有额外的 pandas headers
完整的重新分区过程-
import pyarrow.parquet as pq
tmp_file = f'{TMP_DIR}/{rand_string()}'
s3_client.download_file(firehose_bucket, key, tmp_file)
pq_table = pq.read_table(tmp_file)
pq.write_to_dataset(
pq_table,
local_partitioned_dir,
partition_cols=['year', 'month', 'day', 'hour'],
use_deprecated_int96_timestamps=True
)
我想尺寸会有一些变化,但我很惊讶地发现有这么大的差异。鉴于我所描述的过程,什么会导致源镶木地板从 30kb 变为 900kb?
Parquet 使用不同的列编码来非常有效地存储低熵数据。例如:
- 它可以使用增量编码来只存储值之间的差异。例如
9192631770, 9192631773, 9192631795, 9192631797
将有效地存储为9192631770, +3, +12, +2
. - 它可以使用字典编码来简短地引用公共值。例如,
Los Angeles, Los Angeles, Los Angeles, San Francisco, San Francisco
将存储为0 = Los Angeles, 1 = San Francisco
和引用0, 0, 0, 1, 1
的字典
- 可以使用运行长度编码只存储重复值的个数。例如,
Los Angeles, Los Angeles, Los Angeles
将有效地存储为Los Angeles×3
。 (实际上据我所知目前纯RLE只用于boolean类型,但思路是一样的。) - 以上的组合,特别是 RLE 和字典编码。例如,
Los Angeles, Los Angeles, Los Angeles, San Francisco, San Francisco
将存储为0 = Los Angeles, 1 = San Francisco
和引用0×3, 1×2
的字典
对于上面示例中的 3 到 5 个值,节省的并不多,但是您拥有的值越多,收益就越大。由于你有 150k 条相同的记录,收益将是巨大的,因为使用 RLE 字典编码,每个列值将只需要存储一次,然后标记为重复 150k 次。
但是,pyarrow 似乎没有使用这些 space-saving 编码。您可以使用 parquet-tools meta
查看这两个文件的元数据来确认这一点。这是一个示例输出:
file schema: hive_schema
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
id: OPTIONAL INT32 R:0 D:1
name: OPTIONAL BINARY O:UTF8 R:0 D:1
row group 1: RC:61 TS:214 OFFSET:4
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
id: INT32 UNCOMPRESSED DO:0 FPO:4 SZ:107/107/1.00 VC:61 ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY ST:[min: 1, max: 5, num_nulls: 0]
name: BINARY UNCOMPRESSED DO:0 FPO:111 SZ:107/107/1.00 VC:61 ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY ST:[min: Los Angeles, max: San Francisco, num_nulls: 0]
编码显示为ENC:BIT_PACKED,RLE,PLAIN_DICTIONARY
。