读取和写入 Apache Parquet 格式#

Apache Parquet 项目提供了一种标准化的开源列式存储格式,用于数据分析系统。它最初是为了在 Apache Hadoop 中与 Apache DrillApache HiveApache ImpalaApache Spark 等系统一起使用而创建的,这些系统将其作为高性能数据 I/O 的共享标准。

Apache Arrow 是用于读取或写入 Parquet 文件的数据的理想内存传输层。我们一直在同步开发 Apache Parquet 的 C++ 实现,其中包括一个原生的、多线程的 C++ 适配器,用于在内存 Arrow 数据之间进行转换。PyArrow 包含了此代码的 Python 绑定,因此也可以使用 pandas 读取和写入 Parquet 文件。

获取支持 Parquet 的 pyarrow#

如果您通过 pip 或 conda 安装了 pyarrow,它应该会内置 Parquet 支持。

In [1]: import pyarrow.parquet as pq

如果您从源代码构建 pyarrow,则在编译 C++ 库时必须使用 -DARROW_PARQUET=ON,并在构建 pyarrow 时启用 Parquet 扩展。如果要使用 Parquet 加密,则在编译 C++ 库时也必须使用 -DPARQUET_REQUIRE_ENCRYPTION=ON。有关更多详细信息,请参阅 Python 开发 页面。

读取和写入单个文件#

函数 read_table()write_table() 分别用于读取和写入 pyarrow.Table 对象。

让我们看一个简单的表格:

In [2]: import numpy as np

In [3]: import pandas as pd

In [4]: import pyarrow as pa

In [5]: df = pd.DataFrame({'one': [-1, np.nan, 2.5],
   ...:                    'two': ['foo', 'bar', 'baz'],
   ...:                    'three': [True, False, True]},
   ...:                    index=list('abc'))
   ...: 

In [6]: table = pa.Table.from_pandas(df)

我们使用 write_table 将其写入 Parquet 格式

In [7]: import pyarrow.parquet as pq

In [8]: pq.write_table(table, 'example.parquet')

这将创建一个单个 Parquet 文件。实际上,Parquet 数据集可能由许多目录中的许多文件组成。我们可以使用 read_table 读取单个文件

In [9]: table2 = pq.read_table('example.parquet')

In [10]: table2.to_pandas()
Out[10]: 
   one  two  three
a -1.0  foo   True
b  NaN  bar  False
c  2.5  baz   True

您可以传递要读取的列的子集,这可能比读取整个文件快得多(由于列式布局)

In [11]: pq.read_table('example.parquet', columns=['one', 'three'])
Out[11]: 
pyarrow.Table
one: double
three: bool
----
one: [[-1,null,2.5]]
three: [[true,false,true]]

当从使用 Pandas DataFrame 作为源的文件中读取列子集时,我们使用 read_pandas 来维护任何额外的索引列数据

In [12]: pq.read_pandas('example.parquet', columns=['two']).to_pandas()
Out[12]: 
   two
a  foo
b  bar
c  baz

我们不需要使用字符串来指定文件的来源。它可以是以下任何一种:

  • 作为字符串的文件路径

  • PyArrow 中的 NativeFile

  • 一个 Python 文件对象

一般来说,Python 文件对象的读取性能最差,而字符串文件路径或 NativeFile 实例(特别是内存映射)的性能最佳。

读取 Parquet 和内存映射#

由于 Parquet 数据需要从 Parquet 格式和压缩中解码,因此无法直接从磁盘映射。因此,memory_map 选项在某些系统上可能会表现更好,但对常驻内存消耗没有太大帮助。

>>> pq_array = pa.parquet.read_table("area1.parquet", memory_map=True)
>>> print("RSS: {}MB".format(pa.total_allocated_bytes() >> 20))
RSS: 4299MB

>>> pq_array = pa.parquet.read_table("area1.parquet", memory_map=False)
>>> print("RSS: {}MB".format(pa.total_allocated_bytes() >> 20))
RSS: 4299MB

如果您需要处理大于内存的 Parquet 数据,那么 表格数据集 和分区可能是您正在寻找的。

