本文档主要介绍使用 PyProton 的最佳实践,内容将包含一些跟易用性、性能相关的使用技巧以及一些关于稳定性、健壮性的特别说明。
PyProton 访问TOS需要经过TOS认证,目前支持两种认证类型:Assume Role、Access Key ID/Secret Access Key(简称AK/SK)。这些凭证的详细介绍请参考:Assume Role的获取文档 和 Access Key(密钥)管理 。获取到这些凭证之后,PyProton支持两种方式设置它们:环境变量和代码设置,推荐设置到环境变量中。下面以AK/SK为例,介绍如何初始化 ProtonFileSystem
:
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", # access key id (ak) secret="", # secret access key (sk) session_token="", # 参考assume role的指引文档获取"SessionToken" ) fs.ls("tos://bucket/to/path/", detail=False)
整体上来说:用户看到的PyProton的日志是由:PyProton 和 Proton SDK的日志汇聚到一处所呈现出来的结果。以下分别说明:
虽然以上两种类型的日志输出方式不同,但有一些共性:
PyProton 支持通过一个名为 PROTONFS_LOGGING_LEVEL
的环境变量来设置其自身和内置的Proton SDK的Log Level。默认情况下,如果用户没有设置该环境变量,PyProton会使用 INFO
级别。当用户想debug问题时,可以显式将其设置为DEBUG
,环境变量的设置可以通过两种方式:
直接在OS的shell中 export PROTONFS_LOGGING_LEVEL='DEBUG'
生效。
在计算引擎中间接指定,这里以最常用的Ray为例。
方式一:
import ray runtime_env = {"env_vars": {"PROTONFS_LOGGING_LEVEL": "DEBUG"}} ray.init(runtime_env=runtime_env) @ray.remote def f(): # user business print(ray.get(f.remote()))
方式二:
import ray from ray.runtime_env import RuntimeEnv runtime_env = RuntimeEnv(env_vars={"PROTONFS_LOGGING_LEVEL": "DEBUG"}) ray.init(runtime_env=runtime_env) @ray.remote def f(): # user business print(ray.get(f.remote()))
由于PyProton默认调用了Proton SDK的接口,因此无法避免拉起JVM。对于JVM的常规调优参数主要是Memory的最大(Xmx
)、最小(Xms
)阈值。PyProton已经默认设置了一批Proton在生产实践中久经考验且稳定的JVM参数。其中:Xms
的默认值为64m
,Xmx
的默认值为2g
,一般情况下用户无需额外调整,但如果面对极端场景,可以通过配置 PYPROTON_JVM_XMX
环境变量来调整Proton SDK的 Xmx
参数,配置方式请参考上文中的“Log Level设置”章节。
在调用 ls API 的时候,如果只想获取一个 path 下的文件或文件夹清单,可显式将 detail 参数设置为False
(默认值为True
)以提升在性能。调用示例:
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", secret="", session_token="", ) fs.ls("tos://bucket/to/path/", detail=False)
原因说明:为了对齐 fsspec#ls
API的语义,当调用ls API的时候,如果 detail=True
当碰到一个资源类型为“目录”时,它会额外发起一次请求去获取该目录下所有资源汇总后的存储占用量。
在调用rm API的时候,如果想获得最佳的删除性能,请不要显式指定maxdepth
参数并覆盖其默认值(该参数的默认值为:None
)。调用示例:
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", secret="", session_token="", ) # 是否需要将recursive设置为True,取决于是否需要递归删除子目录 fs.rm("tos://bucket/to/path/", recursive=True)
原因说明:如果用户显式设置 maxdepth
内部调用将会fallback到 fsspec#rm
的默认实现:对该 path 进行expand 和 walk(list)。对于删除海量文件场景,可能会引发超长的删除时间以及Proton SDK的OOM。
而如果不显式指定 maxdepth ,则默认会使用更高效的batch delete的删除方式。这种针对海量文件的删除优化,无论在稳定性和性能上都有大幅的提升。
对于单个大文件(如GB级别)的写优化,以下使用实践将有助于降低写入耗时、提升写吞吐与稳定性:
拆分chunk攒批多次写:建议chunk_size设置为“5MB”跟fsspec默认的buffer size保持一致,可以避免内存空间碎片,提升内存利用率。并且可以防止一次性积攒大量的数据因为内存OOM而造成稳定性问题。请参考以下示例代码:
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", secret="", session_token="", ) def write_in_chunks(file_object, content, chunk_size): for i in range(0, len(content), chunk_size): file_object.write(content[i : i + chunk_size]) def write_via_pyproton(dest_folder, size_of_byte_per_file, file_name, content, chunk_size=0): full_file_path = f"{dest_folder}/{file_name}" if chunk_size != 0: with fs.open(full_file_path, "wb", block_size=chunk_size) as f: write_in_chunks(f, content, chunk_size) else: with fs.open(full_file_path, "wb") as f: f.write(content) assert fs.exists(full_file_path), f"File {full_file_path} does not exist."
调优 Proton 参数以获得写时多盘Staging MPU的写优势从而答复提升写性能(降低写耗时),具体的参数说明请参考Proton官网文档[1],以下示例给出调优设置方式(请注意,所有配置项的值只支持字符串类型,如果值为数字,也请加上""
):
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", secret="", session_token="", # proton sdk支持的参数设置 config_dict = { # other args... "fs.tos.multipart.staging-dir": "", "fs.tos.multipart.size": "", "fs.tos.multipart.thread-pool-size": "", "fs.tos.multipart.staging-buffer-size": "", "fs.tos.multipart.threshold": "", "fs.tos.task.thread-pool-size": "", } )
对于单个大的二进制文件(如GB级别)的读优化,以下使用实践将有助于降低读耗时、提升读吞吐与稳定性:
拆分chunk攒批多次读:建议chunk_size设置为“5MB”,可以防止一次性读取大量的数据因为内存OOM而造成稳定性问题。请参考以下示例代码:
from pyproton.protonfs import ( ProtonFileSystem ) fs = ProtonFileSystem( endpoint_url="http://tos-cn-beijing.volces.com", key="", secret="", session_token="", ) def read_from_chunks(file_handle, chunk_size=0): total_bytes_read = 0 if chunk_size == 0: total_bytes_read = len(file_handle.read(-1)) else: while True: data_chunk = file_handle.read(chunk_size) bytes_read = len(data_chunk) if not data_chunk: break # End of file reached total_bytes_read += bytes_read return total_bytes_read def read_via_pyproton(full_file_path, size_in_bytes, chunk_size=0): with fs.open(full_file_path, "rb") as f: assert read_from_chunks(f, chunk_size) == size_in_bytes
对于有明确换行符且格式良好的文本文件,推荐使用 readline
或readlines
API。
用户可以认为:在使用PyProton进行读写的过程中,无需担心连接稳定性的问题。用户在使用 PyProton 时,经常的模式可能会是“边读边处理”或“边写边处理”。“处理”的时间可长可短,如果在处理的耗时非常长的时候,用户也无需担心连接断开从而导致读写异常。因为 Proton 内置了连接重建和连接恢复以及断点续传和断点续读的一些接续处理逻辑。这些稳定性的优化对用户是无感的。