读取和写入 Apache Parquet 格式#
Apache Parquet 项目提供了一种标准化的开源列式存储格式,用于数据分析系统。 它最初是为在 Apache Hadoop 中与 Apache Drill,Apache Hive,Apache Impala 和 Apache Spark 等系统一起使用而创建的,并将其作为高性能数据 IO 的共享标准。
Apache Arrow 是用于读取或写入 Parquet 文件的数据的理想内存传输层。 我们一直在同时开发 Apache Parquet 的 C++ 实现,其中包括与内存中 Arrow 数据进行本机,多线程的 C++ 适配器。 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 数据,则 Tabular Datasets 和分区可能是您正在寻找的。
Parquet 文件写入选项#
write_table()
有许多选项可以在写入 Parquet 文件时控制各种设置。
version
,要使用的 Parquet 格式版本。'1.0'
确保与旧版读取器的兼容性,而'2.4'
及更高的值可以启用更多 Parquet 类型和编码。data_page_size
,用于控制列块内编码数据页面的大致大小。 目前,此值默认为 1MB。flavor
,用于设置特定于 Parquet 使用者的兼容性选项,例如 Apache Spark 的'spark'
。
有关更多详细信息,请参见 write_table()
docstring。
下面描述了一些其他特定于数据类型处理的选项。
省略 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 0x7f9dffdc0720>
created_by: parquet-cpp-arrow version 20.0.0
num_columns: 4
num_rows: 3
num_row_groups: 1
format_version: 2.6
serialized_size: 2606
In [21]: parquet_file.schema
Out[21]:
<pyarrow._parquet.ParquetSchema object at 0x7f9dffda7f40>
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 文件元数据#
可以通过 ParquetFile
访问 Parquet 文件的 FileMetaData
,如上所示
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 0x7f9dffdc1e90>
created_by: parquet-cpp-arrow version 20.0.0
num_columns: 4
num_rows: 3
num_row_groups: 1
format_version: 2.6
serialized_size: 2606
返回的 FileMetaData
对象允许检查 Parquet 文件元数据,例如行组和列块元数据以及统计信息
In [31]: metadata.row_group(0)
Out[31]:
<pyarrow._parquet.RowGroupMetaData object at 0x7f9dffdc26b0>
num_columns: 4
num_rows: 3
total_byte_size: 282
sorting_columns: ()
In [32]: metadata.row_group(0).column(0)
Out[32]:
<pyarrow._parquet.ColumnChunkMetaData object at 0x7f9dffdc25c0>
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 0x7f9dffdc2840>
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
compression: SNAPPY
encodings: ('PLAIN', 'RLE', 'RLE_DICTIONARY')
has_dictionary_page: True
dictionary_page_offset: 4
data_page_offset: 36
total_compressed_size: 104
total_uncompressed_size: 100
数据类型处理#
将类型读取为 DictionaryArray#
read_table
和 ParquetDataset
中的 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'])
在这种情况下,根路径指定将保存数据的父目录。分区列是用于对数据集进行分区的列名。列按照给定的顺序进行分区。分区拆分由分区列中的唯一值确定。
要使用其他文件系统,您只需添加 filesystem 参数,各个表的写入使用 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
文件。
这些文件包含有关完整数据集的 schema 信息(对于 _common_metadata
)以及分区数据集中的所有文件的所有行组元数据(对于 _metadata
)。实际文件是仅包含元数据的 Parquet 文件。请注意,这不是 Parquet 标准,而是这些框架在实践中设置的约定。
使用这些文件可以更有效地创建 Parquet Dataset,因为它可以使用存储的 schema 和所有行组的文件路径,而不是推断 schema 并爬取目录以查找所有 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 对象的 schema 应该相同。
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
类接受目录名称或文件路径列表,并且可以发现和推断一些常见的 partition 结构,例如 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/”),以及为分区键指定 schema 的能力。
注意
当您想要在读取列的子集时将分区键包含在结果中时,需要将分区键显式包含在
columns
关键字中。
与 Spark 一起使用#
Spark 对其将读取的 Parquet 文件的类型施加了一些约束。选项 flavor='spark'
将自动设置这些选项,并且还会清理 Spark SQL 不支持的字段字符。
多线程读取#
默认情况下,每个读取函数都使用多线程并行读取列。根据 IO 的速度以及解码特定文件中的列的成本(尤其是在使用 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)
目前,支持 HDFS
和 Amazon S3-compatible 存储
。有关更多详细信息,请参见 文件系统接口 文档。对于那些内置文件系统,如果指定为 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 文件的列式加密。
Parquet 使用信封加密实践,其中文件部分使用“数据加密密钥”(DEK)加密,DEK 使用“主加密密钥”(MEK)加密。DEK 由 Parquet 随机生成,用于每个加密的文件/列。MEK 在用户选择的密钥管理服务(KMS)中生成、存储和管理。
读取和写入加密的 Parquet 文件涉及将文件加密和解密属性传递给 ParquetWriter
和 ParquetFile
。
写入加密的 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)
为了创建加密和解密属性,应该创建一个 pyarrow.parquet.encryption.CryptoFactory
,并使用 KMS 客户端详细信息进行初始化,如下所述。
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)
在 Apache Arrow GitHub 仓库中可以找到一个开源 KMS 类的 example
示例。生产环境的 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
,数据加密密钥 (DEK) 的长度,由 Parquet 密钥管理工具随机生成。 可以是 128、192 或 256 位。
注意
当 double_wrapping
为 true 时,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"],
},
)
注意
加密具有嵌套字段(结构体、map 或列表数据类型)的列需要内部字段的列密钥,而不是外部列本身。 为外部列配置列密钥会导致此错误(此处的列名称为 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
。