Parquet 文件写入选项#

write_table() 有许多选项可以控制写入 Parquet 文件时的各种设置。

  • version,要使用的 Parquet 格式版本。'1.0' 确保与旧读取器兼容,而 '2.4' 和更大的值可以启用更多的 Parquet 类型和编码。

  • data_page_size,用于控制列块内编码数据页的近似大小。当前默认值为 1MB。

  • flavor,用于设置特定于 Parquet 消费者的兼容性选项,例如 Apache Spark 的 'spark'

有关更多详细信息,请参阅 write_table() 文档字符串。

下面描述了一些额外的数据类型处理特定选项。

省略 DataFrame 索引#

当使用 pa.Table.from_pandas 转换为 Arrow 表时,默认情况下会添加一个或多个特殊列以跟踪索引(行标签)。存储索引会占用额外空间,因此如果您的索引不重要,您可以选择通过传递 preserve_index=False 来省略它

In [13]: df = pd.DataFrame({'one': [-1, np.nan, 2.5],
   ....:                    'two': ['foo', 'bar', 'baz'],
   ....:                    'three': [True, False, True]},
   ....:                    index=list('abc'))
   ....: 

In [14]: df
Out[14]: 
   one  two  three
a -1.0  foo   True
b  NaN  bar  False
c  2.5  baz   True

In [15]: table = pa.Table.from_pandas(df, preserve_index=False)

然后我们有

In [16]: pq.write_table(table, 'example_noindex.parquet')

In [17]: t = pq.read_table('example_noindex.parquet')

In [18]: t.to_pandas()
Out[18]: 
   one  two  three
0 -1.0  foo   True
1  NaN  bar  False
2  2.5  baz   True

在这里您会看到索引没有在往返中保留。

更精细的读写#

read_table 使用 ParquetFile 类,该类具有其他功能

In [19]: parquet_file = pq.ParquetFile('example.parquet')

In [20]: parquet_file.metadata
Out[20]: 
<pyarrow._parquet.FileMetaData object at 0x7fdfff1ce3e0>
  created_by: parquet-cpp-arrow version 22.0.0
  num_columns: 4
  num_rows: 3
  num_row_groups: 1
  format_version: 2.6
  serialized_size: 2652

In [21]: parquet_file.schema
Out[21]: 
<pyarrow._parquet.ParquetSchema object at 0x7fe0be78b680>
required group field_id=-1 schema {
  optional double field_id=-1 one;
  optional binary field_id=-1 two (String);
  optional boolean field_id=-1 three;
  optional binary field_id=-1 __index_level_0__ (String);
}

正如您在 Apache Parquet 格式中可以了解到的,Parquet 文件由多个行组组成。read_table 将读取所有行组并将它们连接成一个表格。您可以使用 read_row_group 读取单个行组

In [22]: parquet_file.num_row_groups
Out[22]: 1

In [23]: parquet_file.read_row_group(0)
Out[23]: 
pyarrow.Table
one: double
two: string
three: bool
__index_level_0__: string
----
one: [[-1,null,2.5]]
two: [["foo","bar","baz"]]
three: [[true,false,true]]
__index_level_0__: [["a","b","c"]]

我们可以类似地使用 ParquetWriter 写入具有多个行组的 Parquet 文件

In [24]: with pq.ParquetWriter('example2.parquet', table.schema) as writer:
   ....:    for i in range(3):
   ....:       writer.write_table(table)
   ....: 

In [25]: pf2 = pq.ParquetFile('example2.parquet')

In [26]: pf2.num_row_groups
Out[26]: 3

检查 Parquet 文件元数据#

Parquet 文件的 FileMetaData 可以通过 ParquetFile 访问,如上所示

In [27]: parquet_file = pq.ParquetFile('example.parquet')

In [28]: metadata = parquet_file.metadata

或者也可以直接使用 read_metadata() 读取

In [29]: metadata = pq.read_metadata('example.parquet')

