【问题标题】:Repartitioning parquet-mr generated parquets with pyarrow/parquet-cpp increases file size by x30?使用 pyarrow/parquet-cpp 重新分区 parquet-mr 生成的镶木地板会使文件大小增加 x30?
【发布时间】:2018-10-26 16:38:31
【问题描述】:

我正在使用 AWS Firehose 将传入记录转换为镶木地板。在一个示例中,我有 150k 条相同的记录进入 firehose,并且单个 30kb parquet 被写入 s3。由于 firehose 对数据的分区方式,我们在 parquet 中读取了一个辅助进程(由 s3 put 事件触发的 lambda)并根据事件本身的日期对其进行重新分区。在这个重新分区过程之后,30kb 的文件大小会跳到 900kb。

检查两个 parquet 文件-

  • 元不变
  • 数据没有变化
  • 它们都使用 SNAPPY 压缩
  • firehose parquet 由 parquet-mr 创建,pyarrow 生成的 parquet 由 parquet-cpp 创建
  • pyarrow 生成的 parquet 有额外的 pandas 标头

完整的重新分区过程-

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 文件的固定开销为 4kb+。当您重新分区到太多文件时,这可能是来源之一。

标签: pandas parquet amazon-kinesis-firehose pyarrow


【解决方案1】:

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 目前只用于布尔类型,但思路是一样的。)
  • 上述组合,特别是 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 似乎没有使用这些节省空间的编码。您可以通过使用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

【讨论】:

  • 这很有意义-谢谢!但是,在此示例中,它仅尝试重新分区。结果是数据没有拆分。 1 个文件 -> 重新分区 -> 1 个文件。所以在重新分区过程中的某个地方它没有有效地压缩?
  • 嗯,确实,对于相同的记录,分区也将是相同的,我应该意识到这一点。 :) 在那种情况下,我唯一的猜测是 pyarrow 出于某种原因没有或不能使用任何这些节省空间的编码。您可以为这两个文件发布parquet-tools meta 的输出吗?我希望看到这两个文件的不同编码。
  • 您对压缩方法的看法是正确的。 Pandas 目前不支持,并且可能永远不会支持这种压缩。因此,如果您有 1000 个 NULL 值,pandas 实际上会填充 1000 个 NULL 值,而不是记下“接下来的 1000 个为空”
  • 我写的内容主要适用于常规值,因为 NULL 值有些特殊处理。然而,缺乏对某些编码的支持肯定会增加文件大小。
  • 希望这听起来不像是在与您相矛盾。 NULL 值在 parquet 文件中专门处理。但是 pandas 库在 parquet 的实现中没有利用这一点 - 导致传入和传出 pandas 后文件大小要大得多
猜你喜欢
  • 2021-03-26
  • 2019-10-27
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 2021-12-06
  • 2018-05-06
  • 2021-10-28
  • 2020-02-25
相关资源
最近更新 更多