tee*_*jay 6 parquet apache-arrow pyarrow
我开始使用镶木地板文件格式。Apache 官方网站建议使用 512MB 到 1GB 的大行组(此处)。一些在线资源(例如这个)建议默认行组大小为 128MB。
我有大量 parquet 文件,稍后我将使用 AWS Glue 上的 PySpark 在下游处理这些文件。这些文件具有非常小的行组。我无法控制我开始使用的文件,但想要组合行组,以便在下游处理之前获得“更高效”的文件(为什么?这些文件将上传到 S3 并使用 Spark 进行处理;我的理解是Spark 一次会读取一个行组,因此更多较小的行组会导致 IO 操作增加,效率低下;如果此假设无效,请赐教)。
对于这个问题,我们只考虑其中一个文件。它经过压缩(经过snappy压缩),在磁盘上的大小为 85MB。当我使用该pqrs工具检查其架构时,它报告该文件在 1,115 个行组中有 55,733 条记录,每个行组似乎约为 500 kB - 具体来说,如下所示:
row group 7:
--------------------------------------------------------------------------------
total byte size: 424752
num of rows: 50
Run Code Online (Sandbox Code Playgroud)
如果我简单地采用(1115 个行组 * 500 kB/行组),则大约为 500MB;而磁盘上的文件为 85MB。诚然,有些行组小于 500kB,但我观察了大约 100 个行组(一半在顶部,一半在底部),它们都在大致范围内。
子问题 1: 差异是多少(计算值 500MB 与实际值 85MB),因为报告的行组大小实际上pqrs代表未压缩的大小,也许行组的内存大小是多少(大概会大于磁盘上的压缩序列化大小)?换句话说,我不能做一个简单的 1115 * 500 但必须应用某种压缩因子?
子问题2: 当我看到建议的批量大小是128MB时,这到底是什么意思?未压缩的内存大小?磁盘上的序列化压缩大小?还有别的事吗?它与 所报道的内容有什么关系pqrs?
我用于压缩这些行组的(简化的)代码是:
row group 7:
--------------------------------------------------------------------------------
total byte size: 424752
num of rows: 50
Run Code Online (Sandbox Code Playgroud)
主要问题:应该batchsize是什么?
iter_batches视为batch_size记录数而不是字节大小。我可以根据总记录和所需的批次数来计算它,但我不清楚我应该在这里计算什么。
我试过这个:
当我以 60k 的批量大小运行代码时:
row group 0:
--------------------------------------------------------------------------------
total byte size: 262055359
num of rows: 32768
Run Code Online (Sandbox Code Playgroud)
我认为我的一些假设 - 或对 parquet 文件格式、pqrs工具或pyarrow库的理解 - 是错误的。有人可以帮我揭开神秘面纱吗?
Pac*_*ace 10
TL;DR - 1 Mi 行
你的理解大致是正确的。不同的工具有不同的建议,一些工具(例如 pyarrow)将使用行数来确定行组大小,而其他工具(例如 parquet-mr,spark 使用的 java parquet 实现)将使用字节数。
是差异(计算的 500MB 与实际的 85MB),因为 pqrs 报告的行组大小实际上代表未压缩的大小
是的。工具对此往往不是很清楚。我发现在处理 parquet 元数据字段时, parquet thrift 定义是一个很好的基本事实来源。
struct RowGroup {
/** Metadata for each column chunk in this row group.
* This list must have the same order as the SchemaElement list in FileMetaData.
**/
1: required list<ColumnChunk> columns
/** Total byte size of all the uncompressed column data in this row group **/
2: required i64 total_byte_size
Run Code Online (Sandbox Code Playgroud)
当我看到建议的批量大小是 128MB 时,这到底是什么意思?未压缩的内存大小?磁盘上的序列化压缩大小?还有别的事吗?它与 pqrs 报告的内容有何关系?
主要问题:批量大小应该是多少?
答案通常归结为确保您发出的 I/O 请求适合您的存储系统。但是,如果您的行组非常小(例如 100、1k、10k 行),那么您的存储是什么可能并不重要(既因为行组引入了额外的计算,又因为行组影响元数据/数据比率)。这些非常小的尺寸几乎总是对性能不利。
如果您使用 HDFS,我相信规则可能会略有不同。我对 HDFS 没有太多经验。在所有其他情况下,您通常希望行组足够大,以便您的 I/O 请求足够大以满足您的存储系统。
例如,当从 HDD 读取时,如果执行一堆随机 64 字节读取,则会比一堆连续 64 字节读取获得更差的性能。但是,如果您执行一堆随机 4MiB 读取,那么您应该获得与一堆连续 4MiB 读取大致相同的性能。当然,这取决于硬盘驱动器,但我发现 4MiB 对于 HDD 来说是一个不错的数字。另一方面,如果您从 S3 读取数据,那么他们的指南建议为 8-16MiB。
将其转换为行组大小有点棘手,并且取决于您的查询习惯。如果您通常从文件中读取所有列,那么您将希望行组为 8-16MiB。另一方面,如果您通常只从文件中读取“某些”列,那么您希望每列为 8-16MiB。
现在事情变得棘手,因为我们必须考虑压缩和编码。例如,布尔列几乎不会是 8MiB。由于压缩,您至少需要 64Mi 行,并且可能需要更多行。float32 列更容易理解。您应该获得 2Mi 行的 8MiB 读取,并且在许多情况下,您不会从中获得太多压缩。
以上都是理论。在实践中,我在本地磁盘和 S3 上进行了大量的基准测试,我发现 1Mi 行通常对于行组来说是一个不错的大小。在某些情况下,较大的行组可能是个好主意,而使用较小的行组仍然可以获得相当好的性能。您最终需要针对您自己的个人用例进行基准测试。然而,1Mi 是一个很好记的整数。如果您需要以未压缩字节数表示行组大小,那么这取决于您有多少列。同样,根据经验,我们可以假设列是 4 个字节,因此您可以使用以下计算:
# of bytes = 1Mi * # of columns * 4
Run Code Online (Sandbox Code Playgroud)
换句话说,如果您有 10 列,那么行组的大小至少为 40MiB。
鉴于上述情况,使行组变得庞大似乎很简单。这将确保您拥有理想的 I/O 请求。在完美的世界中,如果所有 parquet 读取器都创建为平等的,那么我会说这是正确的(每个文件 1 行组是理想的)。
然而,许多镶木地板阅读器将使用行组作为:
由于这些原因,您通常希望避免行组大小过大。
这通常是保持行组较小的另一个原因。行组统计是最容易使用的下推过滤工具,一些读者完全依赖于此。这意味着下推只能根据过滤器过滤掉整个行组。因此,较小的行组意味着您更有可能完全消除 I/O。
幸运的是,parquet 读取工具已经慢慢转向使用页面级统计数据(或页面级布隆过滤器)来执行此过滤。页面非常小(~1MiB),并且可以提供非常好的过滤分辨率(尽管在极少数情况下,分辨率太精细,因为它需要更多的元数据处理)。如果您的镶木地板阅读器能够利用页面级统计信息进行下推,那么行组大小应该不会影响下推。
任何类型的单行跳过或加载都与行组大小无关。Parquet 读取器应该能够以页面级分辨率应用跳过。
免责声明:我从事 arrow-c++/pyarrow 工作。由于我上面描述的一些原因,pyarrow 数据集读取器的性能非常依赖于行组大小(我正在慢慢尝试解决此问题)。
| 归档时间: |
|
| 查看次数: |
4009 次 |
| 最近记录: |