In [30]: metadata
Out[30]: 
<pyarrow._parquet.FileMetaData object at 0x7fdffc943dd0>
  created_by: parquet-cpp-arrow version 22.0.0
  num_columns: 4
  num_rows: 3
  num_row_groups: 1
  format_version: 2.6
  serialized_size: 2652

返回的 FileMetaData 对象允许检查 Parquet 文件元数据,例如行组和列块元数据以及统计信息

In [31]: metadata.row_group(0)
Out[31]: 
<pyarrow._parquet.RowGroupMetaData object at 0x7fe0be7c50d0>
  num_columns: 4
  num_rows: 3
  total_byte_size: 290
  sorting_columns: ()

In [32]: metadata.row_group(0).column(0)
Out[32]: 
<pyarrow._parquet.ColumnChunkMetaData object at 0x7fe0be7c4a90>
  file_offset: 0
  file_path: 
  physical_type: DOUBLE
  num_values: 3
  path_in_schema: one
  is_stats_set: True
  statistics:
    <pyarrow._parquet.Statistics object at 0x7fe0be7c5cb0>
      has_min_max: True
      min: -1.0
      max: 2.5
      null_count: 1
      distinct_count: None
      num_values: 2
      physical_type: DOUBLE
      logical_type: None
      converted_type (legacy): NONE
  geo_statistics:
    None
  compression: SNAPPY
  encodings: ('PLAIN', 'RLE', 'RLE_DICTIONARY')
  has_dictionary_page: True
  dictionary_page_offset: 4
  data_page_offset: 36
  total_compressed_size: 106
  total_uncompressed_size: 102

数据类型处理#

将类型读取为 DictionaryArray#

read_tableParquetDataset 中的 read_dictionary 选项将使列被读取为 DictionaryArray,当转换为 pandas 时,它将成为 pandas.Categorical。此选项仅对字符串和二进制列类型有效,并且对于具有许多重复字符串值的列,它可以显着降低内存使用并提高性能。

pq.read_table(table, where, read_dictionary=['binary_c0', 'stringb_c2'])

存储时间戳#

某些 Parquet 读取器可能仅支持以毫秒 ('ms') 或微秒 ('us') 分辨率存储的时间戳。由于 pandas 使用纳秒来表示时间戳,这有时会带来不便。默认情况下(当写入版本 1.0 的 Parquet 文件时),纳秒将被转换为微秒 ('us')。

此外,我们提供了 coerce_timestamps 选项,允许您选择所需的分辨率

pq.write_table(table, where, coerce_timestamps='ms')

如果转换为较低分辨率的值可能导致数据丢失,默认情况下会引发异常。可以通过传递 allow_truncated_timestamps=True 来抑制此异常。

pq.write_table(table, where, coerce_timestamps='ms',
               allow_truncated_timestamps=True)

当使用较新的 Parquet 格式版本 2.6 时,可以存储纳秒级时间戳而无需进行类型转换

pq.write_table(table, where, version='2.6')

然而,许多 Parquet 读取器尚不支持此新格式版本,因此默认情况下会写入版本 1.0 文件。当需要跨不同处理框架的兼容性时,建议使用默认的 1.0 版本。

较旧的 Parquet 实现使用基于 INT96 的时间戳存储,但现在已弃用。这包括 Apache Impala 和 Apache Spark 的一些较旧版本。要以这种格式写入时间戳,请在 write_table 中将 use_deprecated_int96_timestamps 选项设置为 True

pq.write_table(table, where, use_deprecated_int96_timestamps=True)

压缩、编码和文件兼容性#

最常用的 Parquet 实现使用字典编码写入文件;如果字典变得太大,它们就会“回退”到纯编码。是否使用字典编码可以通过 use_dictionary 选项进行切换。

pq.write_table(table, where, use_dictionary=False)

行组中列内的数据页可以在编码通过后(字典、RLE 编码)进行压缩。在 PyArrow 中,我们默认使用 Snappy 压缩,但也支持 Brotli、Gzip、ZSTD、LZ4 和未压缩。

