Fsspec
是一个库,它充当许多类似文件系统的存储后端的通用 Python 接口,例如远程(例如,SSH、HDFS)和云(例如,GCS、S3)服务。
在本文中,我们将介绍其缓存远程内容的新功能,保留本地副本以便在初始读取后更快地查找。类似文本首先出现在 fsspec 文档中,但在此处我们提供更多详细信息和用例。
这项工作灵感来源于 Intake 中的缓存机制,该机制被证明是有用且受欢迎的,但 a) 对于除了最简单的情况之外的所有情况都相当难以使用,并且 b) 仅在 Intake 目录中可用,因此对其他人无用。现在我们已经在一个更低的层面上提供了一个类似的概念。
现在,任何使用 Python 文件的人都可以使用全部或部分远程数据的缓存!
Intake 体验
Intake 的全部意义在于在目录中描述数据源,以便可以为特定作业找到正确的数据源,并且可以将数据加载到 Python 中,用户只需付出最小的努力。
您可能需要远程数据的本地副本主要有两个原因
- 它将被多次访问,并且本地存储比从远程读取快得多,那么在首次访问时复制可以证明是显着的时间节省。
- Python 是一种非常通用的语言,可以连接到许多最初用 C 或其他语言编写的外部库。即使
fsspec
通过实现 Python 类文件接口来提供对远程数据的访问,编译后的代码通常也需要一个真正的本地文件才能工作。
此外,还可以提供许多额外的优点,以及 Intake 实现,例如下载时解压缩,或从通常不被认为是文件系统的系统抓取,但仍然是数据分发机制(例如,DAT 项目和 git
)。
不幸的是,尝试创建一个规范来描述所有这些,并让 Intake 通过其内部配置系统管理缓存被证明难以实现。当我们想要更多功能(例如缓存过期和多个存储位置)时,重写代码变得棘手。
fsspec
“文件系统规范”,或简称为 fsspec
,是一个新项目,旨在统一访问本地磁盘以外的各种位置的数据。它提供了一个简单、全面且熟悉的 API,无论您访问的是 Amazon 的 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 中推荐的方法。