你把仓库搬到郊外,是因为那里便宜又能无限扩建。但每取一次货都要叫车,仓库内部怎么摆、一次搬多少,就突然变得很重要。数据库迁往对象存储,也面临类似变化:容量和扩展不再是首要难题,远端请求的延迟与次数才是。
对象存储通过对象键取数据,通常只能整体写入或替换,虽然支持读取文件中的指定范围,却不像本地磁盘那样能低延迟地修改任意小块。数据平台常把 Parquet——一种按列压缩、带统计信息的文件格式——放在这里。问题在于,Parquet 再高效,文件摆错位置,查询仍会发出大量远端请求。
这份经验来自 infino 作者 Pranav2612000 的一篇 Reddit 帖子。infino 据作者介绍,是一个用 Rust 编写、采用 Apache-2.0 许可证的嵌入式检索引擎,支持对象存储上的 Parquet SQL、全文检索和向量检索。以下结论均为作者的单一项目经验,缺少独立基准测试。
先决定货架怎么摆
作者首先强调数据布局,也就是数据如何分区、排序并组合成文件。经常一起访问的文件,应尽量放在同一目录。文件内部也要围绕读取步骤排列,让一次 ranged GET——只取对象中某一段的请求——拿到该步骤所需的全部内容。
这两条看似朴素,实际改变了优化顺序。对象存储数据库不能只盯着 SQL 写得快不快,还要先问:执行这条查询,需要跨多少目录、读多少文件、发多少次请求。布局贴合访问习惯,引擎才能少跑远路。
对于很少更新的目录,作者建议额外维护一个索引文件,记录目录内容。查询时读取这个索引,便可用一次 GET 代替 LIST——列举目录中对象的操作。代价是系统必须维护索引与实际目录的一致性,原帖没有展开这部分机制。
大文件不要一口吞
作者称,infino 冷打开一个 1.5 GiB 的 segment(可独立读取的一段数据)时,单次大 GET 较慢。其做法是先从 footer——文件尾部保存的结构和统计信息——开始,按需发起数个较小的并行范围请求。换句话说,先看目录,再派几个人同时取真正需要的货。
这与我们此前对 Polars Cloud 的报道相呼应:对象存储的请求延迟、并发和分段传输,会直接限制读取速度。不过,1.5 GiB 只是 infino 的案例。材料没有给出云服务、网络条件、并发数量或耗时数据,不能把它当作通用阈值。
写入也有类似取舍。作者建议对 50MB 以上文件使用 MultiPart PUT,也就是把大文件分块上传,再由服务端合并。他称这能提高吞吐量,也让追加过程更能承受局部失败。但失败的分块上传不会由 S3 默认清理,需要配置 lifecycle policy(生命周期规则)回收。50MB 同样只是项目经验,并非经材料验证的普遍最佳值。
少传数据,也少付请求费
对于很少变化的文件,作者建议先读 metadata——描述文件版本等状态的元数据——只有发现新版本时才重新获取文件。它类似先检查包裹标签,而不是每次都拆箱。这既可能缩短读取时间,也能减少请求成本。
这里其实体现了缓存的核心取舍。缓存把远端内容暂存在更快的位置,减少重复下载;但系统必须判断远端版本是否变化。原帖片段提出了先查元数据的方向,却没有交代缓存放在哪里、何时失效,以及如何处理并发更新。
为什么值得关注
对象存储数据库的难点,不只是把本地文件路径换成远端地址。目录组织、文件内部排列、范围读取、并发上传和版本检查,会共同决定一次查询要付出多少网络往返。数据库迁移之后,许多性能工作因而从“把计算做快”变成“避免不必要的数据搬运”。
局限与未知
- 原帖标题称有 11 条经验,但供稿在第七条中途截断,不能据此补成完整十一条。
- 作者称删除受 eventual consistency(操作已确认、各处稍后才完全可见)影响,可能数分钟后才完全可见;核查备注指出,这与 Amazon S3 自 2020 年公开宣称的强一致性冲突。作者可能使用了其他兼容 S3 的存储,也可能沿用了过时认识。
- “更快”等判断没有具体耗时、成本或对照环境;1.5 GiB 和 50MB 两个数字不宜直接移植到其他系统。