pq.write_table(table, where, compression='snappy')
pq.write_table(table, where, compression='gzip')
pq.write_table(table, where, compression='brotli')
pq.write_table(table, where, compression='zstd')
pq.write_table(table, where, compression='lz4')
pq.write_table(table, where, compression='none')

Snappy 通常会带来更好的性能,而 Gzip 可能会生成更小的文件。

这些设置也可以按列进行设置。

pq.write_table(table, where, compression={'foo': 'snappy', 'bar': 'gzip'},
               use_dictionary=['foo', 'bar'])

分区数据集(多文件)#

多个 Parquet 文件构成一个 Parquet *数据集*。这些文件可能以多种方式呈现:

  • Parquet 绝对文件路径列表

  • 包含定义分区数据集的嵌套目录的目录名称

按年和月分区的数据集在磁盘上可能看起来像这样:

dataset_name/
  year=2007/
    month=01/
       0.parq
       1.parq
       ...
    month=02/
       0.parq
       1.parq
       ...
    month=03/
    ...
  year=2008/
    month=01/
    ...
  ...

写入分区数据集#

您可以为任何 pyarrow 文件系统(即文件存储,例如本地、HDFS、S3)写入分区数据集。未添加文件系统时的默认行为是使用本地文件系统。

# Local dataset write
pq.write_to_dataset(table, root_path='dataset_name',
                    partition_cols=['one', 'two'])

在这种情况下,根路径指定数据将保存到的父目录。分区列是用于分区数据集的列名。列按照给定的顺序进行分区。分区拆分由分区列中的唯一值确定。

要使用另一个文件系统,您只需添加文件系统参数,各个表写入都使用 with 语句进行包装,因此 pq.write_to_dataset 函数无需这样做。

# Remote file-system example
from pyarrow.fs import HadoopFileSystem
fs = HadoopFileSystem(host, port, user=user, kerb_ticket=ticket_cache_path)
pq.write_to_dataset(table, root_path='dataset_name',
                    partition_cols=['one', 'two'], filesystem=fs)

兼容性说明:如果使用 pq.write_to_dataset 创建一个将由 HIVE 使用的表,则分区列值必须与您正在运行的 HIVE 版本的允许字符集兼容。

写入 _metadata_common_metadata 文件#

一些处理框架,例如 Spark 或 Dask(可选),使用带有分区数据集的 _metadata_common_metadata 文件。

这些文件包含关于完整数据集的模式信息(对于 _common_metadata),以及可能包含分区数据集中所有文件的所有行组元数据(对于 _metadata)。实际文件是仅包含元数据的 Parquet 文件。请注意,这并非 Parquet 标准,而是这些框架在实践中设定的一种约定。

使用这些文件可以更有效地创建 Parquet 数据集,因为它可以使用存储的模式和所有行组的文件路径,而不是推断模式并遍历目录以查找所有 Parquet 文件(对于访问文件成本高昂的文件系统尤其如此)。

write_to_dataset() 函数不会自动写入此类元数据文件,但您可以使用它来收集元数据并手动组合和写入它们

# Write a dataset and collect metadata information of all written files
metadata_collector = []
pq.write_to_dataset(table, root_path, metadata_collector=metadata_collector)

# Write the ``_common_metadata`` parquet file without row groups statistics
pq.write_metadata(table.schema, root_path / '_common_metadata')

# Write the ``_metadata`` parquet file with row groups statistics of all files
pq.write_metadata(
    table.schema, root_path / '_metadata',
    metadata_collector=metadata_collector
)

当不使用 write_to_dataset() 函数,而是使用 write_table()ParquetWriter 写入分区数据集的单个文件时,也可以使用 metadata_collector 关键字来收集写入文件的 FileMetaData。在这种情况下,您需要确保在合并元数据之前自行设置行组元数据中包含的文件路径,并且所有不同文件和收集的 FileMetaData 对象的模式应该相同。

metadata_collector = []
pq.write_table(
    table1, root_path / "year=2017/data1.parquet",
    metadata_collector=metadata_collector
)

# set the file path relative to the root of the partitioned dataset
metadata_collector[-1].set_file_path("year=2017/data1.parquet")

