Fsspec
是一个库,充当许多类似文件系统的存储后端(例如远程(例如 SSH、HDFS)和云(例如 GCS、S3)服务)的通用 Python 接口。
在本文中,我们将介绍它新的远程内容缓存功能,在初始读取后保留本地副本以加快查找速度。类似的文本首次出现在 fsspec 文档 中,但在这里我们提供了更多详细信息和用例。
这项工作灵感来自 Intake 中的缓存机制,该机制已被证明非常有用和流行,但它 a) 对于除最简单的情况以外的所有情况都很难使用,以及 b) 仅在 Intake 目录中可用,因此对其他人没有用。现在,我们已将类似的概念在更低级别提供。
现在,任何使用 Python 文件的人都可以缓存整个或部分远程数据!
Intake 体验
Intake 的全部意义在于在目录中描述数据源,以便可以为特定作业找到正确的数据源,并且可以将数据加载到 Python 中,而用户只需付出最少的努力。
您可能想要拥有远程数据的本地副本有两个主要原因
- 它将被多次访问,并且本地存储速度远快于从远程读取,因此在第一次访问时复制可以节省大量时间。
- Python 是一种非常通用的语言,它连接到许多最初用 C 或其他语言编写的外部库。即使
fsspec
通过实现 Python 类文件接口来提供对远程数据的访问,编译后的代码通常需要一个真正的本地文件才能使用。
此外,可以提供许多很好的额外好处,以及 Intake 实现,例如下载时的解压缩,或从通常不被视为文件系统的系统中获取数据,但它们仍然是数据分发机制(例如,DAT 项目和 git
)。
不幸的是,尝试创建规范来描述所有这些内容,并让 Intake 通过其内部配置系统管理缓存,这被证明难以实现。当我们想要更多功能,例如缓存过期和多个存储位置时,重写代码变得难以处理。
fsspec
“文件系统规范”,或简称为 fsspec
,是一个新项目,用于统一访问本地磁盘以外各种位置的数据。它提供了一个简单、全面且熟悉的 API,无论您是访问亚马逊的 S3 服务、HDFS 集群还是通过 SSH 连接的远程服务器,该 API 都是统一的。
由于其他文件系统实现,Dask 和 Intake 都开始依赖 fsspec
,因此它是实现文件缓存的最佳位置,以便将此功能提供给所有用户。此外,代码的结构使得编写缓存层作为其自身的文件系统实现变得非常容易。
文件缓存
您可能希望做的最简单的事情是在第一次访问时将远程文件复制到本地,然后引用本地副本。实现此功能的类被称为“filecache”文件系统,由 WholeFileCacheFileSystem
类实现。您需要提供远程文件系统的协议和任何选项,它将对该远程系统进行调用以列出和下载文件,但一旦下载,就会使用本地副本。
例如,在 之前的一篇文章 中,我们展示了如何将远程存储与 fsspec
整合
import fsspec
of = fsspec.open("s3://anaconda-public-datasets/iris/iris.csv", mode='rt', anon=True)
with of as f:
print(f.readline())
输出第一行数据,即 Iris 数据集中的第一个样本(“5.1,3.5,1.4,0.2,Iris-setosa”)。这每次都会打开远程文件并下载数据。如果我们希望无缝提供本地缓存,我们可以执行以下操作
import fsspec
of = fsspec.open("filecache://anaconda-public-datasets/iris/iris.csv", mode='rt',
cache_storage='/tmp/cache1',
target_protocol='s3', target_options={'anon': True})
with of as f:
print(f.readline())
这也产生相同的输出,但现在我们在本地目录中创建了几个文件,“cache” 和 “f89e764b2ba1a15b39e656eba3c67e583f8497bb68dfa760f07618deac3db7ff”。第二个文件是远程文件的副本(带有散列名称),而第一个文件是所有存储数据的元数据。现在,如果您再次打开文件,不会轮询远程位置,并且速度快得多。此外,输出文件 f
处于文本模式,但当然,存储的文件是来自远程源的原始字节。
部分文件缓存
如果您打算完全读取远程文件,则将整个文件复制到本地是有意义的。但是,在许多情况下,您可能不想读取整个文件,因为您没有足够的存储空间或不想等待下载整个文件。
例如,文件 “s3://anaconda-public-datasets/gdelt/csv/20150906.export.csv” 比较大,为 47MB(当然,文件可以比这大得多!)。
of = fsspec.open("blockcache://anaconda-public-datasets/gdelt/csv/20150906.export.csv",
mode='rt', target_protocol='s3', cache_storage='/tmp/cache2',
target_options={'anon': True, "default_block_size": 2**20})
with of as f:
print(f.read(1000))
再次运行此代码时,第二次返回的速度比第一次快得多,因为数据存在于本地。有趣的是,列出存储目录显示以下内容
total 2064
-rw-r--r-- 1 mdurant 47319281 11 Oct 14:28 6edb94fb86a5c48a6d3993efba3b8fa1ff62af1b920f621ac39ffdff8a15c7e4
-rw-r--r-- 1 mdurant 267 11 Oct 14:28 cache
在我的系统和给定的磁盘上,稀疏文件是可能的,因此对目录执行 du
(此命令在 linux 和 osx 上可用)显示,显然为 47MB 的文件仅占用了 1MB 的空间,即代码中选择的 “block_size”。当我们读取文件时,将填充更多块,但在这种情况下,如果这只是我们感兴趣的该文件的一部分,那么这正是我们想要的行为。
这种第二种形式的缓存确实存在一些注意事项:只有在您的操作系统和磁盘文件系统支持的情况下,您才能获得 “稀疏” 行为;输出是一个 Python 文件对象,因此它仅适用于接受此对象的代码(即 Python 代码,而不是 C 代码);并且它仅适用于 fsspec
实现提供同样基于 fsspec
的文件实例的地方(适用于本地文件、s3、gcs、ftp、hdfs)。
请注意,我已经指定了两个示例的缓存位置不要重叠,因为如果 filecache
遇到 blockcache
创建的部分文件,它将出错。
更多乐趣
文件系统链接
通过直接使用 fsspec
类,您可以将一些相当复杂的行为链接在一起。例如,请考虑以下代码
fs = fsspec.filesystem('http')
f = fs.open('https://www.bkk.hu/gtfs/budapest_gtfs.zip')
fs2 = fsspec.filesystem('zip', fo=f)
fs3 = fsspec.filesystem('filecache', target_protocol=fs2, cache_storage='/tmp/cache3')
f2 = fs3.open('stops.txt', 'rt')
df = pandas.read_csv(f2)
df.head()
这打开了远程服务器上的压缩存档,并且仅在本地缓存了一个包含的文件(“stops.txt”),并将此文件传递给 Pandas。
缓存过期和检查
理论上,缓存文件系统的多个实例可以访问相同的本地存储,并了解那里的所有文件 - 缓存元数据会以设定的节奏自动重新加载,默认情况下为 10 秒。
相反,缓存可以使用多个存储区域来检查数据。它将按给定的顺序解析这些区域,如果找不到文件,则会像往常一样从远程获取。这将允许在共享网络驱动器上创建一个 “二级” 缓存,用于网络上的人员频繁访问的数据。在这种情况下,仅写入列表中给出的最后一个缓存位置。
filecache
和 blockcache
都允许缓存文件过期 - 当磁盘上的版本比某个秒数旧时,它将从远程源更新。对于稀疏文件,这是自本地文件创建以来的时间(即最旧块的年龄)。
最后,在后端支持的地方(不是 HTTP,而是基本上所有其他地方),您可以要求缓存系统在每次访问时读取远程文件的校验和或其他唯一标识符,这样您就可以始终了解它是否已更改,以及是否需要更新缓存。当然,这仍然需要一些时间,但通常比在每次读取时从远程下载整个文件要快得多。
总结
文件系统级别的缓存适用于所有人,无论您从哪里获取数据,都可以通过 fsspec
使用。这种方法已被证明既易于实现又易于使用,因此它很快将成为 Intake 中的推荐方法。