你只想翻一本厚书的其中几页,没必要先把整套书搬回家。分析 S3 上的大文件却常常如此:程序先下载整个对象,才能开始工作。DemandMap 想改变这一步。它把远端对象伪装成一块本地数据,让程序用到哪里,再取哪里。作者展示了一个约 100 毫秒构造大型 Polars DataFrame 的例子。值得注意的是,这不是在 100 毫秒内下载完全部数据。本文数字与效果均来自作者的 Reddit 帖文及其代码仓库,尚无独立信源交叉验证。
把远端仓库接到本地书桌
Amazon S3 是对象存储:它按键名保存完整对象,常被用来放 Parquet、Arrow 等分析数据。它不像本地磁盘,不能天然配合操作系统做随机分页读取。
DemandMap 借用了内存映射(memory mapping)的思路。普通内存映射让程序像读内存一样读本地文件;程序真正访问某个位置时,操作系统才把对应页面载入。DemandMap 则试图把这种体验延伸到远端对象。
作者给出的示例创建了一个 S3Alloc,用本地 cache.bin 保存缓存,并把远端对象切成 1 MiB 的块。程序触及尚未载入的区域时,再请求相应数据。远端按需读取通常依靠 HTTP Range Request——只获取对象中的一段字节,而不是整包下载。
这套设计首先处理 macOS 的 user faulting API。user faulting 可以理解为:当程序访问一块尚未准备好的内存时,由程序自己补入数据。不同操作系统提供的接口并不相同;作者称 DemandMap 的目标是最终提供跨平台的按需映射块存储 API,但跨平台目前只是目标。项目还提供 Rust API。
100 毫秒到底完成了什么?
演示从两个远端数组建立缓冲区。两个数组各含 50,000,000 个 i64 整数,各自超过 400 MB。随后,作者把它们构造成一个形状为 (50_000_000, 2) 的 Polars DataFrame。Polars 是面向分析任务的列式数据处理库;列式布局把同一列的数据连续存放,便于按范围定位和读取。
据作者报告,构造这个 DataFrame 约需 100 毫秒。这里完成的主要是建立映射和数据结构,让 DataFrame 可以开始使用,而不是把远端内容全部传到本机。实际访问某一列或某一段数据时,DemandMap 仍要发起网络请求;首次读取的等待时间和流量并没有消失,只是从启动阶段推迟到了真正使用数据的时候。
标题所说的“600 MB”也与演示口径不一致。两个数组各超过 400 MB;按 50,000,000 × 2 × 8 字节计算,原始整数数据约为 800 MB。作者还特意给 “Download” 加了引号。因此,更准确的说法是“约 100 毫秒打开一个远端大型 DataFrame”,而非“约 100 毫秒下载完成”。
为什么值得关注?
价值不在于让网络传输凭空变快,而在于改变等待方式。如果分析只会触及大对象的一小部分,按需映射可以避免启动时搬运无关数据,也能让程序更早进入可操作状态。本地缓存还可保留已经取回的数据块,供后续访问使用。
这种思路与 Apache Arrow 的列式内存布局相配合时尤其直观:如果数据格式允许程序先定位元数据、列和字节范围,就可能只请求当前计算需要的部分。它把“先完整下载,再分析”改成了“先建立入口,边用边取”。
局限与未知
- 示例实际连接的是 DigitalOcean Spaces 的 S3 兼容存储,不能视为已经直接验证 AWS S3。
- 约 100 毫秒只说明建立映射和构造 DataFrame 的速度。实际性能仍取决于缓存命中、网络、对象存储服务以及访问模式;材料没有提供完整读取或对照测试。
- 项目目前首先面向 macOS。其他平台的支持范围、稳定性和成熟度,现有材料没有披露。