# combine and write the metadata
metadata = metadata_collector[0]
for _meta in metadata_collector[1:]:
    metadata.append_row_groups(_meta)
metadata.write_metadata_file(root_path / "_metadata")

# or use pq.write_metadata to combine and write in a single step
pq.write_metadata(
    table1.schema, root_path / "_metadata",
    metadata_collector=metadata_collector
)

从分区数据集读取#

ParquetDataset 类接受目录名或文件路径列表,并且可以发现和推断一些常见的分区结构,例如 Hive 生成的结构。

dataset = pq.ParquetDataset('dataset_name/')
table = dataset.read()

您还可以使用 pyarrow.parquet 提供的便利函数 read_table,这样就无需额外的 Dataset 对象创建步骤。

table = pq.read_table('dataset_name')

注意:加载时,原始表中的分区列的类型将转换为 Arrow 字典类型(pandas categorical)。在保存/加载过程中,分区列的顺序不会保留。如果从远程文件系统读取到 pandas dataframe,您可能需要运行 sort_index 以维护行顺序(只要在写入时启用了 preserve_index 选项)。

其他特性

  • 对所有列(使用行组统计信息)进行过滤,而不仅仅是对分区键进行过滤。

  • 细粒度分区:除了类似 Hive 的分区(例如“/2019/11/15/”而不是“/year=2019/month=11/day=15/”)之外,还支持目录分区方案,并且能够为分区键指定模式。

注意

  • 当您在读取列子集时想要将分区键包含在结果中,需要使用 columns 关键字明确包含分区键。

与 Spark 一起使用#

Spark 对其将读取的 Parquet 文件类型施加了一些限制。flavor='spark' 选项将自动设置这些选项,并清理 Spark SQL 不支持的字段字符。

多线程读取#

默认情况下,每个读取函数都使用多线程并行读取列。根据 I/O 速度以及解码特定文件中列的成本(特别是使用 GZIP 压缩时),这可以显着提高数据吞吐量。

这可以通过指定 use_threads=False 来禁用。

注意

并发使用的线程数由 Arrow 自动推断,可以使用 cpu_count() 函数进行检查。

从云存储读取#

除了本地文件,pyarrow 还通过 filesystem 关键字支持其他文件系统,例如云文件系统

from pyarrow import fs

s3  = fs.S3FileSystem(region="us-east-2")
table = pq.read_table("bucket/object/key/prefix", filesystem=s3)

目前,支持 HDFSAmazon S3兼容存储。有关更多详细信息,请参阅 文件系统接口 文档。对于这些内置文件系统,如果文件路径指定为 URI,也可以从文件路径推断文件系统。

table = pq.read_table("s3://bucket/object/key/prefix")

如果存在 fsspec 兼容的实现,仍然可以支持其他文件系统。有关更多详细信息,请参阅 将 fsspec 兼容的文件系统与 Arrow 结合使用。一个例子是 Azure Blob 存储,可以通过 adlfs 包进行接口。

from adlfs import AzureBlobFileSystem

abfs = AzureBlobFileSystem(account_name="XXXX", account_key="XXXX", container_name="XXXX")
table = pq.read_table("file.parquet", filesystem=abfs)

Parquet 模块化加密(列式加密)#

从 Apache Arrow 4.0.0 开始,C++ 支持 Parquet 文件的列式加密;从 Apache Arrow 6.0.0 开始,PyArrow 也支持列式加密。

Parquet 采用信封加密实践,其中文件部分使用“数据加密密钥”(DEK)加密,DEK 使用“主加密密钥”(MEK)加密。DEK 由 Parquet 为每个加密文件/列随机生成。MEK 由用户选择的密钥管理服务(KMS)生成、存储和管理。

读取和写入加密的 Parquet 文件涉及将文件加密和解密属性分别传递给 ParquetWriterParquetFile

写入加密的 Parquet 文件

encryption_properties = crypto_factory.file_encryption_properties(
                                 kms_connection_config, encryption_config)
with pq.ParquetWriter(filename, schema,
                     encryption_properties=encryption_properties) as writer:
   writer.write_table(table)

读取加密的 Parquet 文件

decryption_properties = crypto_factory.file_decryption_properties(
                                                 kms_connection_config)
parquet_file = pq.ParquetFile(filename,
                              decryption_properties=decryption_properties)

为了创建加密和解密属性,应创建并使用 KMS 客户端详细信息初始化一个 pyarrow.parquet.encryption.CryptoFactory,如下所述。

KMS 客户端#

主加密密钥应保存在用户组织中部署的生产级密钥管理系统(KMS)中。使用 Parquet 加密需要为 KMS 服务器实现一个客户端类。任何 KmsClient 实现都应遵循 pyarrow.parquet.encryption.KmsClient 定义的非正式接口,如下所示:

import pyarrow.parquet.encryption as pe

class MyKmsClient(pe.KmsClient):

   """An example KmsClient implementation skeleton"""
   def __init__(self, kms_connection_configuration):
      pe.KmsClient.__init__(self)
      # Any KMS-specific initialization based on
      # kms_connection_configuration comes here

   def wrap_key(self, key_bytes, master_key_identifier):
      wrapped_key = ... # call KMS to wrap key_bytes with key specified by
                        # master_key_identifier
      return wrapped_key

   def unwrap_key(self, wrapped_key, master_key_identifier):
      key_bytes = ... # call KMS to unwrap wrapped_key with key specified by
                      # master_key_identifier
      return key_bytes

具体的实现将在运行时由用户提供的工厂函数加载。此工厂函数将用于初始化 pyarrow.parquet.encryption.CryptoFactory,以创建文件加密和解密属性。

例如,为了使用上面定义的 MyKmsClient

def kms_client_factory(kms_connection_configuration):
   return MyKmsClient(kms_connection_configuration)

crypto_factory = CryptoFactory(kms_client_factory)

一个开源 KMS 的此类类的示例可以在 Apache Arrow GitHub 仓库中找到。生产 KMS 客户端应与组织的安全管理员合作设计,并由具有访问控制管理经验的开发人员构建。一旦创建了此类类,就可以通过工厂方法将其传递给应用程序,并由普通 PyArrow 用户利用,如上面加密 Parquet 写入/读取示例所示。

KMS 连接配置#

KMS 连接配置(创建文件加密和解密属性时使用的 pyarrow.parquet.encryption.KmsConnectionConfig)包括以下选项:

  • kms_instance_url,KMS 实例的 URL。

  • kms_instance_id,将用于加密的 KMS 实例 ID(如果存在多个 KMS 实例)。

  • key_access_token,将传递给 KMS 的授权令牌。

  • custom_kms_conf,包含 KMS 类型特定配置的字符串字典。

加密配置#

pyarrow.parquet.encryption.EncryptionConfiguration(创建文件加密属性时使用)包括以下选项:

  • footer_key,用于页脚加密/签名的主密钥 ID。

  • column_keys,哪些列使用哪个密钥进行加密。字典,以主密钥 ID 为键,列名列表为值,例如 {key1: [col1, col2], key2: [col3]}。请参阅下面关于嵌套字段的注意事项。

  • encryption_algorithm,Parquet 加密算法。可以是 AES_GCM_V1(默认)或 AES_GCM_CTR_V1

  • plaintext_footer,是否以纯文本写入文件页脚(否则它将被加密)。

  • double_wrapping,是否使用双重包装——数据加密密钥(DEK)使用密钥加密密钥(KEK)加密,而 KEK 又使用主加密密钥(MEK)加密。如果设置为 false,则使用单重包装——DEK 直接使用 MEK 加密。

  • cache_lifetime,缓存实体(密钥加密密钥、本地包装密钥、KMS 客户端对象)的生命周期,表示为 datetime.timedelta

  • internal_key_material,是否将密钥材料存储在 Parquet 文件页脚内部;此模式不生成额外文件。如果设置为 false,密钥材料存储在同一文件夹中的单独文件中,这使得不可变 Parquet 文件能够进行密钥轮换。

  • data_key_length_bits,Parquet 密钥管理工具随机生成的数据加密密钥(DEK)的长度。可以是 128、192 或 256 位。

注意

double_wrapping 为真时,Parquet 实现了一种“双信封加密”模式,最大限度地减少程序与 KMS 服务器的交互。在此模式下,DEK 使用“密钥加密密钥”(KEK,由 Parquet 随机生成)加密。KEK 在 KMS 中使用“主加密密钥”(MEK)加密;结果和 KEK 本身都缓存在进程内存中。

加密配置示例

encryption_config = pq.EncryptionConfiguration(
   footer_key="footer_key_name",
   column_keys={
      "column_key_name": ["Column1", "Column2"],
   },
)

注意

加密具有嵌套字段(结构体、映射或列表数据类型)的列需要为内部字段而不是外部列本身提供列密钥。为外部列配置列密钥会导致此错误(此处列名为 col)。

OSError: Encrypted column col not in file schema

包含嵌套字段的列的加密配置示例,其中所有列都将使用由 column_key_id 标识的相同密钥进行加密

import pyarrow.parquet.encryption as pe

schema = pa.schema([
  ("ListColumn", pa.list_(pa.int32())),
  ("MapColumn", pa.map_(pa.string(), pa.int32())),
  ("StructColumn", pa.struct([("f1", pa.int32()), ("f2", pa.string())])),
])

encryption_config = pe.EncryptionConfiguration(
   footer_key="footer_key_name",
   column_keys={
      "column_key_id": [
        "ListColumn.list.element",
        "MapColumn.key_value.key", "MapColumn.key_value.value",
        "StructColumn.f1", "StructColumn.f2"
      ],
   },
)

解密配置#

pyarrow.parquet.encryption.DecryptionConfiguration(在创建文件解密属性时使用)是可选的,它包含以下选项:

  • cache_lifetime,缓存实体(密钥加密密钥、本地包装密钥、KMS 客户端对象)的生命周期,表示为 datetime.timedelta

内容定义分块#

注意

此功能是实验性的,未来版本可能会更改。

PyArrow 引入了一个实验性功能,通过内容定义分块 (CDC) 优化 Parquet 文件以用于内容寻址存储 (CAS) 系统。此功能可以高效地跨文件进行数据去重,从而提高网络传输和存储效率。

启用后,数据页将根据内容定义的数据块边界写入,这些边界由滚动哈希算法确定,该算法根据数据的实际内容识别数据块边界。当列中的数据被修改(例如,插入、删除或更新)时,此方法可以最大限度地减少更改的数据页数量。

此功能可以通过在 Parquet 写入器中设置 use_content_defined_chunking 参数来启用。它接受布尔值或字典进行配置。

  • True:使用默认配置
    • 最小块大小:256 KiB

    • 最大块大小:1024 KiB

    • 归一化级别:0

  • dict:允许自定义分块参数
    • min_chunk_size:最小块大小(以字节为单位)(默认值:256 KiB)。

    • max_chunk_size:最大块大小(以字节为单位)(默认值:1024 KiB)。

    • norm_level:用于调整块大小分布的归一化级别(默认值:0)。

请注意,块大小是在应用任何编码或压缩之前对逻辑值计算的。数据页的实际大小可能会根据使用的编码和压缩而有所不同。

注意

为了充分利用此功能,您应确保 Parquet 写入选项在写入和文件之间保持一致。对不同文件使用不同的写入选项(如压缩、编码或行组大小)可能会阻止正确的去重并导致次优的存储效率。

import pyarrow as pa
import pyarrow.parquet as p

table = pa.Table.from_pandas(df)

# Enable content-defined chunking with default settings
pq.write_table(table, 'example.parquet', use_content_defined_chunking=True)

# Enable content-defined chunking with custom settings
pq.write_table(
    table,
    'example_custom.parquet',
    use_content_defined_chunking={
        'min_chunk_size': 128 * 1024,  # 128 KiB
        'max_chunk_size': 512 * 1024,  # 512 KiB
    }
)